1. 程式人生 > >bind、call、apply的區別與實現原理

bind、call、apply的區別與實現原理

const con ons 運行時 模擬實現 原理 支持 包裝 ofo

1、簡單說一下bind、call、apply的區別

  三者都是用於改變函數體內this的指向,但是bind與apply和call的最大的區別是:bind不會立即調用,而是返回一個新函數,稱為綁定函數,其內的this指向為創建它時傳入bind的第一個參數,而傳入bind的第二個及以後的參數作為原函數的參數來調用原函數。


var obj = {};

function test() {
    console.log(this === obj);
}

test(); //false

var testObj = test.bind(obj);
testObj();  //true

  apply和call都是為了改變某個函數運行時的上下文而存在的(就是為了改變函數內部this的指向);apply和call的調用返回函數執行結果;

  如果使用apply或call方法,那麽this指向他們的第一個參數,apply的第二個參數是一個參數數組,call的第二個及其以後的參數都是數組裏面的元素,就是說要全部列舉出來;

以下是MDN文檔:


bind語法:

func.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg 當綁定函數被調用時,該參數會作為原函數運行時的this指向。當使用new 操作符調用綁定函數時,該參數無效。
arg1, arg2, ... 當綁定函數被調用時,這些參數將置於實參之前傳遞給被綁定的方法。

call語法:

fun.call(thisArg, arg1, arg2, ...)
thisArg::在fun函數運行時指定的this值。需要註意的是,指定的this值並不一定是該函數執行時真正的this值,如果這個函數處於非嚴格模式下,則指定為null和undefined的this值會自動指向全局對象(瀏覽器中就是window對象),同時值為原始值(數字,字符串,布爾值)的this會指向該原始值的自動包裝對象。
arg1, arg2, ... 指定的參數列表。

apply語法:

fun.apply(thisArg, [argsArray])
thisArg: 在 fun 函數運行時指定的 this 值。需要註意的是,指定的 this 值並不一定是該函數執行時真正的 this 值,如果這個函數處於非嚴格模式下,則指定為 null 或 undefined 時會自動指向全局對象(瀏覽器中就是window對象),同時值為原始值(數字,字符串,布爾值)的 this 會指向該原始值的自動包裝對象。
argsArray: 一個數組或者類數組對象,其中的數組元素將作為單獨的參數傳給 fun 函數。如果該參數的值為null 或 undefined,則表示不需要傳入任何參數。從ECMAScript 5 開始可以使用類數組對象。

區別總結:

當我們使用一個函數需要改變this指向的時候才會用到call,apply,bind
如果你要傳遞的參數不多,則可以使用fn.call(thisObj, arg1, arg2 ...)
如果你要傳遞的參數很多,則可以用數組將參數整理好調用fn.apply(thisObj, [arg1, arg2 ...])
如果你想生成一個新的函數長期綁定某個函數給某個對象使用,則可以使用


const newFn = fn.bind(thisObj);
newFn(arg1, arg2...)

2、bind、call、apply的實現

myBind:


Function.prototype.myBind = function() {
    var _this = this;
    var context = [].shift.call(arguments);// 保存需要綁定的this上下文
    var args = [].slice.call(arguments); //剩下參數轉為數組
    console.log(_this, context, args);
    return function() {
        return _this.apply(context, [].concat.call(args, [].slice.call(arguments)));
    }
};

myCall:


/**
 * 每個函數都可以調用call方法,來改變當前這個函數執行的this關鍵字,並且支持傳入參數
 */
Function.prototype.myCall = function(context) {
    //第一個參數為調用call方法的函數中的this指向
    var context = context || global;
    //將this賦給context的fn屬性
    context.fn = this;//此處this是指調用myCall的function

    var arr = [];
    for (var i=0,len=arguments.length;i<len;i++) {
        arr.push("arguments[" + i + "]");
    }
    //執行這個函數,並返回結果
    var result = eval("context.fn(" + arr.toString() + ")");
    //將this指向銷毀
    delete context.fn;
    return result;
}

myApply:


/**
 * apply函數傳入的是this指向和參數數組
 */
Function.prototype.myApply = function(context, arr) {
    var context = context || global;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn(); //直接執行
    } else {
        var args = [];
        for (var i=0,len=arr.length;i<len;i++) {
            args.push("arr[" + i + "]");
        }
        result = eval("context.fn([" + args.toString() + "])");
    }
    //將this指向銷毀
    delete context.fn;
    return result;
}

  以上是bind、apply、和call的模擬實現

註意:綁定函數(bind函數返回的新函數)不可以再通過apply和call改變其this指向,即當綁定函數調用apply和call改變其this指向時,並不能達到預期效果。

var obj = {};

function test() {
    console.log(this === obj);
}

var testObj = test.bind(obj);
testObj();  //true

var objTest = {
    "作者": "chengbo"
};
/**
 * 預期返回false, 但是testObj是個綁定函數,所以不能改變其this指向
 */
testObj.apply(objTest); //true
testObj.call(objTest); //true

歡迎留言。

原文鏈接:《bind、call、apply的區別與實現原理》

bind、call、apply的區別與實現原理