ホームページ > ウェブフロントエンド > jsチュートリアル > ネイティブ JavaScript を使用してバインド分析を段階的に実装する

ネイティブ JavaScript を使用してバインド分析を段階的に実装する

高洛峰
リリース: 2016-11-03 16:18:35
オリジナル
963 人が閲覧しました

bind() 関数は、呼び出される関数 (バインド関数のターゲット関数) と同じ関数本体 (ECMAScript 5 仕様の組み込み呼び出し属性) を持つ新しい関数 (バインド関数と呼ばれる) を作成します。ターゲット関数が呼び出されるとき、この値はbind()の最初のパラメータにバインドされ、オーバーライドできません。バインドされた関数が呼び出されるとき、bind() は元の関数に提供するプリセット パラメーターも受け入れます。バインドされた関数は、new 演算子を使用してオブジェクトを作成することもできます。これは、コンストラクターとして元の関数と同様に動作します。指定された this 値は無視され、呼び出しの引数がモック関数に指定されます。

JavaScriptのスコープは実行環境によって決まるため、関数の定義が実際に実行される環境と異なる場合が多く、それに伴ってスコープも変わります。
たとえば、次のような状況です。

var id = 'window';
//定义一个函数,但是不立即执行
var test = function(){
    console.log(this.id)
}
test() // window
//把test作为参数传递
var obj = {
    id:'obj',
    hehe:test
}
//此时test函数运行环境发生了改变
obj.hehe() // 'obj'
//为了避免这种情况,javascript里面有一个bind方法可以在函数运行之前就绑定其作用域,修改如下

var id = 'window';
var test = function(){
    console.log(this.id)
}.bind(window)
var obj = {
    id:'obj',
    hehe:test
}
test() // window
obj.hehe() // window
ログイン後にコピー

上で紹介したように、bind メソッドの重要な役割はスコープを関数にバインドすることですが、ここではバインド メソッドは下位バージョンのブラウザと互換性がありません。 。

重要なアイデアを分割します

バインド メソッドは関数をすぐに実行しないため、実行される関数を返す必要があります (ここではクロージャが使用されており、関数を返すことができます) return function(){}

スコープ バインディング, ここでは、apply または call メソッドを使用して、xx.call(yy)/xx.apply(yy)

パラメータ転送を実装できます。パラメータが不確実であるため、配列を渡すには apply を使用する必要があります。より明確) xx.apply(yy, [...Array...])、呼び出しの背後にあるパラメーターを 1 つずつリストする必要があるため、呼び出しを使用するのは不便です

実装

上記のアイデアでは、大まかなプロトタイプはすでに明確になっており、コードは実装も簡単です

バインディングスコープとバインディングパラメータ

Function.prototype.testBind = function(that){    var _this = this,        /*
        *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性
        *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外,
        *后面的所有参数都需要作为数组参数传递
        *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1)
        */
        slice = Array.prototype.slice,
        args = slice.apply(arguments,[1]);    //返回函数    
    return function(){        //apply绑定作用域,进行参数传递
        return _this.apply(that,args)
    }    
}
ログイン後にコピー

test

var test = function(a,b){  
  console.log('作用域绑定 '+ this.value)  
    console.log('testBind参数传递 '+ a.value2)    
    console.log('调用参数传递 ' + b)
}var obj = {
    value:'ok'}var fun_new = test.testBind(obj,{value2:'also ok'})

fun_new ('hello bind')// 作用域绑定 ok// testBind参数传递 also ok// 调用参数传递  undefined
ログイン後にコピー

動的パラメータ

バインドメソッドのスコープバインディングは上記で実装されていますが、フライイン軟膏は、 function を返すので、呼び出し時にパラメータの受け渡しをサポートする必要があるということです。明らかに、上記の fun_new は呼び出し時にパラメータの受け渡しをサポートしておらず、testBind がバインドされているときにのみパラメータを渡すことができます。これは、最終的に呼び出すのはこの return であるためです。 function

function(){
        return _this.apply(that,args)
    }    

这里面的args在绑定的时候就已经确定了,调用的时候值已经固定,
我们并没有处理这个function传递的参数。
ログイン後にコピー

Transformation

return function(){        return _this.apply(that,
            args.concat(Array.prototype.slice.apply(arguments,[0]))
        )
    }
ログイン後にコピー

Array.prototype.slice.apply(arguments,[0])があります。ここでは、return関数が実行されるときに渡される一連のパラメータを参照しているため、最初のパラメータ[0]から始まります。前の args = slide.apply(arguments,[1]) は、testBind メソッドの実行時に渡されるパラメーターを参照しているため、2 番目のパラメーター [1] 以降、この 2 つは本質的に異なり、マージした後にのみ混同することはできません。 two これは return 関数の完全なパラメータです

そこで、次の実装があります

Function.prototype.testBind = function(that){
    var _this = this,
        slice = Array.prototype.slice,
        args = slice.apply(arguments,[1]);
    return function(){
        return _this.apply(that,
                    args.concat(Array.prototype.slice.apply(arguments,[0]))
                )
    }    
}
ログイン後にコピー

Test

var test = function(a,b){
    console.log('作用域绑定 '+ this.value)
    console.log('testBind参数传递 '+ a.value2)
    console.log('调用参数传递 ' + b)
}
var obj = {
    value:'ok'
}
var fun_new = test.testBind(obj,{value2:'also ok'})

fun_new ('hello bind')
// 作用域绑定 ok
// testBind参数传递 also ok
// 调用参数传递  hello bind
ログイン後にコピー

上記の 2 つのパラメータ渡しメソッドのうち、bind が最も優先されます。args.concat(Array.prototype.slice. apply(arguments,[ 0])) バインドのパラメータが配列の前にあることもわかります。

プロトタイプチェーン

公式ドキュメントには次の一文があります:

A bound function may also be constructed using the new operator: doing
so acts as though the target function had instead been constructed.
The provided this value is ignored, while prepended arguments are
provided to the emulated function.
ログイン後にコピー

これは、バインドされた関数が new によってインスタンス化された後、元の関数のプロトタイプ チェーン メソッドを継承する必要があり、バインド中に提供される this を意味します。プロセスは無視されます(関数の元の this オブジェクトを継承します)が、パラメーターは引き続き使用されます。
ここでは、プロトタイプチェーンを渡すために伝達関数が必要です

fNOP = function () {} //创建一个中转函数
fNOP.prototype = this.prototype;
xx.prototype = new fNOP() 
修改如下
Function.prototype.testBind = function(that){
    var _this = this,
        slice = Array.prototype.slice,
        args = slice.apply(arguments,[1]),
        fNOP = function () {},
        //所以调用官方bind方法之后 有一个name属性值为 'bound '
        bound = function(){
            return _this.apply(that,
                args.concat(Array.prototype.slice.apply(arguments,[0]))
            )
        }    

    fNOP.prototype = _this.prototype;

    bound.prototype = new fNOP();

    return bound;
}
ログイン後にコピー

そして、bindメソッドの最初のパラメータthisを渡す必要がない場合は2つの状況があります

bind後にメソッドを直接呼び出す

var f = function () { console.log('不传默认为'+this)  };f.bind()()
// 不传默认为 Window
ログイン後にコピー

ので、直接呼び出します。 binding メソッドを定義する場合、apply(that を apply(that||window, に変更することをお勧めします。実際には、これが渡されない場合はデフォルトのポイントが window になるため、変更しなくても大丈夫です。 new を使用してバインドされたメソッドをインスタンス化します。混乱しやすいですが、new を実装したときに標準のバインド メソッドが何を行うのかを明確にすることが重要です。ここでは、new メソッドがどのような操作を行うかを確認する必要があります。例: var a = new b()

空のオブジェクト a = { } を作成します。この変数参照はこの空のオブジェクトを指します a

はインスタンス化された関数のプロトタイプを継承します: a.__proto__ = b.prototype

被实例化方法b的this对象的属性和方法将被加入到这个新的 this 引用的对象中: b的属性和方法被加入的 a里面

新创建的对象由 this 所引用 :b.call(a)

通过以上可以得知,如果是var after_new = new bindFun(); 由于这种行为是把原函数当成构造器,那么那么最终实例化之后的对象 this需要继承自原函数, 而这里的 bindFun 目前是

function(){            return _this.apply(that || window,
                args.concat(Array.prototype.slice.apply(arguments,[0]))
            )
        }
ログイン後にコピー

这里apply的作用域是绑定的that || window,在执行 testBind()的时候就已经固定,并没有把原函数的this对象继承过来,不符合我们的要求,我们需要根据apply的特性解决这个问题:

在一个子构造函数中,你可以通过调用父构造函数的 `apply/call` 方法来实现继承

例如

function Product(name, price) {
  this.name = name;
  this.price = price;

  if (price < 0) {
    throw RangeError(&#39;Cannot create product &#39; +
                      this.name + &#39; with a negative price&#39;);
  }
}

function Food(name, price) {
  Product.call(this, name, price); 
  this.category = &#39;food&#39;;
}

//等同于(其实就是把Product放在Food内部执行了一次)
function Food(name, price) { 
    this.name = name;
    this.price = price;
    if (price < 0) {
        throw RangeError(&#39;Cannot create product &#39; +
                this.name + &#39; with a negative price&#39;);
    }

    this.category = &#39;food&#39;; 
}
ログイン後にコピー

所以在new新的实例的时候实时将这个新的this对象 进行 apply 继承原函数的 this 对象,就可以达到 new 方法里面的第 3 步的结果

apply(that||window,
//修改为 如果是new的情况,需要绑定new之后的作用域,this指向新的实例对象
apply(isNew ? this : that||window,  ==>

Function.prototype.testBind = function(that){
    var _this = this,
        slice = Array.prototype.slice,
        args = slice.apply(arguments,[1]),
        fNOP = function () {},
        //所以调用官方bind方法之后 有一个name属性值为 &#39;bound &#39;
        bound = function(){
            return _this.apply(isNew ? this : that||window,
                args.concat(Array.prototype.slice.apply(arguments,[0]))
            )
        }    

    fNOP.prototype = _this.prototype;

    bound.prototype = new fNOP();

    return bound;
}
ログイン後にコピー

这里的 isNew 是区分 bindFun 是直接调用还是被 new 之后再调用,通过原型链的继承关系可以知道,
bindFun 属于 after_new的父类,所以 after_new instanceof bindFun 为 true,同时
bindFun.prototype = new fNOP() 原型继承; 所以 fNOP 也是 after_new的父类, after_new instanceof fNOP 为 true

最终结果

Function.prototype.testBind = function(that){
        var _this = this,
            slice = Array.prototype.slice,
            args = slice.apply(arguments,[1]),
            fNOP = function () {},
            bound = function(){
                //这里的this指的是调用时候的环境
                return _this.apply(this instanceof  fNOP ? this : that||window,
                    args.concat(Array.prototype.slice.apply(arguments,[0]))
                )
            }    
        fNOP.prototype = _this.prototype;
    
        bound.prototype = new fNOP();
    
        return bound;
    }
ログイン後にコピー

我看到有些地方写的是

this instanceof fNOP && that ? this : that || window,
ログイン後にコピー

我个人觉得这里有点不正确,如果绑定时候不传参数,那么that就为空,那无论怎样就只能绑定 window作用域了。


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート