Heim > Web-Frontend > js-Tutorial > Implementieren Sie die Bindungsanalyse Schritt für Schritt mit nativem Javascript

Implementieren Sie die Bindungsanalyse Schritt für Schritt mit nativem Javascript

高洛峰
Freigeben: 2016-11-03 16:18:35
Original
937 Leute haben es durchsucht

Die Funktion

bind() erstellt eine neue Funktion (eine sogenannte gebundene Funktion), die denselben Funktionskörper (das integrierte Aufrufattribut in der ECMAScript 5-Spezifikation) wie die aufgerufene Funktion (die Zielfunktion der gebundenen Funktion) hat ). Wenn die Zielfunktion aufgerufen wird, ist dieser Wert an den ersten Parameter von bind() gebunden und kann nicht überschrieben werden. Wenn die gebundene Funktion aufgerufen wird, akzeptiert bind() auch voreingestellte Parameter, um sie der ursprünglichen Funktion bereitzustellen. Eine gebundene Funktion kann auch Objekte mithilfe des new-Operators erstellen: Dieser verhält sich wie die ursprüngliche Funktion als Konstruktor. Der angegebene Wert wird ignoriert und die Argumente für den Aufruf werden an die Mock-Funktion übergeben.

Da der Geltungsbereich in JavaScript durch die Umgebung bestimmt wird, in der es ausgeführt wird, unterscheidet sich die Funktionsdefinition häufig von der Umgebung, in der sie tatsächlich ausgeführt wird, und auch der Geltungsbereich ändert sich entsprechend.
Zum Beispiel die folgende Situation:

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
Nach dem Login kopieren

Wie oben erwähnt, besteht eine wichtige Rolle der Bind-Methode darin, einen Bereich an eine Funktion zu binden. Die Bind-Methode ist jedoch nicht mit niedrigeren Versionen kompatibel Hier können wir es manuell implementieren.

Teilen Sie die Schlüsselideen auf

Da die Bindungsmethode die Funktion nicht sofort ausführt, muss sie eine auszuführende Funktion zurückgeben (hier wird der Abschluss verwendet, und eine Funktion kann zurückgegeben werden). function(){ }

Bereichsbindung, hier können Sie die Methode apply oder call verwenden, um xx.call(yy)/xx.apply(yy) zu implementieren

Parameterübergabe aufgrund der Unsicherheit der Parameter. Sie müssen apply verwenden, um das Array zu übergeben (das Beispiel ist klarer) xx.apply(yy,[...Array...] Es ist nicht praktisch, wenn Sie call verwenden, da die Parameter danach Die Aufrufe müssen einzeln aufgelistet werden

Implementierung

Mit den oben genannten Ideen ist der grobe Prototyp bereits klar und der Code sollte einfach zu implementieren sein

Umfang binden , Bindungsparameterübertragung

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)
    }    
}
Nach dem Login kopieren

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
Nach dem Login kopieren

Dynamische Parameter

Die Scope-Bindung der Bindungsmethode wurde oben implementiert, aber der Wermutstropfen ist Da wir eine Funktion zurückgeben, sollte diese beim Aufrufen von Parametern unterstützt werden. Offensichtlich unterstützt das obige fun_new die Übergabe von Parametern beim Aufruf nicht und kann Parameter nur übergeben, wenn testBind gebunden ist, da wir am Ende diese Rückgabefunktion < aufrufen 🎜>

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

这里面的args在绑定的时候就已经确定了,调用的时候值已经固定,
我们并没有处理这个function传递的参数。
Nach dem Login kopieren
Wir werden transformieren

return function(){        return _this.apply(that,
            args.concat(Array.prototype.slice.apply(arguments,[0]))
        )
    }
Nach dem Login kopieren
Array.prototype.slice.apply(arguments,[0]) bezieht sich hier auf eine Reihe von Parametern, die bei Ausführung der Rückgabefunktion übergeben werden Es beginnt mit dem ersten Parameter [0], das vorherige args = Slice.apply(arguments,[1]) bezieht sich auf die Parameter, die bei der Ausführung der testBind-Methode übergeben werden. Beginnend mit dem zweiten [1] sind die beiden also im Wesentlichen unterschiedlich und können nicht verwechselt werden, nur nachdem die beiden zusammengeführt wurden, sind die vollständigen Parameter der zurückgegebenen Funktion

, daher gibt es die folgende Implementierung

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]))
                )
    }    
}
Nach dem Login kopieren
Test

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

fun_new (&#39;hello bind&#39;)
// 作用域绑定 ok
// testBind参数传递 also ok
// 调用参数传递  hello bind
Nach dem Login kopieren
In den beiden oben genannten Methoden zur Parameterübergabe ist die Priorität von bind hoch, wie aus args.concat(Array.prototype.slice.apply(arguments,[0])) hervorgeht, stehen die Parameter von bind im Vordergrund des Arrays.

Prototypenkette

In der offiziellen Dokumentation gibt es einen Satz:

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.
Nach dem Login kopieren
Dies bedeutet, dass die gebundene Funktion nach der Instanziierung durch new den Prototyp erben muss Chain-Methode der Originalfunktion und das während des Bindungsprozesses bereitgestellte This wird ignoriert (das This-Objekt der Originalfunktion wird geerbt), die Parameter werden jedoch weiterhin verwendet.

Hier ist eine Übertragungsfunktion erforderlich, um die Prototypenkette weiterzugeben

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属性值为 &#39;bound &#39;
        bound = function(){
            return _this.apply(that,
                args.concat(Array.prototype.slice.apply(arguments,[0]))
            )
        }    

    fNOP.prototype = _this.prototype;

    bound.prototype = new fNOP();

    return bound;
}
Nach dem Login kopieren
Und der erste Parameter der Bindungsmethode, this, muss nicht übergeben werden. Er muss in zwei Situationen unterteilt werden

Direkt Die Methode nach dem Aufruf von bind

var f = function () { console.log(&#39;不传默认为&#39;+this)  };f.bind()()
// 不传默认为 Window
Nach dem Login kopieren
Wenn Sie also die Bindungsmethode direkt aufrufen apply(that), wird empfohlen, sie in apply(that||window,) zu ändern. Tatsächlich , es ist in Ordnung, es nicht zu ändern, denn wenn es nicht übergeben wird, ist der Standardpunkt window

Die Verwendung von new zum Instanziieren einer gebundenen Methode

ist leicht zu verwechseln. Der Schlüssel liegt darin Finden Sie heraus, was die Standardbindungsmethode tut, wenn Sie new verwenden, und dann können Sie sie klar implementieren

Hier müssen wir sehen, welche Operationen die neue Methode ausführt, zum Beispiel var a = new b()

erstellt ein leeres Objekt a = {}, und diese Variablenreferenz zeigt auf dieses leere Objekt a

Erbt den Prototyp der instanziierten Funktion: 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]))
            )
        }
Nach dem Login kopieren

这里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;; 
}
Nach dem Login kopieren

所以在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;
}
Nach dem Login kopieren

这里的 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;
    }
Nach dem Login kopieren

我看到有些地方写的是

this instanceof fNOP && that ? this : that || window,
Nach dem Login kopieren

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


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage