call apply bind 详细解析【手写call apply bind】

55 阅读4分钟

call() - “逐个点名”

  • 语法:func.call(thisArg, arg1, arg2, ...)

  • 作用

    1. 立即执行 func 函数。
    2. 将 func 内部的 this 指向第一个参数 thisArg。
    3. 将后续的参数 (arg1, arg2, ...) 逐个地传递给 func 函数。

apply() - “清单列表”

apply(this,args)的this永远指向“调用”方法的对象本身

  • 语法:func.apply(thisArg, [argsArray])

重写方法

image.png

image.png

  • 作用

    1. 立即执行 func 函数。
    2. 将 func 内部的 this 指向第一个参数 thisArg。
    3. 将一个数组 [argsArray]  中的元素,**展开**后作为参数传递给 func 函数。

    func.apply(thisArg, argsArray) 它的第二个参数 必须是一个数组或类数组对象apply自动把这个数组「拆开」一个个独立参数传给函数。

bind() - “专属遥控器”

  • 语法:func.bind(thisArg, arg1, arg2, ...)

  • 作用

    1. 不会立即执行 func 函数。
    2. 而是返回一个全新的函数
    3. 这个新函数被永久地绑定了 this 为 thisArg,并且可以预先绑定部分参数。

手写call

Function.prototype.myCall = function(thisArg, ...args) {
 // 步骤 1: 处理 thisArg
 // 如果 thisArg 是 null 或 undefined,this 应该指向全局对象 (浏览器中是 window)
 // 使用 Object() 可以将原始类型(如 1, 'abc', true)包装成对象
 const context = (thisArg === null || thisArg === undefined) ? window : Object(thisArg);

 // 步骤 2: 将函数挂载到 context 上
 // this 在这里指向调用 myCall 的函数本身 (例如: sayHello.myCall(...))
 // 使用 Symbol 创建一个唯一的 key,防止覆盖 context 上已有的属性
 const fnSymbol = Symbol('fn');
 context[fnSymbol] = this; //this就是调用Call方法的函数

 // 步骤 3: 执行函数并获取返回值
 // 通过 context[fnSymbol](...args) 调用,此时函数内的 this 就指向了 context
 const result = context[fnSymbol](...args);

 // 步骤 4: 清理,删除临时添加的属性
 delete context[fnSymbol];

 // 步骤 5: 返回函数的执行结果
 return result;
};

函数调用call改变自己this的过程:

1、函数首先调用Call方法

2、自身没有这个方法之后,通过内部原型去自己的原型对象。(也就是构造函数的原型)

3、原型上有这个方法之后。函数则开始执行这个call方法(函数是call方法的执行上下文,原型只是存放这个Call方法的仓库)所以 执行Call方法时,Call方法内部的this是指向调用Call的函数。

call方法的本质就是 让call方法第一个参数拥用了调用call方法的函数,然后让参数调用这个函数。

手写apply

Function.prototype.myApplyStrict = function(thisArg, argsArray) {
    // 步骤 1: 处理 thisArg (与之前版本相同)
    // 如果 thisArg 是 null 或 undefined,this 应该指向全局对象
    // 使用 Object() 可以将原始类型包装成对象
    const context = (thisArg === null || thisArg === undefined) ? window : Object(thisArg);

    // 步骤 2: 将函数挂载到 context 上 (与之前版本相同)
    // this 指向调用 myApplyStrict 的函数本身
    const fnSymbol = Symbol('fn');
    context[fnSymbol] = this;

    let result;

    // ======================================================================
    // 步骤 3: 严格的参数校验和执行 (这是与简化版的关键区别)
    // ======================================================================

    // Case 1: 如果第二个参数为 null 或 undefined,则视为不带参数调用
    // `argsArray == null` 是 `argsArray === null || argsArray === undefined` 的简写
    if (argsArray == null) {
        result = context[fnSymbol]();
    } else {
        // Case 2: 如果第二个参数存在,但不是一个对象 (e.g., a number, string, boolean)
        // 那么就应该抛出 TypeError。注意:数组和类数组也是对象。
        if (typeof argsArray !== 'object') {
            // 这个错误信息是模仿 V8 引擎 (Chrome, Node.js) 的原生错误信息
            throw new TypeError('CreateListFromArrayLike called on non-object');
        }

        // Case 3: 参数是对象 (如数组或类数组)
        // 我们使用展开运算符 (...) 来传递参数。
        // 现代 JS 的展开运算符本身就能很好地处理 "可迭代对象"。
        // 如果传入一个不可迭代的普通对象 (如 { a: 1, b: 2 }),展开操作符也会抛出 TypeError,
        // 这恰好与原生 apply 的行为一致。
        result = context[fnSymbol](...argsArray);
    }

    // 步骤 4: 清理,删除临时添加的属性 (与之前版本相同)
    delete context[fnSymbol];

    // 步骤 5: 返回函数的执行结果 (与之前版本相同)
    return result;
};

手写bind

Function.prototype.myBind = function(thisArg, ...boundArgs) {
    // 获取原始函数
    const self = this;

    // 返回一个新函数
    const fBound = function(...args) {
        // 步骤 1: 合并参数
        // 合并 bind 时传入的预置参数和调用时传入的新参数
        const finalArgs = [...boundArgs, ...args];

        // 步骤 2: 判断是否通过 new 调用
        // 如果是 new 调用,fBound 的实例的 __proto__ 会指向 fBound.prototype
        // 而 fBound.prototype 我们会将其指向 self.prototype
        // 所以 this instanceof fBound 会为 true
        // 此时,this 指向 new 创建的新对象,bind 绑定的 thisArg 无效
        if (this instanceof fBound) {
            // 使用 apply 将参数传递给原始构造函数,this 指向新实例
            return self.apply(this, finalArgs);
        } else {
            // 普通调用,this 指向绑定的 thisArg
            return self.apply(thisArg, finalArgs);
        }
    };

    // 步骤 3: 维护原型链
    // 让返回的函数的 prototype 指向原始函数的 prototype
    // 这样通过 bind 创建的函数 new 出来的实例,才能继承原始构造函数的原型属性
    if (self.prototype) {
        fBound.prototype = Object.create(self.prototype);
    }

    return fBound;
};