幾點基礎知識:
- Function.prototype.call 和 Function.prototype.apply 方法用於改變內部函數 this 的指向
func.apply(thisArg[, argsArray])
接受兩個參數,第一個參數指定了 func 函數運行時的this,第二個參數是一個數組或者類數組對象func.call(thisArg, arg1, arg2, ...)
方法的作用和 apply() 方法類似,只有一個區別,就是 call()方法接受的是若干個參數的列表,而apply()方法接受的是一個包含多個參數的數組
舉個栗子:
var hero = {
name:'奧特曼'
};
function beatMonster() {
console.log(this.name);
}
// call把this由window指向hero對象,並執行了beatMonster方法
beatMonster.call(hero); // 輸出:奧特曼
模擬實現call
思路:
1. 將函數設置爲對象的屬性
2. 執行該函數
3. 刪除對象中的該函數
Function.prototype.myCall = function(context) {
// context即爲hero
// this爲被調用的方法 因爲myCall在beatMonster的上下文中執行,所以this指向beatMonster
context.fn = this;
context.fn();
delete context.fn;
}
beatMonster.myCall(hero); // 輸出:奧特曼 沒毛病(・。・)
// 接下來考慮有參數的情況
function beatMonster(weapon, level) {
console.log(this.name);
console.log(weapon);
console.log(level);
}
beatMonster.call(hero,'動感光波',10); // 輸出:奧特曼 動感光波 10
// 因爲參數的實際情況不明,所以使用函數內部的arguments(類數組對象)來獲取實參,先憑本能搞一波
Function.prototype.myCall = function(context) {
// 借用 Array.prototype的方法拿到實參數組
var args = [].slice.apply(arguments,[1]);
context.fn = this;
context.fn.apply(context, args);
//這裏可以用es6的...操作符 context.fn(...args);但call是es3的方法
delete context.fn;
}
beatMonster.myCall(hero,'巴拉拉能量',8); // 輸出:奧特曼 巴拉拉能量 8
// 以上的實現從結果來看沒毛病,但是感覺怪怪的,模擬call的時候用他兄弟apply,那還模擬個啥
// 改下改下 用個eval方法
Function.prototype.myCall = function(context) {
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args[i-1] = "arguments["+i+"]";
}
context.fn = this;
// 這裏 args 會自動調用 Array.toString() 這個方法
eval('context.fn('+ args +')');
delete context.fn;
}
看起來好像成功了 (´ω`★)
然而 還有一種情況 beatMonster.myCall(null);
報錯 Cannot set property 'fn' of null
再加上兩點必須注意:
- 凡是方法中使用到上下文語境的,一定要考慮默認的上下文(this)
- 凡是編碼對象是function的,一定要考慮返回值
於是代碼改爲:
Function.prototype.myCall = function(context) {
context = context || window;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args[i-1] = "arguments["+i+"]";
}
context.fn = this;
// 這裏 args 會自動調用 Array.toString() 這個方法
var result = eval('context.fn('+ args +')');
delete context.fn;
return result;
}
(๑•̀ㅂ•́)و✧
模擬實現apply
直接上代碼:
Function.prototype.myApply = function(context, arr) {
context = 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[i] = "arr["+i+"]";
}
result = eval('context.fn('+ args +')');
}
delete context.fn;
return result;
}