call、bind & apply的代码实现

202 阅读3分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

call

call 方法的作用是改变函数内部的this指向

传入参数分两部分,第一个参数是需要指向的this,后面的参数是实参列表 arg1,arg2,arg3...

我们要实现call方法的话,需要一步一步来处理

首先,我们先来看一下call是怎么用的

// 我们有一个函数
function getName() {
    return this.name;
}
// 还有一个对象
const obj = {
    name: "张三",
};
// 最终输出
getName.call(obj); 
//张三

这个方法要怎么来实现呢?写代码和吃饭一样不能贪心,我们先来实现this指向的变更

我们可以为call增加一个this的方法

// 直接使用symbal方式了,原理和正常对象一样
Function.prototype.call = function (context) {
    const symbol = Symbol("fn");
    context[symbol] = this;
    context[symbol]();
    delete context[symbol];
};

再来实现参数的传递

Function.prototype.call = function () {
    const [ctx, args] = arguments;
    const symbol = Symbol("fn");
    ctx[symbol] = this || window;
    const result = ctx[symbol](args);
    delete ctx[symbol];
    return result;
};

apply

apply的实现和call基本一致,唯一的区别就是参数传入的区分,我们借助上面的call来直接实现

Function.prototype.apply = function () {
    const [ctx, args] = arguments;
    const symbol = Symbol();
    ctx[symbol] = this || window;
    const result = ctx[symbol](args);
    delete ctx[symbol];
    return result;
};

bind

bind和上面两个不太一样,但是可以借用apply来实现,我们先来分析bind的特性,并对应进行代码实现输出

bind会返回一个新函数,新函数执行时this是bind方法的第一个参数

Function.prototype.bind = function () {
    const [ctx, ...args] = arguments;
    const self = this;
    return function () {
        self.apply(args);
    };
};

上面的方法就将this绑定的功能实现了,我们要来继续处理这个方法

其实bind也是可以传递参数的,bind在返回的时候可以传递参数,并且调用bind方法也是有返回值的

因此我们需要来对其做以下处理

Function.prototype.bind = function () {
    const [ctx, ...args] = arguments;
    const self = this;
    return function () {
        // arguments调用bind会返回函数,此函数被调用时被传瑞的参数需要一起连接在一起
        // 以下代码来进行处理
        return self.apply(ctx, args.concat(arguments));
    };
};

由于bind返回的函数时可以作为构造函数,作为构造函数的函数,之前绑定的this会被忽略。

我们在下面的处理中需要保证bind返回的函数可以继承道调用函数的原型

因此,我们需要修改bind返回的函数的原型是this的原型

以下进行代码实现

Function.prototype.bind = function () {
    const [ctx, args] = arguments;
    const self = this;
    const newFunc = function () {
        // 此处this会被变更为 调用bind之后的方法的this
        // 即this = func.bind(this);
        return self.apply(
            this instanceof newFunc ? this : (ctx, args.concat(arguments))
        );
    };

    newFunc.prototype = self.prototype;
    return newFunc;
};

接下来我们发现,bind对于函数的原型也不会修改,我们要怎么处理呢?

我们可以使用中间函数来继承,方案如下

Function.prototype.bind = function () {
    if (typeof this !== "function") {
        throw new Error("Must be function to call bind");
    }

    const [ctx, args] = arguments;
    const self = this;
    var centerFunc = function () {};
    const newFunc = function () {
        // 此处this会被变更为 调用bind之后的方法的this
        // 即this = func.bind(this);
        return self.apply(
            this instanceof newFunc ? this : (ctx, args.concat(arguments))
        );
    };

    centerFunc.prototype = self.prototype;
    newFunc.prototype = new centerFunc();
    return newFunc;
};

以上,我们就将bind方方法相对比较完整的实现了一遍。

同时bind的四个特性也梳理出来了

  1. bind会返回一个新函数,新函数执行时的this是bind方法的第一个参数

  2. bind返回的时候也是可以传递参数的,同时调用bind方法是有返回值的

  3. bind可以作为构造函数

  4. bind不会修改函数的原型

你学废了么~~~