JavaScript中的Call、Apply、Bind的實現(xiàn)

我們知道在javascript中call和apply以及bind都可以改變this指向,那么它們是怎么實現(xiàn)的呢?彼此之間有什么區(qū)別呢?首先我們先來分別解析一下它們,這篇文章簡單的介紹了實現(xiàn)call() , apply() , bind()的思路

JavaScript中的Call、Apply、Bind的實現(xiàn)

call的解讀與實現(xiàn)

    var leo = {
        name: 'Leo',
        sayHai: function(){
            return "Hi I'm " + this.name;
        },
        add: function(a, b){
            console.log(this)
            return a + b;
        }
    }

    var neil = {
        name:'Neil'
    }

    console.log(leo.sayHai.call(neil)) //Neil
    console.log(leo.sayHai.call(null)) //undefine

如上面輸出結果可以看出:
1.call方法的第一個參數(shù)用于改變this指向,但是如果傳入null/undefined值,this會指向window
2.call方法需要把實參按照形參的個數(shù)傳進去
現(xiàn)在我們已經(jīng)知道如何使用call以及它的規(guī)則,那么我們來封裝一個自己的call方法

    //ES3實現(xiàn)方法利用了eval()函數(shù),將字符串當做JavaScript表達是執(zhí)行
    Function.prototype.es3call = function(ctx) {
        var ctx = ctx || global || window;
        ctx.fn = this;
        var args = [];
        for (var i = 1; i < arguments.length; i++) {
            args.push('arguments[' + i + ']');
        }
        var result = eval('ctx.fn(' + args.join(',') + ')');
        delete ctx.fn;
        return result;
    }
    
    //ES6實現(xiàn)方法利用了擴展運算符rest運算符
    Function.prototype.es6call = function(ctx){
        var ctx = ctx || window || global;
        ctx.fn = this;
        var args = [];
        for(var i = 1; i < arguments.length; i++){
            args.push('arguments[' + i +']');
        }
        const result = ctx.fn(...args);
         return result;
    }

    console.log(leo.sayHai.es3call(neil)) //Neil
    console.log(leo.sayHai.es3call(null)) //undefine
    console.log(leo.sayHai.es6call(neil)) //Neil
    console.log(leo.sayHai.es6call(null)) //undefine

apply的解讀與實現(xiàn)

    console.log(leo.add.apply(neil, [2, 3])) //neil 5
    console.log(leo.add.apply(null, [2, 3])) //window or global 5

1.apply方法的第一個參數(shù)用于改變this指向,但是如果傳入null/undefined值,this會指向window
2.apply方法的第二個參數(shù)需要傳入一個實參列表,也就是arguments

    //ES3實現(xiàn)方法利用了eval()函數(shù),將字符串當做JavaScript表達是執(zhí)行
    Function.prototype.es3apply = function(ctx, arr){
        var ctx = ctx || global || window;
        ctx.fn = this;
        var result = null;
        if(!arr){
            result = ctx.fn();
        }else{
            if(!(arr instanceof Array)) throw new Error('params must Array');
            var args = [];
            for(var i = 0; i < arr.length; i++){
                args.push('arr[' + i +']');
            }
            result = eval('ctx.fn('+ args.join(',') + ')');
          }
        delete ctx.fn;
        return result;
    }

    //ES6實現(xiàn)方法利用了擴展運算符rest運算符
    Function.prototype.es6apply = function(ctx, arr){
        var ctx = ctx || global || window;
        ctx.fn = this;
        var result = null;
        if(!arr){
            result = ctx.fn();
        }else{
            if(!(arr instanceof Array)) throw new Error('params must Array');
            var args = [];
            for(var i = 0; i < arr.length; i++){
                args.push('arr[' + i +']');
            }
            result = ctx.fn(...args);
        }
        delete ctx.fn;
        return result;
    }
    console.log(leo.add.es3apply(neil, [2, 3])) //neil 5
    console.log(leo.add.es3apply(null, [2, 3])) //window or global 5
    console.log(leo.add.es6apply(neil, [2, 3])) //neil 5
    console.log(leo.add.es6apply(null, [2, 3])) //window or global 5

bind的解讀與實現(xiàn)

    var value = 'window';
    var obj = {
        value: 'obj'
    };
    Parent.prototype.value = 'parent';
    function Parent(){};
    Child.prototype = new Parent();
    function Child(){};
    function show(name){
        console.log(this.value, name)
    }

    show();  //window undefined
    var newShow1 = show.bind(obj);
    newShow1();//obj undefined
    var newShow2 = show.bind(null);
    newShow2();//window undefined
    var newShow3 = show.bind(obj, 'test');  //函數(shù)擁有預設的初始參數(shù)
    newShow3();//obj test
    new newShow3();//undefined test
    var oS = Child.bind(obj);
    var fn = new oS();
    console.log(fn, fn.value);    //Child{} "parent"  當使用new 操作符調用綁定函數(shù)時,參數(shù)obj無效。

根據(jù)上面的運行結果,我們可以總結一下bind的用法規(guī)則:
1.bind() 函數(shù)會創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調函數(shù)(綁定函數(shù)的目標函數(shù))具有相同的函數(shù)體
2.bind方法的第一個參數(shù)也是用于改變this指向,如果傳入null/undefined,this會指向window
3.一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當成構造器。提供的 this 值被忽略
4.bind方法可以使函數(shù)擁有預設的初始參數(shù)。這些參數(shù)(如果有的話)作為bind()的第二個參數(shù)跟在this(或其他對象)后面,之后它們會被插入到目標函數(shù)的參數(shù)列表的開始位置,傳遞給綁定函數(shù)的參數(shù)會跟在它們的后面。

    //ES3實現(xiàn)方法
    Function.prototype.es3bind = function(ctx){
        var ctx = ctx || global || window;
        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);
        if(typeof this !== "function") throw new Error('what is trying to be bind is not callback');
        var temp = function(){};
        var fn = function(){
        var fnArgs = Array.prototype.slice.call(arguments, 0);
            return self.apply(this instanceof fn ? this : ctx, args.concat(fnArgs));
        }
        // 先讓 temp 的原型方法指向 _this 即原函數(shù)的原型方法,繼承 _this 的屬性
        temp.prototype = this.prototype;
        // 再將 fn 即要返回的新函數(shù)的原型方法指向 temp 的實例化對象
        // 這樣,既能讓 fn 繼承 _this 的屬性,在修改其原型鏈時,又不會影響到 _this 的原型鏈
        fn.prototype = new temp();     //原型繼承
        return fn;
    }
    
    //ES6實現(xiàn)方法
    Function.prototype.es6bind = function(ctx, ...rest){
        if(typeof this !== "function") throw new Error('what is trying to be bind is not callback');
        var self = this;
        return function F(...args){
            if(this instanceof F){
                return new self(...rest, ...args);
            }
            return self.apply(ctx, rest.concat(args));
        }
    }
    show();  //window undefined
    var newShow1 = show.es3bind(obj);
    newShow1();//obj undefined
    var newShow2 = show.es3bind(null);
    newShow2();//window undefined
    var newShow3 = show.es3bind(obj, 'test');  //函數(shù)擁有預設的初始參數(shù)
    newShow3();//obj test
    new newShow3();//undefined test
    var oS = Child.es3bind(obj);
    var fn = new oS();
    console.log(fn, fn.value);    //Child{} "parent"  當使用new 操作符調用綁定函數(shù)時,參數(shù)obj無效。

    show();  //window undefined
    var newShow1 = show.es6bind(obj);
    newShow1();//obj undefined
    var newShow2 = show.es6bind(null);
    newShow2();//window undefined
    var newShow3 = show.es6bind(obj, 'test');  //函數(shù)擁有預設的初始參數(shù)
    newShow3();//obj test
    new newShow3();//undefined test
    var oS = Child.es6bind(obj);
    var fn = new oS();
    console.log(fn, fn.value);    //Child{} "parent"  當使用new 操作符調用綁定函數(shù)時,參數(shù)obj無效。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 2018年3月14日,73班第三組戰(zhàn)友踐行情況總結如下: 三組戰(zhàn)友共11人 志愿者:李聘聘 1、三組戰(zhàn)友踐行基本情...
    翁翁yeah閱讀 166評論 0 1
  • 再說閉包:首先要理解函數(shù)的作用域(全局和私有),內部的可以訪問全局的,全局的不可以訪問(內容)私有的。函數(shù)執(zhí)行完畢...
    五四青年_4e7d閱讀 250評論 0 0
  • 1、翻云覆雨:玩弄手段,反復無常。 2、求全責備:對人對事要求十全十美。這里的“責備”是“要求完備”的意思,不是批...
    半夏風吹閱讀 334評論 0 2
  • 一輪皎潔出水面, 煙波霧渺誰蕩舟。 燈影湖波醉兩岸, 相悅不知是夜半。 2019年3月4日 大李興業(yè)二十八年正月 ...
    香雪風輕揚閱讀 339評論 0 8

友情鏈接更多精彩內容