手动实现call、apply、bind方法

215 阅读2分钟

call

函数通过call调用时,函数体内的this指向第一个参数,后面可以传多个参数来传入原函数argument中。

function setDetails(name, color) {
    this.name = name;
    this.color = color;
}
Function.prototype.call2 = function (context) {
    // 定义一个唯一的属性名
    function mySymbol(obj) {
        let unique = (Math.random() + new Date());
        // 如果冲突就递归调用
        return obj.hasOwnProperty(unique) ? mySymbol(obj) : unique;
    }
    let uniqueName = mySymbol(context);

    // 参数列表
    let args = [];
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments['+ i +']');
    }

    context[uniqueName] = this;
   let result =  eval('context[uniqueName](' + args.join(',') + ')');
   // 删除临时方法
   delete context[uniqueName];
   return result;
}
let cat1 = {};
setDetails.call2(cat1, '大毛', '橘色');
// {name: "大毛", color: "橘色"}

apply

call与apply不同之处在于call接收的是一个参数列表,而apply只接收两个实参,第一个实参是指向的this,第二个是参数数组。

使用ES6方法改写:

function setDetails(name, color) {
    this.name = name;
    this.color = color;
}
Function.prototype.apply2= function (context, args) {
    // 定义一个唯一的属性名
    let uniqueName = Symbol(context);
    context[uniqueName] = this;
    let result = context[uniqueName](...args);
   // 删除临时方法
   delete context[uniqueName];
   return result;
}
let cat1 = {};
setDetails.apply2(cat1, ['大毛', '橘色',5]);

bind

bind与call和apply的功能相似,但有不同,bind不会立即调用函数,只做this绑定,并且返回一个新的函数,这个函数运行的逻辑与原函数一致,但是this会指向之前绑定的对象。

原生用法:

function setDetails(name, color) {
    this.name = name;
    this.color = color;
}

let cat1 = {};
let res = setDetails.bind(cat1);
res('大毛', '橘色');
// bind函数可以在立即调用的时候传入一个参数,然后再去调用返回的函数再去传另一个
let res = setDetails.bind(cat1, '大毛');
res('橘色');

自己实现:

function setDetails(name, color) {
    this.name = name;
    this.color = color;
}

Function.prototype.bind2 = function (context) {
    let originFn = this;
    let args = Array.from(arguments).slice(1);
    return function () {
        let bindArgs = Array.from(arguments);
        return originFn.apply(context, args.concat(bindArgs));
    }
}
let cat1 = {};
let res = setDetails.bind2(cat1);
res('大毛', '橘色');

但是如果使用构造函数的方式去调用bind返回的函数,那么返回函数的this就会指向实例对象。因此只需要判断 this instanceof 返回的函数,另外,返回的函数要继承原函数(将原型链连接起来)

function setDetails(name, color) {
    this.name = name;
    this.color = color;
}

Function.prototype.bind2 = function (context) {
    let originFn = this;
    let args = Array.from(arguments).slice(1);
    function fBind() {
        let bindArgs = Array.from(arguments);
        // 判断是否作为构造函数调用
        return originFn.apply(this instanceof fBind ? this : context , args.concat(bindArgs));
    }
    // 将fBind原型链连接起来
    // fBind.prototype.__proto__ = this.prototype;
    fBind.prototype = Object.create(this.prototype);
    return fBind;
}
let cat1 = {};
let Res = setDetails.bind2(cat1);
let res2 = new Res('大毛', '橘色');
// fBind {name: "大毛", color: "橘色"}

网上的资料深浅不一,本人也是处在学习的过程中的总结,如果发现错误,欢迎留言指出~

扫码加入前端群,分享技术难题~