Call的模擬實現
call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。
這是MDN上關於call的描述,用通俗的語言解釋一下就是:
- 指定了this值(修改this)
- 可以傳入多個參數
- 它的調用對象時函數
可能這樣解釋還是有些模糊,可以看一個例子:
function Test(name, age) {
console.log(this.name);
this.name = name;
this.age = age;
}
var man = {
name: 'Wei'
};
Test.call(man, 'Willem', 18); // Wei
console.log(man.name); // Willem
console.log(man.age); // 18
在Test函數中輸出this.name之前其實是沒有name屬性的,但是在執行Test.call(man, 'Willem', 18);之後卻輸出了Wei;在man對象中也是沒有age屬性並且name屬性的值為Wei,輸出的值卻是Willem。説明在Test函數執行的過程中this的值指向了man對象,並且可以通過call函數向Test函數傳遞參數。
這一通分析下來無非就是一句話:用於函數的call函數可以指定this並傳入多個參數。
在開始實現call函數之前,可以思考下如何修改this的指向,其實很簡單,直接把目標函數掛載到需要修改的上下文。類似於這種:
var obj = {
name: 'Willem',
sayHi: function() {
console.log(this.name);
}
};
obj.sayHi(); /* Willem */
那代碼如何實現就很清晰了啊。
Function.prototype.wcall = function(context) {
context = context || window; // 不傳入this時,默認是window
context.__fn__ = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('"'+ arguments[i] +'"');
}
var ret = eval('context.__fn__(' + args + ')');
delete context.__fn__;
return ret;
}
上述代碼中,為了執行函數所以採用了eval,當然es6的擴展運算符也可以實現相同的功能。對eval或者js的類型轉換不太熟悉的童鞋可能會對eval那段代碼會有些疑惑。
var args = ['a', 'b', 'c'];
eval('context.__fn__(' + args + ')');
// 上面的代碼實際上是兩句
// 這實際上要説到js的隱式類型轉換了,這裏就不過多贅述了
// 只説一點:'string ' + [1, 2] 字符串和數組使用 +, 會將數組轉換為一個字符串,和join方法的效果差不多
// 所以'string' + [1, 2] === 'string 1,2'
var str = 'context.__fn__(' + args + ')'; // === 'context.__fn__(a,b,c)'
// eval()則是會將傳入的字符串當做JavaScript代碼進行執行
// eval(str) 就相當於是直接執行 context.__fn__(a,b,c)
// 可以發現a, b, c其實只是參數字符串而不是變量
// 所以在最開始我們需要將args的參數都是用字符串進行包裹,wcall代碼中:args.push('"'+ arguments[i] +'"')
// args = ["'a'", "'b'", "'c'"];
// 'context.__fn__(' + args + ')' === 'context.__fn__("a","b","c")'
// 就相當於在直接執行 context.__fn__("a","b","c")
eval(str);
以上就是關於call的實現。
Apply的模擬實現
apply() 方法調用一個具有給定this值的函數,以及作為一個數組(或類似數組對象)提供的參數。
原理啥的就不多説了,apply和call的作用是一樣的,區別是apply是將參數作為一個數組傳入,而call是將參數一個一個的傳入。直接上代碼:
Function.prototype.wapply = function(context, args) {
context = context || window;
context.__fn__ = this;
var ret;
if (!args || !(args instanceof Array) || args.length === 0) {
ret = context.__fn__();
} else {
var arr = [];
for (var i = 0; i < args.length; i++) {
arr.push('"' + args[i] + '"');
}
ret = eval('context.__fn__(' + arr + ')');
}
return ret;
}
以上就是關於apply的實現。
更多:使用ts模擬實現的部分JavaScript函數和屬性