JavaScript重識bind、call、apply

前言——this的一些誤解

  1. 是指向自身
  2. this 在任何狀況下都指向函數的詞法做用域

思考:javascript

function foo() {
    var a = 2;
    this.bar(); 
}
function bar() { 
    console.log( this.a );
}
foo(); // ReferenceError: a is not defined ?
複製代碼

this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。java

當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包 含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的 其中一個屬性,會在函數執行的過程當中用到。數組

這裏要說的call,apply,bind都是來改變this的指向的瀏覽器

一、call、apply、bind對比

call,apply能夠屢次改變綁定對象;只是apply接受數組格式參數;緩存

  • 一、都是用來改變函數的this對象的指向的。
  • 二、第一個參數都是this要指向的對象。
  • 三、均可以利用後續參數傳參。

bind()方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,傳入 bind() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。bash

bind 是返回對應函數,便於稍後調用;apply 、call 則是當即調用 。app

bind 這些改變上下文的 API 了,對於這些函數來講,this 取決於第一個參數,若是第一個參數爲空,那麼就是 window。函數

二、關於綁定

  1. 默認的 this 綁定, 就是說 在一個函數中使用了 this, 可是沒有爲 this 綁定對象. 這種狀況下, 非嚴格默認, this 就是全局變量 Node 環境中的 global, 瀏覽器環境中的 window.
  2. 隱式綁定: 使用 obj.foo() 這樣的語法來調用函數的時候, 函數 foo 中的 this 綁定到 obj 對象.
  3. 顯示綁定: foo.call(obj, ...), foo.apply(obj,[...]), foo.bind(obj,...)
  4. 構造綁定: new foo() , 這種狀況, 不管 foo 是否作了綁定, 都要建立一個新的對象, 而後 foo 中的 this 引用這個對象.

優先級: 構造綁定>顯示綁定>隱式綁定>默認的 this 綁定 post

三、硬綁定

bind只能被綁定一次;以第一次爲準;優化

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
foo.bind(obj).call(obj2)  // name: obj
foo.bind(obj).bind(obj2)()  // name: obj
複製代碼

bind 內部就是 包了一個apply;等到調用的時候再執行這個包含apply的函; 實際上,ES5 中內置的 Function.prototype.bind(..) 更加複雜。下面是 MDN 提供的一種bind(..) 實現:

詳細請看 爲何JavaScript屢次綁定只有一次生效?

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        //一個函數去調用,也就是說bind,call,apply的this是個函數;
        //而後再去改變這個函數裏面的this;
        if (typeof this !== "function") {
         // 與 ECMAScript 5 最接近的
         // 內部 IsCallable 函數 
         throw new TypeError(
          "Function.prototype.bind - what is trying " +
         "to be bound is not callable"
         ); 
        }
        //這裏將初始化的參數緩存起來;
        var aArgs = Array.prototype.slice.call( arguments, 1 ),
        // ftoBind 指向要bind的函數;
        fToBind = this,
        // 返回一個新函數
        fNOP = function(){}, 
        fBound = function(){
        //fToBind.apply 改變綁定this;
        // 執行的時候判斷,當前this等於fNOP而且傳入oThis,就設置成當前this,否則就改變成初始化傳入的oThis;
           return fToBind.apply( 
            (this instanceof fNOP && oThis ? this : oThis ),
            aArgs.concat(Array.prototype.slice.call( arguments ) )
            ); 
        };
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    };
}

複製代碼

解釋(this instanceof fNOP && oThis ? this : oThis ) 這段代碼請看 javascript 深刻解剖bind內部機制

四、軟綁定

硬綁定這種方式能夠把 this 強制綁定到指定的對象(除了使用 new 時),防止函數調用應用默認綁定規則。問題在於,硬綁定會大大下降函數的靈活性,使用硬綁定以後就沒法使用隱式綁定或者顯式綁定來修改 this。

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj) { 
        var fn = this; // 捕獲全部 curried 參數
        var curried = [].slice.call( arguments, 1 );
        var bound = function() {
            return fn.apply((!this || this === (window || global)) ?
            obj : this,curried.concat.apply( curried, arguments ) ); 
        }; 
        bound.prototype = Object.create( fn.prototype );
        return bound;
    }; 
} 
複製代碼

它會對指定的函 數進行封裝,首先檢查調用時的 this,若是 this 綁定到全局對象或者 undefined,那就把 指定的默認對象 obj 綁定到 this,不然不會修改 this。此外,這段代碼還支持可選的柯里化;

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj); 
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看! 
setTimeout( obj2.foo, 10 );// name: obj <---- 應用了軟綁定
複製代碼

注意

若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值 在調用時會被忽略,實際應用的是 默認綁定規則:

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}
// 把數組「展開」成參數
foo.apply( null, [2, 3] ); // a:2, b:3

// 使用 bind(..) 進行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
複製代碼

用 null 來忽略 this 綁定可能會有反作用。若是某個函數確實使用了 this(好比第三方庫中的一個函數),那默認綁定規則會把 this 綁定到全局對象(在瀏覽 器中這個對象是 window),這將致使不可預計的後果(好比修改全局對象)。

優化:用Object.create(null)替代 null 或者 undefined


  • 參考:《你不知道的javascript》
相關文章
相關標籤/搜索