一道call()
函數的面試題
call()
函數能改變this指針的指向,能方便的爲對象擴展方法,在實際項目中屬於重度應用的方法。
下面來看題目:
function fn1() {console.log(1);}
function fn2() {console.log(2);}
fn1.call(fn2);
fn1.call.call(fn2);
fn1.call.call.call.call(fn2);
fn2.call();
Function.prototype.call(fn2);
Function.prototype.call.call.call(fn2);
Function.prototype.call.call.call(fn1);
//Array.prototype.call([1,2,3]); // 所以這樣調用會報 TypeError 錯誤,Array.prototype.call is not a function
-
fn1.call(fn2);
這樣的調用方式大家應該比較熟悉。call()
函數的第一個參數應該是對象,fn2
是函數,在Js中函數的本質也是對象;所以就是在fn2對象上調用fn1方法(注意fn2上本來是沒有fn1這個方法的,調用call時會給fn2臨時添加一個屬性,它的值就是fn1方法的地址),等同的效果就是直接執行fn1()
; -
fn2.call();
call()
可以不傳參,這時候默認的就是全局Global對象,web環境中全局Global對象就是window
。這裏假定是web環境,所以相當於fn1.call(window);
fn1.call(window);
這個比較簡單啦,這就是call的普通用法,等價於window.fn1();
,實際上就是調用fn1()函數。
-
Function.prototype.call(fn2);
首先,Function的原型比較特殊,它的原型是匿名空函數,別的類型的原型對象都是對象。
所以這行代碼表示在fn2上調用匿名空函數,所以就不會輸出內容。 -
Function.prototype.call.call.call(fn2);
,實際上和fn1.call.call(fn2)
類似
- 可以拆分一下,這樣容易說明問題
(Function.prototype.call.call).call(fn2);
, 我們把Function.prototype.call.call
給擴了起來。 - 來看
Function.prototype.call.call
,可以理解爲Function.prototype.call
的call方法;Function.prototype.call
本身也是一個函數,如果不重寫實際上所有函數的call方法都是Function
原型中的call
方法, 即Function.prototype.call
。
也可以說Function.prototype.call
和Function.prototype.call.call
、Function.prototype.call.call.call...
都是等價的,即Function原型上的call方法。 - 這樣
(Function.prototype.call.call).call(fn2);
就相當於在fn2對象(js中函數也是對象)上調用原型中的call
方法,且沒有傳遞參數;實際上就是fn2.call();
。
4)fn2.call();
我們觀察下call函數中沒傳值,默認就是window對象,fn2.call(window);
又等價於window.fn2();
。
模擬call()
函數實現
call()
的內部原理是怎樣的?我們來模擬一下call函數:
function call(context = window, ...args) {
// this -> 指向調用call的那個函數(記住call前面必須是函數在調用)
// 實際上就是執行 this(...args);
// 是哪個對象要執行這個this函數?答案是context
// 所以先把這個函數賦值給context的一個屬性
// 然後調用context這個新屬性,接收傳入的參數
context.$fn = this;
// 模擬call要處理的問題
let res = context.$fn(...args);
delete context.$fn; // 移除我們私自添加的函數 $fn
return res;
}