我要懂系列1-call和apply

call和apply

fn.call(isThis, arg1, arg2, ....)
fn.apply(isThis, [arg1, arg2, ....])

相同點:數組

  1. 改變 this 指向
  2. 能夠傳參
  3. 當即調用

區別:app

  1. apply 接收一個數組參數,call 直接接收參數
  2. apply 的性能會比call差,由於要對數組參數進行判斷和解構

模擬實現:性能

Function.prototype.callLike = function (isThis) {
    //...
}

Function.prototype.applyLike = function (isThis) {
    //...
}
  1. 傳遞的 isThis 若是是 undefined 或者 null,那麼 this 就是 window,不然傳遞進來的就是要指向的this
  2. call 從第二個參數開始就是要調用時用到參數
  3. apply 的第二個參數爲數組,數組中的元素就是調用時用到參數

第一個條件很簡單,判斷下 isThis 的類型便可this

isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;


function isNull (value) {
    return typeof value === 'object' && !value === true
}

第二個條件和第三個條件是同樣的,call 的參數咱們須要處理下,由於咱們預期不到它的參數個數prototype

咱們從第二個參數開始遍歷一遍 arguments,而後放到一個數組裏面去code

var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
    args.push(arguments[i]);
}

可是最關鍵的調用怎麼辦呢,怎麼改變 this 的指向,通常來講誰調用誰就是 this,咱們要想改變 this,那麼就要用傳遞進來的 isThis 來調用對象

isThis.fn = this;
isThis.fn();
delete isThis.fn;

這樣的話就改變了 this 的指向,以後再 delete 掉,就 ok 了,可是這裏會有一個問題,若是傳遞進來的是值類型呢,值類型咱們是不能給它添加屬性和方法的,因此 isThis.fn() 確定會提示 isThis.fn is not a function,這裏咱們能夠想想值類型也是能夠像對象同樣有屬性和方法的,而且能夠添加屬性和方法,可是爲何賦值完後就找不到呢作用域

這裏要說下包裝類型了,值類型按理說是不可能有本身的屬性和方法的,可是考慮到有時候須要處理下雜七雜八的雜事,因此當咱們訪問或者賦值的時候,它會臨時給咱們建立一個對應的包裝對象,在咱們訪問或者賦值結束後那麼這個包裝對象就會被清理掉,那麼咱們就能夠這樣作,來模擬下包裝對象字符串

var valueType = typeof isThis;
if (valueType === 'string') {
    isThis = new String(isThis)
}else if (valueType === 'number') {
    isThis = new Number(isThis)
}else if (valueType === 'boolean') {
    isThis = new Boolean(isThis)
}else if (valueType === 'symbol') {
    isThis = Object(isThis)
}

恩,這樣一來就差很少了,接下來看看傳參,args 的元素纔是咱們想要的參數,因此怎麼拆開string

var result = eval('isThis.fn(' + args.join() + ')');

使用 eval,得益於 eval 強大的能力,咱們能夠把字符串當作 js 代碼來執行,而且能夠獲得返回值,完美

可是這裏會有一個問題,若是參數是個對象,那麼 eval 對參數 toString 後咱們咱們就得不到想要的參數了,因此這裏改造下,由於 eval 能夠動態的改變做用域

var args = [];
for (var i = 1, l = arguments.length; i< l; i++) {
    args.push('arguments[' + i  + ']');
}
var result = eval('isThis.fn(' + args.join() + ')');

這裏咱們用 args 存放着 arguments[1] arguments[1]... 這樣的字符串,那麼 eval 執行的時候會在上下文查找並綁定所須要的變量,這樣就能夠實現參數傳遞了

完整代碼:

Function.prototype.callLike = function (isThis) {
    isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;

    var valueType = typeof isThis;
    if (valueType === 'string') {
        isThis = new String(isThis)
    }else if (valueType === 'number') {
        isThis = new Number(isThis)
    }else if (valueType === 'boolean') {
        isThis = new Boolean(isThis)
    }else if (valueType === 'symbol') {
        isThis = Object(isThis)
    }
    
    isThis.fn = this;

    var args = [];
    for (var i = 1, l = arguments.length; i< l; i++) {
        args.push('arguments[' + i  + ']');
    }

    var result = eval('isThis.fn(' + args.join() + ')');
    delete isThis.fn;

    function isNull (value) {
        return typeof value === 'object' && !value === true
    }

    return result;
}
Function.prototype.applyLike = function (isThis, args) {
    isThis = typeof isThis === 'undefined' || isNull(isThis) ? window : isThis;

    var valueType = typeof isThis;
    if (valueType === 'string') {
        isThis = new String(isThis)
    }else if (valueType === 'number') {
        isThis = new Number(isThis)
    }else if (valueType === 'boolean') {
        isThis = new Boolean(isThis)
    }else if (valueType === 'symbol') {
        isThis = Object(isThis)
    }
    
    isThis.fn = this;

    var args = [];
    for (var i = 1, l = arguments.length; i< l; i++) {
        args.push('arguments[' + i  + ']');
    }
    
    var result = eval('isThis.fn(' + args.join() + ')');
    delete isThis.fn;

    function isNull (value) {
        return typeof value === 'object' && !value === true
    }

    return result;
}
相關文章
相關標籤/搜索