call
是 JavaScript 中非常核心的函數方法之一。它能改變函數的執行上下文(也就是 this 的指向),在日常開發和面試中都極其常見。本文將帶你一步步實現一個Function.prototype.call
的自定義版本,真正理解它的底層原理。
? 一、call 方法做了什么?
在 MDN 中,Function.prototype.call()
是這樣定義的:
call()
方法使用一個指定的this
值和若干個指定的參數(參數的列表)來調用一個函數。
通俗來講,它的作用就是:
顯式指定函數的
this
指向;并立即執行該函數;
參數可以依次傳入。
📌 二、原生 call 的底層邏輯拆解
來看一段模擬的執行邏輯:
var obj = {x: 100,fn() {console.log(this.x);}
};
obj.fn(); // 輸出 100
上面 fn()
的 this 指向 obj
,因為是以 obj.fn()
的形式調用。
而 call
就是模擬了類似的過程——把函數掛載到目標對象上執行,從而實現 this 指向綁定。
🧠 三、call 方法內部做了哪些事?
判斷傳入的
context
(目標對象)是否為值類型(如字符串、數字),如果是,就轉為對象(如'abc'
→new String('abc')
);將函數臨時設為
context
的屬性;通過
context.fn()
執行函數,此時this
指向 context;刪除臨時掛載的函數屬性,防止污染;
返回函數的執行結果。
🔧 四、手動實現 call 方法(完整版)
Function.prototype.myCall = function (context = window, ...args) {if (typeof context !== 'object' && typeof context !== 'function') {context = new Object(context); // 將值類型轉換為對象}const fnKey = Symbol(); // 創建唯一屬性名,避免覆蓋已有屬性context[fnKey] = this; // this 是當前函數,即被調用的函數const result = context[fnKey](...args); // 執行函數,綁定 this 并傳參delete context[fnKey]; // 清理屬性,防止污染return result; // 返回函數執行結果
};
🧪 五、測試用例驗證
function f(a, b) {console.log(a + b); // 輸出 3console.log(this.name); // 輸出 1
}const obj = { name: 1 };
f.myCall(obj, 1, 2); // 等價于 f.call(obj, 1, 2)
輸出結果:
3
1
💬 六、值類型綁定說明
function print() {console.log(this);
}print.myCall('abc'); // this → String 對象:new String('abc')
值類型(如字符串、數字)在傳入時會被自動裝箱為對象類型。
🚧 七、邊界情況優化建議
如果
context
是null
或undefined
,原生call
會將this
指向全局對象(非嚴格模式),或undefined
(嚴格模式);也可以通過判斷加以處理:
context = context ?? window;
🎯 八、面試高頻拓展:call vs apply vs bind
方法名 | 作用 | 參數傳入方式 | 是否立即執行 |
---|---|---|---|
call | 改變 this 并執行函數 | 參數列表 | ? 是 |
apply | 改變 this 并執行函數 | 參數數組 | ? 是 |
bind | 改變 this,返回新函數 | 參數列表 | ? 否(需手動執行) |
📎 九、小結
手動實現 call
的過程并不復雜,但卻是理解 JavaScript 中函數和 this 機制的關鍵一步。建議掌握以下重點:
this 的綁定原理(對象調用 vs 普通調用)
值類型的自動裝箱
如何通過函數掛載 + Symbol 解決屬性污染
面試中可以延伸講解
apply
和bind
的區別
🎁 十、完整源碼
Function.prototype.myCall = function (context = window, ...args) {if (typeof context !== 'object' && typeof context !== 'function') {context = new Object(context);}const fnKey = Symbol();context[fnKey] = this;const result = context[fnKey](...args);delete context[fnKey];return result;
};
如果你覺得這篇文章有幫助,歡迎點贊 👍 收藏 ? 評論 💬 交流~