ホームページ  >  記事  >  ウェブフロントエンド  >  バインドの学習からJavaScriptでバインドを実装するまでの過程を詳しく解説

バインドの学習からJavaScriptでバインドを実装するまでの過程を詳しく解説

小云云
小云云オリジナル
2018-01-08 09:21:452150ブラウズ

この記事では、JavaScript でバインドを学習してから実装するまでのプロセスを主に紹介します。興味のある方はフォローして学習していただければ幸いです。

bind とは何ですか?

bind() メソッドは、新しい関数を作成し、呼び出されるときに、指定されたパラメータ シーケンスを指定した値に設定します。

var result = fun.bind(thisArg[, arg1[, arg2[, ...]]]) 
result(newArg1, newArg2...)

理解できなくても、大丈夫です、読み続けてください。

bind は具体的に何をするのか

上記の紹介から 3 つの点がわかります。まず、bind メソッドを呼び出すと、新しい関数が返されます (この新しい関数の関数本体は fun と同じである必要があります)。同時に、2 つのパラメーターがバインドで渡されます。最初のパラメーターは this によってポイントされます。つまり、これが渡されるものはすべて、それと同じです。次のコードに示すように:

this.value = 2
var foo = {
  value: 1
}
var bar = function() {
 console.log(this.value)
}
var result = bar.bind(foo)
bar() // 2
result() // 1,即this === foo

2 番目のパラメーターはシーケンスであり、任意の数のパラメーターを渡すことができます。そして、それは新しい関数パラメータの前にプリセットされます。

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家里蹲大学'
}
var result = bar.bind(foo, 'An') //预置了部分参数'An'
result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

result(22, 'University at home') が最終的に呼び出されたとき、bind の呼び出し時に渡された 'An' がすでに含まれていることがわかります。

一言で要約すると、bind を呼び出すと新しい関数が返されます。この関数の this は、bind の最初のパラメータを指しており、これに続くパラメータは、この新しい関数に事前に渡されます。新しい関数が呼び出されるとき、渡されるパラメータは事前​​設定されたパラメータの後に配置され、一緒に新しい関数に渡されます。

自分でバインドを実装する

バインドを実装するには、次の2つの関数を実装する必要があります

関数を返し、これをバインドし、プリセットパラメータを渡します

バインドによって返される関数は、コンストラクタ。したがって、コンストラクターとして使用する場合、これは無効である必要がありますが、渡されるパラメーターは引き続き有効です

1. 関数を返し、これをバインドし、プリセットパラメーターを渡します

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
  console.log(name) // 'An'
  console.log(age) // 22
  console.log(school) // '家里蹲大学'
  console.log(this.value) // 1
}
Function.prototype.bind = function(newThis) {
  var aArgs  = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的预置参数序列
  var that = this
  return function() {
    return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
    //绑定this同时将调用时传递的序列和预置序列进行合并
  }
}
var result = bar.bind(foo, 'An')
result(22, '家里蹲大学')

ここでの詳細は Array.prototype.slice です。 .call (arguments, 1) この文では、arguments 変数は関数の呼び出し時に渡されるパラメータを取得できることがわかりますが、これは配列ではなく、length 属性を持っています。なぜこのように呼び出すことで純粋な配列に変えることができるのでしょうか?次に、V8 のソース コードに戻って分析する必要があります。 #このバージョンのソースコードは、内容が比較的少ない初期バージョンです。

function ArraySlice(start, end) {
 var len = ToUint32(this.length); 
 //需要传递this指向对象,那么call(arguments),
 //便可将this绑定到arguments,拿到其length属性。
 var start_i = TO_INTEGER(start);
 var end_i = len;
 if (end !== void 0) end_i = TO_INTEGER(end);
 if (start_i < 0) {
  start_i += len;
  if (start_i < 0) start_i = 0;
 } else {
  if (start_i > len) start_i = len;
 }
 if (end_i < 0) {
  end_i += len;
  if (end_i < 0) end_i = 0;
 } else {
  if (end_i > len) end_i = len;
 }
 var result = [];
 if (end_i < start_i)
  return result;
 if (IS_ARRAY(this))
  SmartSlice(this, start_i, end_i - start_i, len, result);
 else 
  SimpleSlice(this, start_i, end_i - start_i, len, result);
 result.length = end_i - start_i;
 return result;
};

ソースコードからわかるように、引数の下に length 属性を割り当てて呼び出しをスライスすると、start_i と end_i を通じて最終配列を取得できるため、スライス配列変数に渡すことなく純粋な配列を取得できます。 。

2.バインドによって返される関数はコンストラクターとして使用できます

コンストラクターとして使用する場合、これは new によって生成されたインスタンスを指す必要があり、プロトタイプのプロトタイプを指すプロトタイプ属性も必要です。実例。

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school) {
 ...
 console.log('this.value', this.value)
}
Function.prototype.bind = function(newThis) {
 var aArgs  = Array.prototype.slice.call(arguments, 1)
 var that = this //that始终指向bar
 var NoFunc = function() {}
 var resultFunc = function() {
  return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
 } 
 NoFunc.prototype = that.prototype //that指向bar
 resultFunc.prototype = new NoFunc()
 return resultFunc
}
var result = bar.bind(foo, 'An')
result.prototype.name = 'Lsc' // 有prototype属性
var person = new result(22, '家里蹲大学')
console.log('person', person.name) //'Lsc'

上記のシミュレーション コードは 2 つの重要なことを行います。

1. 返された関数のプロトタイプ属性をシミュレートします。 、プロトタイプで定義されたプロパティとメソッドは、コンストラクター new

var NoFunc = function() {}
...
NoFunc.prototype = that.prototype //that指向bar
resultFunc.prototype = new NoFunc()
return resultFunc

からインスタンスを通じてクエリできるため、上記のコードからわかるように、それは常に bar を指しています。同時に、返された関数は that.prototype (bar.prototype) を継承しています。返された関数のプロトタイプ属性 resultFunc.prototype を bar(that).prototype と等しくしないのはなぜでしょうか? これは、新しいインスタンスがプロトタイプ チェーンにアクセスできるためです。直接割り当てられた場合、新しいオブジェクトは bar 関数のプロトタイプ チェーンを直接変更する可能性があり、これはプロトタイプ チェーン汚染です。そこで、継承 (コンストラクターのプロトタイプ チェーンを親コンストラクターのインスタンスに割り当てる) を使用して、新しいオブジェクトのプロトタイプ チェーンを bar から切り離します。

2. これが通常のバインドに使用されるか、this のポイントを変更するために現在呼び出されるコンストラクターに使用されるかを決定します。

これが現在どこを指しているのかを確認するにはどうすればよいでしょうか? 最初の点から、バインド メソッドによって返される新しい関数にはすでにプロトタイプ チェーンがあることがわかっています。あとは、この点を次のように変更するだけです。シミュレーションを完了します。呼び出された現在の姿勢を決定する方法。答えは「instanceof」です。

instanceof 演算子は、オブジェクトのプロトタイプ チェーンにコンストラクターのプロトタイプ プロパティがあるかどうかをテストするために使用されます。

// 定义构造函数
function C(){} 
function D(){} 
var o = new C();
// true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof C; 
// false,因为 D.prototype不在o的原型链上
o instanceof D;

上記からわかるように、instanceof はこの関数によってオブジェクトが新しいかどうかを判断できます。新しい場合は、このオブジェクトのプロトタイプ チェーンが関数のプロトタイプになるはずです。

それでは、このキーの戻り関数の構造を見てください:

var resultFunc = function() {
  return that.apply(this instanceof that ? 
    this : 
    newThis, 
    aArgs.concat(Array.prototype.slice.call(arguments)))
 }

この中で、最初に、このインスタンスの this 、つまりバインド関数が呼び出された後に返される新しい関数の this であることを認識する必要があります。つまり、これは通常のスコープ環境で実行されるかもしれませんし、方向を変えることも新しいかもしれません。もう一度見てみると、that は常に bar を指しており、そのプロトタイプ チェーン that.prototype は常に存在します。したがって、この新しい関数が新しい操作を実行する場合、this は新しい関数を指し、そのインスタンスは === true になるため、新しい関数を指すポインターとしてこれを apply に渡します。これが通常の呼び出しである場合、これは new によって作成されません。つまり、new 関数はコンストラクターとして使用されず、このインスタンス === false であることは明らかです。今回は通常のバインド呼び出しです。呼び出しの最初のパラメータをこれへのポインタとして使用するだけです。

完全なコード (MDN での実装)

if (!Function.prototype.bind) {
 Function.prototype.bind = function(oThis) {
  if (typeof this !== 'function') {
   // closest thing possible to the ECMAScript 5
   // internal IsCallable function
   throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
  }

  var aArgs  = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP  = function() {},
    fBound = function() {
     return fToBind.apply(this instanceof fNOP
         ? this
         : oThis,
         aArgs.concat(Array.prototype.slice.call(arguments)));
    };

  if (this.prototype) {
   // Function.prototype doesn't have a prototype property
   fNOP.prototype = this.prototype; 
  }
  fBound.prototype = new fNOP();
  return fBound;
 };
}

可以看到,其首先做了当前是否支持bind的判定,不支持再实行兼容。同时判断调用这个方法的对象是否是个函数,如果不是则报错。

同时这个模拟的方法也有一些缺陷,可关注MDN上的Polyfill部分

小结

模拟bind实现最大的一个缺陷是,模拟出来的函数中会一直存在prototype属性,但是原生的bind作为构造函数是没有prototype的,这点打印一下即可知。不过这样子new出来的实例没有原型链,那么它的意义是什么呢。

相关推荐:

Jquery中.bind()、.live()、.delegate()和.on()之间的区别实例分享

jQuery中关于bind()函数详解

Js的this指向 apply().call(),bind()的问题

以上がバインドの学習からJavaScriptでバインドを実装するまでの過程を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。