JavaScript 深入之 call 和 apply 的模擬實現

豢生發表於2019-02-16
// 第一版
Function.prototype.call2 = function(context) {
    // 首先要獲取呼叫call的函式,用this可以獲取
    // this的指向為bar,因為bar是Funciton的例項
    context.fn = this;
    context.fn();
    // 相當於把bar掛載到foo上,所以bar可以取到value
    delete context.fn;
}

// 測試一下
var foo = {
    value: 1
};

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

bar.call2(foo); // 1

但是第一版不可以傳遞多個引數

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push(`arguments[` + i + `]`);
    }
    eval(`context.fn(` + args +`)`);
    //這裡 args 會自動呼叫 Array.toString() 這個方法。
    //這裡eval函式輸出的是:context.fn(arguments[1],arguments[2])
    delete context.fn;
}

// 測試一下
var foo = {
    value: 1
};

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

bar.call2(foo, `kevin`, 18);
// kevin
// 18
// 1

第二版的問題是,1.this 引數可以傳 null,當為 null 的時候,視為指向 window2.函式是可以有返回值的!

// 第三版
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;
}

// 測試一下
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
// }

es6 版

Function.prototype.call2 = function (context, ...args) {
    context = context || window
    context.__fn__ = this
    let result = context.__fn__(...args)
    delete context.__fn__
    return result
}

apply 的實現跟 call 類似,在這裡直接給程式碼,程式碼來自於知乎 @鄭航的實現:

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;
}

文章參考來源:https://github.com/mqyqingfen…

相關文章