1. 程式人生 > >call, apply, bind的模擬實現

call, apply, bind的模擬實現

call,apply,bind都是用於改變this的指向的,call和apply的區別在於,他們傳遞的引數不一樣,後者是用陣列傳參,bind和call, apply兩者的區別是,bind返回的是一個函式。
在模擬他們實現之前,我們需要先做些相容處理,如下

Function.prototype.bind = Function.prototype.bind || function(context){
}

call的實現

call的模擬實現的方法是

  1. 把函式掛載在物件上
  2. 然後執行該函式
  3. 刪除該函式
Function.prototype.call
= Function.prototype.call || function(context){ // 當call的第一個引數是null的時候,this的預設指向是window var context = context || window; // 把該函式掛載到物件上 context.fn = this; // 用於儲存call後面的引數 var args = []; var len = arguments.length; // 這裡是為了將一個函式的引數傳入到另外一個函式執行 for(var i = 1; i < len;
i++){ args.push('arguments[' + i + ']'); } // 這裡的args預設是會呼叫Array.toString()方法的 var result = eval('context.fn(' + args + ')'); // 刪除掛載在物件裡的該函式 delete context.fn; // 因為函式可能有放回值,所以把結果也返回出去給他們 return result; } var data = { name: 'mr3x' } function Person(age){ console.
log(this.name, age); return { name: this.name, age: age, }; } Person.call(data, 21); //mr3x 21

apply的實現

apply的模擬實現和call的模擬實現大同小異

Function.prototype.apply = Function.prototype.apply || function(context, arr){
    context.fn = this;
    var result;
    var args = [];
    var arr = arr || [];
    var len = arr.length;
    if(!Array.isArray(arr)) throw new Error('apply的第二個引數必須是陣列')
    for(var i = 0; i < len; i++){
        args.push('arr[' + i +']');
    }
    result = eval('context.fn('+ args + ')');
    delete context.fn;
    return result;
}

var data = {
    name: 'mr3x'
}

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

Person.apply(data, [21]);  //mr3x 21

bind的實現

bind的實現有兩個難點,第一個難點是bind的引數問題,因為bind返回的是一個函式(也叫繫結函式),它不僅可以在bind裡面帶引數,還可以在繫結函式裡面帶引數,如下

var data = {
    name: 'mr3x'
}
function Person(name, age){
    console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
bindPerson(21);  //mr3x bill 21

第二個難點是返回的繫結函式中可以使用new來建立物件(本質是把原函式當成是建構函式),但是這使後的修改的this的指向就失效了,因為用new來建立物件,本質是呼叫了apply,apply也是會修改this的指向的,所以之前bind繫結的就失效了,如下

var data = {
    name: 'mr3x'
}
function Person(name, age){
    console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
var man = new bindPerson(21);   //undefined bill 21

程式碼實現

Function.prototype.bind = Function.prototype.bind || function(context){
    var self = this;
    // 用於解決呼叫的物件非函式
    if(typeof self !== 'function'){
        throw new Error('非函式無法呼叫bind');
    }

    // 外部引數的處理
    var args = Array.prototype.slice.call(arguments, 1);

    // 用於解決引用型別的傳值問題
    var index = function(){}

    var fBound = function(){
        // 用於處理繫結函式裡的引數問題
        var bindArgs = Array.prototype.slice.call(arguments);
        // 當是普通函式的時候,this指向window,
        //但是建構函式的時候,this指向例項,
        return self.apply(this instanceof index ? this : context, args.concat(bindArgs));
    }

    index.prototype = this.prototype;
    fBound.prototype = new index();
    return fBound;
}

上述程式碼有一個問題,它只是勉強算是模擬了bind的實現,有一個缺陷就是,原生的bind所返回的繫結函式是沒有prototype屬性的,最高階版本就去看ES5-shim的原始碼是如何實現bind方法,也可以參考該博文