call和apply的模擬實(shí)現(xiàn)

原文出處

JavaScript深入之call和apply的模擬實(shí)現(xiàn)

call

我們?cè)谀M call之前,先看看 call實(shí)現(xiàn)了哪些功能。

        var foo = {
            value: 1
        };

        function bar() {
            console.log(this.value);
        }

        bar.call(foo); // 1

注意兩點(diǎn):
1.call 改變了 this 的指向,指向到 foo
2.bar 函數(shù)執(zhí)行了

模擬實(shí)現(xiàn)第一步


那么我們?cè)撛趺茨M調(diào)用call時(shí)的這兩個(gè)效果呢?
試想當(dāng)調(diào)用 call 的時(shí)候,把 foo 對(duì)象改造成如下:

        var foo = {
            value: 1,
            bar: function () {  // 把this指向foo
                console.log(this.value)  
            }
        };

        foo.bar(); // 1  //執(zhí)行 bar 函數(shù)

但是這樣卻給 foo 對(duì)象本身添加了一個(gè)屬性,這可不行吶!
不過也不用擔(dān)心,我們用 delete 再刪除它不就好了~
所以我們模擬的步驟可以分為:

  1. 將函數(shù)設(shè)為對(duì)象的屬性
  2. 執(zhí)行該函數(shù)
  3. 刪除該函數(shù)

以上個(gè)例子為例,就是:

        // 第一步,把this指向foo
        foo.fn = bar
        // 第二步,執(zhí)行bar函數(shù)
        foo.fn()
        // 第三步,刪除多余屬性
        delete foo.fn

fn 是對(duì)象的屬性名,反正最后也要?jiǎng)h除它,所以起成什么都無(wú)所謂。
根據(jù)這個(gè)思路,我們可以嘗試著去寫第一版的 call2 函數(shù):

        var foo = {
            value: 1
        };

        function bar() {
            console.log(this.value);
        }

        Function.prototype.call2 = function(context) {
            context.fn = this; // 把this指向foo
            context.fn();  // 執(zhí)行bar函數(shù)
            delete context.fn;
        }

        bar.call2(foo);

模擬實(shí)現(xiàn)第二步


最一開始也講了,call 函數(shù)還能給定參數(shù)執(zhí)行函數(shù)。舉個(gè)例子:

        var foo = {
            value: 1
        };

        function bar(name, age) {
            console.log(name)  // kevin
            console.log(age)  // 18
            console.log(this.value);  // 1
        }

        bar.call(foo, 'kevin', 18);

注意:傳入的參數(shù)并不確定,這可咋辦?
不急,我們可以從 Arguments 對(duì)象中取值,取出第二個(gè)到最后一個(gè)參數(shù),然后放到一個(gè)數(shù)組里。
比如這樣:

        // 因?yàn)閍rguments是類數(shù)組對(duì)象,所以可以用for循環(huán)
        var args = [];
        for (var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }

不定長(zhǎng)的參數(shù)問題解決了,我們接著要把這個(gè)參數(shù)數(shù)組放到要執(zhí)行的函數(shù)的參數(shù)里面去。

        // 將數(shù)組里的元素作為多個(gè)參數(shù)放進(jìn)函數(shù)的形參里
        context.fn(args.join(','))
        // (O_o)??
        // 這個(gè)方法肯定是不行的,因?yàn)?args.join(',')返回一個(gè)字符串參數(shù),

我們可以用 eval方法拼成一個(gè)函數(shù) ,類似于這樣:

eval('context.fn(' + args +')')

這里 args 會(huì)自動(dòng)調(diào)用 Array.toString() 這個(gè)方法

所以我們的第二版克服了兩個(gè)大問題,代碼如下:

        var foo = {
            value: 1
        };

        function bar(name, age) {
            console.log(name);
            console.log(age);
            console.log(this.value);
        }

        Function.prototype.call2 = function(context) {
            context.fn = this; // 把this指向foo
            var args = [];
            for (var index = 1; index < arguments.length; index++) {
                args.push("arguments[" + index + "]");  // 得到初始化參數(shù)
            }
            eval("context.fn(" + args + ")");  // 執(zhí)行bar函數(shù)
            delete context.fn;
        }

        bar.call2(foo, "kobe", 29);

模擬實(shí)現(xiàn)第三步


模擬代碼已經(jīng)完成 80%,還有兩個(gè)小點(diǎn)要注意:
1.this 參數(shù)可以傳 null,當(dāng)為 null 的時(shí)候,視為指向 window
舉個(gè)例子:

        var value = 1;

        function bar() {
            console.log(this.value);  // this指向window
        }

        bar.call(null); // 1

2.函數(shù)是可以有返回值的!

        var obj = {
            value: 1
        }

        function bar(name, age) {
            return {
                value: this.value,
                name: name,
                age: age
            }
        }

        console.log(bar.call(obj, 'kevin', 18));
        // Object {
        //    value: 1,
        //    name: 'kevin',
        //    age: 18
        // }

不過都很好解決,讓我們直接看第三版也就是最后一版的代碼:

        // 第三版
        Function.prototype.call2 = function (context) {
            var context = context || window;
            context.fn = this;

            var args = [];
            for (var i = 1, len = arguments.length; i < len; i++) {
                args.push('arguments[' + i + ']');
            }

            var result = eval('context.fn(' + args + ')');

            delete context.fn
            return result;
        }

        // 測(cè)試一下
        var value = 2;

        var obj = {
            value: 1
        }

        function bar(name, age) {
            console.log(this.value);
            return {
                value: this.value,
                name: name,
                age: age
            }
        }

        bar.call2(null); // 2

        console.log(bar.call2(obj, 'kevin', 18));
        // 1
        // Object {
        //    value: 1,
        //    name: 'kevin',
        //    age: 18
        // }

apply的模擬實(shí)現(xiàn)

apply 的實(shí)現(xiàn)跟 call 類似,在這里直接給代碼,代碼來自于知乎 @鄭航的實(shí)現(xiàn):

        Function.prototype.apply = function (context, arr) {
            var context = Object(context) || window;
            context.fn = this;

            var result;
            if (!arr) {
                result = context.fn();
            } else {
                var args = [];
                for (var i = 0, len = arr.length; i < len; i++) {
                    args.push('arr[' + i + ']');
                }
                result = eval('context.fn(' + args + ')')
            }

            delete context.fn
            return result;
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容