Javascript模擬實現call函式
js自帶call的用法:fn.call(obj,param1,param2,...);
將fn中的this指向obj,並已逗號隔開形式傳入引數
var myobj={name:'peachestao'};
function fn(param){
console.log(this.name);
console.log(param);
}
fn.call(myobj,'hello');
輸出結果:peachestao
hello
其實我們我們自定義函式來模擬實現js自帶的call方法,有兩種方法實現
無論通過何種方法模擬,都需要實現以下幾點:
1、替換原函式中的this為目標物件
2、將引數傳遞給原函式
3、相容null作為目標物件,自動轉換為window物件
方法一:使用eval 將原始函式轉換為字串,再將其中的this替換為目標物件
Function.prototype.myCall=function(context){ var str; var reg=new RegExp('this','g'); str=this.toString().replace(reg,'context');//A、replace('this','context')只會替換第一處,正則表示式會替換所有 var newArguments = [];//B、實現引數傳入,將當前函式中的引數除去第一個將剩餘的傳入目標函式 for(var i = 1, len = arguments.length; i < len; i++) { newArguments.push('arguments[' + i + ']'); } //var newArguments=[].slice.call(arguments,1);//不能用這種方法,否則eval執行時會提示引數變數沒有定義,需要跟B處一樣,將每個引數名轉換為字串形式,eval執行時動態計算每一個引數值 eval('('+str+')('+newArguments+')'); //C、以js表示式的形式執行字串 }
呼叫:fn.myCall(myobj,'hello');
輸出結果:peachestao
hello
關於程式碼中C處實現的原理我這裡解釋以下:
eval函式:將字串以js表示式的形式執行,如下:
function TestEvalFn(){
console.log('this is a test method');
}
eval('TestEvalFn()');
函式TestEvalFn將被執行,輸出:
this is a test method
再次回到程式碼C處:我們可以將eval中的引數打印出來,大家可以可得更加直白些:
"(function fn(param){
console.log(context.name);
console.log(param);
})(arguments[1])"
可以看出,定義了一個立即執行的函式,函式中的this已變更為context,也傳入了引數,字串形式的context和arguments執行時會動態繫結變數
方法二、將fn新增到myobj的屬性,然後myobj.fn()的形式實現
將上面的myobj改造一下:
var myobj={
name:'peachestao',
fn:function(param)
{
console.log(this.name);
console.log(param);
}
};
myobj.fn('this is a param');
這種形式fn函式中的this已經替換為myobj了。我們可以參照這種模式,實現的程式碼如下:
Function.prototype.myCall2=function(context){
context=context||window; //相容傳入null的情況
context.fn=this;//將原函式新增到目標物件屬性上
var newArguments = [];
for(var i = 1, len = arguments.length; i < len; i++) {
newArguments.push('arguments[' + i + ']');
}
eval('context.fn('+newArguments+')'); //執行屬性方法
delete context.fn; //去除屬性
}
呼叫;
fn.myCall2(myobj,'hello');
輸出結果:peachestao
hello
類似於call函式,apply函式也可以自己模擬實現。
目的不是重複造輪子,而是為了深入瞭解原生函式實現的原理,寫程式碼時就會心中有數
如果覺得這篇文章對你有幫助,請點個贊 *^_^*