cocos2d - JS 進階主題 call() 、apply() 和 bind() 解析

Cleve_baby發表於2018-07-05

Tips : Call 和 apply 非常類似 以Call方法為例.

Call方法 :

定義:

呼叫一個物件的一個方法,以另一個物件替換當前物件。

說明:

Call 方法可以用來代替另一個物件呼叫一個方法。 
Call 方法可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件。 
如果沒有提供 thisObj 引數,那麼 Global 物件被用作 thisObj。

JavaScript 程式碼 :

    var Animal = cc.Sprite.extend({
        name : null,
        ctor: function(){
            this._super();
            this.name = "Animal";
        },

        showName: function(){
            cc.log(this.name);
        },
    })

    var Cat = cc.Sprite.extend({
        name : null,
        ctor: function(){
            this._super();
            this.name = "Cat";
        },
    }) 

    var cat = new Cat();
    var animal = new Animal();

    animal.showName()             //結果為Animal
    cat.showName();               //報錯

    animal.showName.call(cat);    //結果為Cat

    //通過call或apply方法,將原本屬於Animal物件的showName()方法交給物件cat來使用了。    
    //animal.showName.apply(cat,[]);  

call 的意思是把 animal 的方法放到cat上執行,原來cat是沒有showName() 方法,現在是把animal 的showName()方法放到 cat上來執行,所以this.name 應該是 Cat

JavaScript 程式碼 :

    function add(a,b)  
    {  
        alert(a+b);  
    }  
    function sub(a,b)  
    {  
        alert(a-b);  
    }  

    add.call(sub,3,1);   

這個例子中的意思就是用 add 來替換 sub,add.call(sub,3,1) == add(3,1) ,所以執行結果為:alert(4);

注意:js 中的函式其實是物件,函式名是對 Function 物件的引用。


Call 和 Apply 的區別 :

定義一個log方法 :

    function log(msg){
        console.log(msg);
    }

log(1) // 1 
log(1, 2) // 1

可以看出來 這個方法只能接受一個引數, 雖然可以解決最基本的需求,但是當傳入引數的個數是不確定的時候,上面的方法就失效了,這個時候就可以考慮使用 apply 替代 call .

注意 :

這裡傳入多少個引數是不確定的,所以使用apply是最好的,方法如下:

function log(){
    console.log.apply(console, arguments);
}

再次嘗試 : 
log(1) // 1 
log(1, 2) // 1 2

結果就正確了!

其實, 在 cocos2d - js 裡 cc.log 的實現方式正是如此 !

這裡寫圖片描述


bind() 方法 :

bind方法本身和 apply 、call很類似, 也同樣是改變this指向的方法, 但是bind使用的地方與apply 和 call 有些不同.

注意 :

bind方法會建立一個新函式, 稱作繫結函式, 當呼叫bind函式時, 為以第一個引數為this(通常傳入的也是this), 傳入bind的第二個以及以後的引數加上繫結函式執行時本身的引數, 按照順序作為原函式的引數來呼叫函式.

在具體例項中, 我們通常用self、that等, 用來儲存this, 例如 :

    var foo = {
        num: 1,
        eventBind: function(){
            var self = this;
            $('.someClass').on('click',function(event) {
                console.log(self.num);     //1
            });
        }
    }

————————-下文為網路原文————————- 
由於 Javascript 特有的機制,上下文環境在 eventBind:function(){ } 過渡到 $(‘.someClass’).on(‘click’,function(event) { }) 發生了改變,上述使用變數儲存 this 這些方式都是有用的,也沒有什麼問題。當然使用 bind() 可以更加優雅的解決這個問題:

    var foo = {
        bar : 1,
        eventBind: function(){
            $('.someClass').on('click',function(event) {
                console.log(this.bar);      //1
            }.bind(this));
        }
    }

在上述程式碼裡,bind() 建立了一個函式,當這個click事件繫結在被呼叫的時候,它的 this 被傳入(呼叫bind()時傳入的引數)。因此,這裡我們傳入想要的上下文 this( foo ),到 bind() 函式中。然後,當回撥函式被執行的時候, this 便指向 foo 物件。

例如:

    var bar = function(){
    console.log(this.x);
    }

    var foo = {
    x:3
    }

    bar(); // undefined
    var func = bar.bind(foo);
    func(); // 3

這裡我們建立了一個新的函式 func,當使用 bind() 建立一個繫結函式之後,它被執行的時候,它的 this 會被設定成 foo , 而不是像我們呼叫 bar() 時的全域性作用域。

有個有趣的問題,如果連續 bind() 兩次,亦或者是連續 bind() 三次那麼輸出的值是什麼呢?像這樣:

    var bar = function(){
        console.log(this.x);
    }

    var foo = {
        x:3
    }

    var sed = {
        x:4
    }

    var func = bar.bind(foo).bind(sed);
    func(); //?

    var fiv = {
        x:5
    }

    var func = bar.bind(foo).bind(sed).bind(fiv);
    func(); //?

答案是,兩次都仍將輸出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是無效的。更深層次的原因, bind() 的實現,相當於使用函式在內部包了一個 call / apply ,第二次 bind() 相當於再包住第一次 bind() ,故第二次以後的 bind 是無法生效的。


Apply 、Call 、 Bind 比較

那麼 apply、call、bind 三者相比較,之間又有什麼異同呢?何時使用 apply、call,何時使用 bind 呢。簡單的一個栗子:


    var A = {
        name : "A",
    }

    var B = {
        name : "B",

        getName : function(){
            console.log(this.name);
        },
    }

    B.getName.bind(A)();  //A  
    B.getName.call(A);    //A
    B.getName.apply(A);   //A

三個輸出的都是A,但是注意看使用 bind() 方法的,他後面多了對括號。

也就是說,區別是,當你希望改變上下文環境之後並非立即執行,而是回撥執行的時候,使用 bind() 方法。而 apply/call 則會立即執行函式。


總結:

apply 、 call 、bind 三者都是用來改變函式的this物件的指向的; 
apply 、 call 、bind 三者第一個引數都是this要指向的物件,也就是想指定的上下文; 
apply 、 call 、bind 三者都可以利用後續引數傳參; 
bind 是返回對應函式,便於稍後呼叫;apply 、call 則是立即呼叫 。

相關文章