18

Douglas Crockford の著書 "Javascript: The Good Parts" で、彼はcurry関数と引数を受け取り、その関数に既に追加された引数を返すメソッドのコードを提供しています (明らかに、これは実際には「カレー」が意味するものではなく、「部分適用」)。これは、彼が作成した他のカスタム コードがなくても機能するように変更したコードです。

Function.prototype.curry = function(){
  var slice = Array.prototype.slice,
      args = slice.apply(arguments),
      that = this;
  return function() {
    // context set to null, which will cause `this` to refer to the window
    return that.apply(null, args.concat(slice.apply(arguments)));
  };
};

したがって、add関数がある場合:

var add = function(num1, num2) {
  return num1 + num2;
};

add(2, 4);          // returns 6

すでに引数が 1 つある新しい関数を作成できます。

var add1 = add.curry(1);

add1(2);           // returns 3

それはうまくいきます。しかし、私が知りたいのは、なぜ彼が に設定thisしたのnullかということです。予想される動作は、カリー化されたメソッドが元のメソッドと同じであるということではないでしょうthisか?

私のバージョンのカレーは次のようになります。

Function.prototype.myCurry = function(){
  var slice = [].slice,
      args = slice.apply(arguments),
      that = this;
  return function() {
    // context set to whatever `this` is when myCurry is called
    return that.apply(this, args.concat(slice.apply(arguments)));
  };
};

(これは例のjsfiddleです)

var calculator = {
  history: [],
  multiply: function(num1, num2){
    this.history = this.history.concat([num1 + " * " + num2]);
    return num1 * num2;
  },
  back: function(){
    return this.history.pop();
  }
};

var myCalc = Object.create(calculator);
myCalc.multiply(2, 3);         // returns 6
myCalc.back();                 // returns "2 * 3"

Douglas Crockford のやり方でやろうとすると、次のようになります。

myCalc.multiplyPi = myCalc.multiply.curry(Math.PI);
myCalc.multiplyPi(1);          // TypeError: Cannot call method 'concat' of undefined

私が自分のやり方でそれを行う場合:

myCalc.multiplyPi = myCalc.multiply.myCurry(Math.PI);
myCalc.multiplyPi(1);          // returns 3.141592653589793
myCalc.back();                 // returns "3.141592653589793 * 1"

しかし、ダグラス・クロックフォードが自分のやり方でやったのなら、おそらく正当な理由があると思います。私は何が欠けていますか?

4

4 に答える 4

4

理由 1 - 一般的な解決策を提供するのは容易ではない

問題は、解決策が一般的ではないことです。呼び出し元が新しい関数をどのオブジェクトにも割り当てないか、まったく別のオブジェクトに割り当てた場合、関数は機能しなくmultiplyPiなります。

var multiplyPi = myCalc.multiply.myCurry(Math.PI);
multiplyPi(1);  // TypeError: this.history.concat is not a function

したがって、Crockford のソリューションもあなたのソリューションも、関数が正しく使用されることを保証できません。curry次に、関数は「メソッド」ではなく「関数」でのみ機能し、それを強制するように設定thisすると言う方が簡単かもしれません。nullただし、Crockford は本でそれについて言及していないため、推測することしかできません。

理由 2 - 機能が説明されている

「なぜクロックフォードがあれやこれを使わなかったのか」と尋ねると、「実証された問題に関しては重要ではなかった」という答えが非常にありそうです。Crockford は、関数の章でこの例を使用しています。サブチャプターの目的curryは次のとおりです。

  • 関数が作成および操作できるオブジェクトであることを示す
  • クロージャーの別の使用法を示す
  • 引数を操作する方法を示します。

オブジェクトでの一般的な使用のためにこれを微調整することは、この章の目的ではありません。不可能ではないにしても問題があるため(理由1を参照)、実際に機能するかどうかに疑問を投げかける可能性のあるものnullを配置する場合は、代わりにそこに配置する方が教育的でした(ただし、あなたの場合は役に立ちませんでした:-)) .

結論

そうは言っても、あなたは自分のソリューションに完全に自信を持っていると思います! あなたのケースでは、Crockfordsthisnull. ただし、ソリューションは特定の状況でのみ機能し、100% クリーンではないことに注意する必要があります。次に、クリーンな「オブジェクト指向」ソリューションは、結果のメソッドが同じオブジェクト内にとどまるように、オブジェクト自体にメソッドのクローンを作成するように依頼することです。

于 2014-01-17T12:38:27.823 に答える
4

しかし、私が知りたいのは、なぜ彼がこれを null に設定したのかということです。

本当に理由はありません。おそらく彼は単純化したかったのでしょう。そして、カリー化または部分的に適用する意味のあるほとんどの関数は、 を使用する OOP メソッドではありませんthis。より機能的なスタイルhistoryでは、追加される配列は、関数の別のパラメーター (および場合によっては戻り値) になります。

カリー化されたメソッドが、同じ this を含めて元のメソッドと同じであることが、予想される動作ではないでしょうか?

はい、実装ははるかに理にかなっていますが、部分的に適用された関数が正しいコンテキストで呼び出される必要があるとは思わないかもしれません (オブジェクトに再割り当てすることによって)。

それらについては、特定の-valueを含む部分適用のための Function オブジェクトのbindメソッドを見ているかもしれません。this

于 2014-01-08T18:54:04.227 に答える
2

MDNから:

thisArg fun の呼び出しに提供される this の値。これは、メソッドによって表示される実際の値ではない可能性があることに注意してください。メソッドが非厳密モード コードの関数である場合、null および undefined はグローバル オブジェクトに置き換えられ、プリミティブ値はボックス化されます。

したがって、メソッドが非厳密モードで、最初の引数がnullorの場合undefinedthisそのメソッド内では が参照されますWindow。厳密モードでは、これはnullまたはundefinedです。この Fiddleに実例を追加しました。

さらに、関数がまったく参照されない場合でも、渡してnullundefined害はありませんthisnullそれがおそらく、クロックフォードが彼の例で、物事を過度に複雑にしないために使用した理由です。

于 2014-01-13T09:55:06.823 に答える