《JavaScript設計模式與開發實踐》——JavaScript中的call和apply

幾點基礎知識:

  • Function.prototype.callFunction.prototype.apply 方法用於改變內部函數 this 的指向
  • func.apply(thisArg[, argsArray]) 接受兩個參數,第一個參數指定了 func 函數運行時的this,第二個參數是一個數組或者類數組對象
  • func.call(thisArg, arg1, arg2, ...) 方法的作用和 apply() 方法類似,只有一個區別,就是 call()方法接受的是若干個參數的列表,而apply()方法接受的是一個包含多個參數的數組

舉個栗子:
var hero = {
    name:'奧特曼'
};
function beatMonster() {
    console.log(this.name);
}
// call把this由window指向hero對象,並執行了beatMonster方法
beatMonster.call(hero); // 輸出:奧特曼
模擬實現call

思路:
1. 將函數設置爲對象的屬性
2. 執行該函數
3. 刪除對象中的該函數

Function.prototype.myCall = function(context) { 
    // context即爲hero
    // this爲被調用的方法  因爲myCall在beatMonster的上下文中執行,所以this指向beatMonster
    context.fn = this; 
    context.fn();
    delete context.fn;
}
beatMonster.myCall(hero); // 輸出:奧特曼 沒毛病(・。・)

// 接下來考慮有參數的情況
function beatMonster(weapon, level) {
    console.log(this.name);
    console.log(weapon);
    console.log(level);
}
beatMonster.call(hero,'動感光波',10); // 輸出:奧特曼 動感光波 10

// 因爲參數的實際情況不明,所以使用函數內部的arguments(類數組對象)來獲取實參,先憑本能搞一波
Function.prototype.myCall = function(context) {
    // 借用 Array.prototype的方法拿到實參數組
    var args = [].slice.apply(arguments,[1]);
    context.fn = this; 
    context.fn.apply(context, args);
    //這裏可以用es6的...操作符 context.fn(...args);但call是es3的方法
    delete context.fn;
}
beatMonster.myCall(hero,'巴拉拉能量',8); // 輸出:奧特曼 巴拉拉能量 8 
// 以上的實現從結果來看沒毛病,但是感覺怪怪的,模擬call的時候用他兄弟apply,那還模擬個啥
// 改下改下 用個eval方法
Function.prototype.myCall = function(context) {
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args[i-1] = "arguments["+i+"]";
    }
    context.fn = this;
    // 這裏 args 會自動調用 Array.toString() 這個方法
    eval('context.fn('+ args +')');
    delete context.fn;
}

看起來好像成功了 (´ω`★)
然而 還有一種情況 beatMonster.myCall(null); 報錯 Cannot set property 'fn' of null
再加上兩點必須注意:

  • 凡是方法中使用到上下文語境的,一定要考慮默認的上下文(this)
  • 凡是編碼對象是function的,一定要考慮返回值

於是代碼改爲:

Function.prototype.myCall = function(context) {
    context = context || window;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args[i-1] = "arguments["+i+"]";
    }
    context.fn = this;
    // 這裏 args 會自動調用 Array.toString() 這個方法
    var result = eval('context.fn('+ args +')');
    delete context.fn;
    return result;
}

(๑•̀ㅂ•́)و✧

模擬實現apply

直接上代碼:

Function.prototype.myApply = function(context, arr) {
    context = context || window;
    context.fn = this;
    var result;
    if(!arr) {
        result = context.fn();
    } else {
        var args = [];
        for(var i = 0, len = arr.length; i < len; i++) {
            args[i] = "arr["+i+"]";
        }
        result = eval('context.fn('+ args +')');
    }
    delete context.fn;
    return result;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章