【2】前端面试手撕-apply bind和call

14 阅读2分钟

1. 手写call函数

call函数用于调整调用函数的this指向,并且会立即执行,参数以剩余参数的形式传递

/**
 * 手写 Function.prototype.call
 *
 * call 的作用:
 * 1. 改变函数执行时的 this 指向
 * 2. 立即执行函数
 * 3. 可以传递多个参数(逐个传递)
 *
 * 实现原理:
 * 1. 将函数设为对象的属性
 * 2. 执行该函数
 * 3. 删除该属性
 */

Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('this is not a function');
    }

    // 1. 如果当前没有传入context则设置为全局对象window
    context = context || window;

    // 2. 如果 context 是基本类型,需要包装成对象
    //    例如: call(1) 应该变成 call(Number(1))
    if (typeof context !== 'object') {
        context = Object(context);
    }

    // 使用Symbol创建唯一属性名,避免属性命名冲突
    const fn = Symbol();

    // 让这个函数成为context的一个属性,这样函数执行的时候this就指向了context
    context[fn] = this;

    // 通过属性调用函数,此时函数内部的this就指向了context
    const result = context[fn](...args);

    // 删除临时属性,避免污染对象
    delete context[fn];

    return result;
};

2. 手写apply函数

作用基本和call函数一样,区别在于参数传递以数组的形式传递

/**
 * 手写 Function.prototype.apply
 *
 * apply 与 call 的区别:
 * - call: 参数逐个传递 fn.call(obj, arg1, arg2, arg3)
 * - apply: 参数以数组形式传递 fn.apply(obj, [arg1, arg2, arg3])
 *
 * 实现原理与 call 相同,只是参数处理方式不同
 */

Function.prototype.myApply = function (context, args = []) {
    if (typeof this !== 'function') {
        throw new TypeError('this is not a function');
    }
    context = context || window;

    if (typeof context !== 'object') {
        context = Object(context);
    }

    const fn = Symbol();

    context[fn] = this;

    const result = context[fn](...args);

    delete context[fn];

    return result;
};

3. 手写bind函数

bind函数也会修改this指向,但是并不会立即执行,可以分步传递参数,并且返回的函数还可以当作构造函数使用,但是在这种情况下,bind绑定的this就会失效,指向创建的实例对象

/**
 * 手写 Function.prototype.bind
 *
 * bind 的特点:
 * 1. 返回一个新函数,不会立即执行
 * 2. 可以分步传递参数 (柯里化)
 * 3. 返回的函数可以作为构造函数使用 (new)
 * 4. 作为构造函数时,bind 绑定的 this 会失效
 *
 * 实现要点:
 * 1. 返回新函数
 * 2. 处理参数合并
 * 3. 处理 new 调用的情况
 */

Function.prototype.myBind = function (context, ...args) {
    // 保存原函数
    const fn = this;

    if (typeof fn !== 'function') {
        throw new TypeError('Bind must be called on a function');
    }

    const boundFunction = function (...innerArgs) {
        // 判断是否是new调用
        // 如果是new调用,则this指向新创建的对象(实例对象)
        // 如果不是new调用,则this指向绑定的context
        const isNew = this instanceof boundFunction;
        const allArgs = [...args, ...innerArgs];

        return fn.apply(isNew ? this : context, allArgs);
    };

    // 维护原型链
    // 使 boundFunction.prototype 指向原函数的 prototype
    // 这样通过 new boundFunction 创建的实例可以访问原函数原型上的方法
    if (fn.prototype) {
        // 使用 Object.create 避免直接修改原函数的 prototype
        boundFunction.prototype = Object.create(fn.prototype);
    }

    // 返回一个新函数
    return boundFunction;
};