前言
要想實現它,就必須先了解它是做什麼的,主要功能是什麼
MDN:Function.prototype.call() 文檔
一句話介紹call:call() 方法在使用一個指定的 this 值和若干個指定的參數值的前提下調用某個函數或方法。
舉個例子:
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
從以上代碼發現:
1. call改變了this的指向,指向到了foo
2. bar函數執行了
模擬實現第一步
我們把bar方法作爲foo對象的屬性來調用,這樣this不就指向了foo嗎? 如下:
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
但是這樣我們就改變了foo對象的屬性, 所以我們最後還是需要delete來刪除它。
所以我們的步驟可以分爲:
1. 將函數設爲對象的屬性
2. 執行該函數
3. 刪除該函數
根據這個思路我們就可以寫出第一版的call2函數:
// 第一版
Function.prototype.call2 = function(context) {
// 首先要獲取調用call的函數,用this可以獲取
context.fn = this;
context.fn();
delete context.fn;
}
測試一下
// 測試一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
模擬實現第二步
call函數還能給定參數執行函數,這裏需要注意一下,給定的參數是不確定的,我們需要怎麼取呢?
我們可以從Arguments對象中取值,取出第二個到最後一個參數,類似這樣
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
// 此時args是類似這樣的["arguments[ 1]", "arguments[ 2]"]
但是我們需要怎麼將參數傳入執行的函數裏面呢???
這裏我們使用eval方法,類似這樣
eval('context.fn(' + args +')')
eval方法:傳入一個字符串,會將該字符串解析爲js代碼來執行。
這樣我們就有了第二版的代碼
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);
模擬實現第三步
寫到這裏,我們已經完成了80%,現在我們還有2個小細節沒有完成:
1. this 參數可以傳 null,當爲 null 的時候,視爲指向 window
2. 函數是可以有返回值的
Function.prototype.call2 = function (context) {
var context = 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;
}
這樣我們就完成了call函數的模擬實現,相信你看到這裏,已經可以自己實現了call函數,如果有什麼不懂的可以私信我或者評論區留言,學無止境,大家加油!!