模擬call和apply的實現

前沿:數組

學習就比如是座大山,人們沿着不一樣的路爬山,分享着本身看到的風景。你不必定能看到別人看到的風景,體會到別人的心情。只有本身去爬山,才能看到不同的風景,體會才更加深入。bash


進入正題:app

call() 方法調用一個函數, 其具備一個指定的 this 值和分別地提供的參數(參數的列表)。
函數

call()apply()的區別在於,call()方法接受的是若干個參數的列表,而apply()方法接受的是一個包含多個參數的數組
學習

舉個例子:測試

var func = function(arg1, arg2) {
     ...
};

func.call(this, arg1, arg2); // 使用 call,參數列表
func.apply(this, [arg1, arg2]) // 使用 apply,參數數組複製代碼

call的模擬實現:

先看下面一個簡單的例子ui

var value = 1;
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1複製代碼

經過上面的介紹咱們知道,call()主要有如下兩點this

  • 一、call()改變了this的指向
  • 二、函數 bar 執行了

模擬實現第一步

若是在調用call()的時候把函數 bar()添加到foo()對象中,即以下spa

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value);
    }
};

foo.bar(); // 1複製代碼

這個改動就能夠實現:改變了this的指向而且執行了函數barprototype

可是這樣寫是有反作用的,即給foo額外添加了一個屬性,怎麼解決呢?

解決方法很簡單,用 delete 刪掉就行了。

因此只要實現下面3步就能夠模擬實現了。

  • 一、將函數設置爲對象的屬性:foo.fn = bar
  • 二、執行函數:foo.fn()
  • 三、刪除函數:delete foo.fn
// 初版
Function.prototype.call2 = function(context) {
    // 首先要獲取調用call的函數,用this能夠獲取
    context.fn = this; 		// foo.fn = bar
    context.fn();			// foo.fn()
    delete context.fn;		// delete foo.fn
}

// 測試一下
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call2(foo); // 1複製代碼

完美!

模擬實現第二步

初版有一個問題,那就是函數 bar 不能接收參數,因此咱們能夠從 arguments中獲取參數,取出第二個到最後一個參數放到數組中,爲何要拋棄第一個參數呢,由於第一個參數是 this

類數組對象轉成數組的方法上面已經介紹過了,可是這邊使用ES3的方案來作。

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}複製代碼

參數數組搞定了,接下來要作的就是執行函數 context.fn()

context.fn( args.join(',') ); // 這樣不行複製代碼

上面直接調用確定不行,args.join(',')會返回一個字符串,並不會執行。

這邊採用 eval方法來實現,拼成一個函數。

eval('context.fn(' + args +')')複製代碼

上面代碼中args 會自動調用 args.toString() 方法,由於'context.fn(' + args +')'本質上是字符串拼接,會自動調用toString()方法,以下代碼:

var args = ["a1", "b2", "c3"];
console.log(args);
// ["a1", "b2", "c3"]

console.log(args.toString());
// a1,b2,c3

console.log("" + args);
// a1,b2,c3複製代碼

因此說第二個版本就實現了,代碼以下:

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

// 測試一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1複製代碼

完美!!

模擬實現第三步

還有2個細節須要注意:

  • 一、this 參數能夠傳 null 或者 undefined,此時 this 指向 window
  • 二、this 參數能夠傳基本類型數據,原生的 call 會自動用 Object() 轉換
  • 三、函數是能夠有返回值的

實現上面的三點很簡單,代碼以下

// 第三版
Function.prototype.call2 = function (context) {
    context = context ? Object(context) : window; // 實現細節 1 和 2
    context.fn = this;

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

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

    delete context.fn
    return result; // 實現細節 3
}

// 測試一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

function foo() {
    console.log(this);
}

bar.call2(null); // 2
foo.call2(123); // Number {123, fn: ƒ}

bar.call2(obj, 'kevin', 18); //1
複製代碼

完美!!!

call和apply模擬實現彙總

call的模擬實現

ES3:

Function.prototype.call = function (context) {
    context = context ? Object(context) : window; 
    context.fn = this;

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

    delete context.fn
    return result;
}複製代碼

ES6:

Function.prototype.call = function (context) {
  context = context ? Object(context) : window; 
  context.fn = this;

  let args = [...arguments].slice(1);
  let result = context.fn(...args);

  delete context.fn
  return result;
}複製代碼

apply的模擬實現

ES3:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(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.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')');
    }

    delete context.fn
    return result;
}複製代碼

ES6:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}複製代碼
相關文章
相關標籤/搜索