1. 程式人生 > >Javascript模擬實現call函式

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函式也可以自己模擬實現。

目的不是重複造輪子,而是為了深入瞭解原生函式實現的原理,寫程式碼時就會心中有數

如果覺得這篇文章對你有幫助,請點個贊 *^_^*