实现 bind 方法

基础知识

在实现我们自己的 bind 方法之前,需要先掌握以下几个 JavaScript 核心概念:

  1. this 关键字: 在 JavaScript 中,this 的值取决于函数的调用方式。

    • 在全局作用域中,this 指向全局对象 (windowglobal)。
    • 作为对象方法调用时,this 指向该对象。
    • 使用 callapplybind 调用时,this 指向这些方法指定的第一个参数。
    • 作为构造函数使用 new 调用时,this 指向新创建的实例对象。
    • 在箭头函数中,this 继承自其父级作用域。
  2. Function.prototype.call(): call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    func.call(thisArg, arg1, arg2, ...);
    
  3. Function.prototype.apply(): apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

    func.apply(thisArg, [argsArray]);
    
  4. 闭包 (Closure): 闭包是指有权访问另一个函数作用域中变量的函数。在实现 bind 时,我们需要利用闭包来保存指定的 this 值和预设的参数。

  5. 函数柯里化 (Currying): 这是一种将接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。bind 的实现就运用了柯里化的思想。

核心思路

我们的目标是创建一个 Function.prototype.myBind 方法,它应该能像原生的 bind 方法一样工作。其核心思路如下:

  1. myBind 方法应该挂载在 Function.prototype 上,这样所有的函数实例都可以调用它。
  2. 它应该接受至少一个参数 context,用来指定新函数执行时的 this 指向。context 后面的所有参数将作为预设参数。
  3. myBind 必须返回一个新的函数。这是 bind 的核心功能——它并不立即执行,而是返回一个绑定了上下文的新函数。
  4. 返回的新函数在被调用时,应该使用我们传入的 context 作为其 this 值。我们可以使用 applycall 来实现这一点。
  5. 返回的新函数在被调用时,可以接收额外的参数。这些参数需要和 myBind 时传入的预设参数合并在一起,并传递给原始函数。
  6. 一个关键点是:如果返回的绑定函数被用作构造函数(即通过 new 操作符调用),那么 bind 所指定的 this 值将被忽略,而 this 将指向新创建的实例。

关键点

根据核心思路,我们在实现时需要注意以下几个关键点:

  • 获取调用者: 在 myBind 内部,需要获取到调用 myBind 的原始函数。因为 myBind 是作为函数方法调用的,所以 this 就指向了这个原始函数。
  • 参数合并: 需要将 myBind 时传入的预设参数和返回的新函数被调用时传入的参数合并起来。可以使用 Array.prototype.sliceArray.prototype.concat 来处理参数。
  • 处理 new 操作符: 这是实现一个健壮的 bind 最容易被忽略的一点。当绑定函数被 new 调用时,new 的优先级更高。我们需要判断返回的函数是否被用作构造函数。可以通过 instanceof 来判断。如果被 new 调用,this 应该指向 new 创建的新对象,而不是我们 bind 时指定的 context

代码实现

下面是 myBind 的一个完整实现,包含了对上述所有关键点的处理。

/**
 * 自定义 bind 方法
 *
 * @param {Object} context - 新函数执行时绑定的 this 对象
 * @param {...*} args - 预设的参数列表
 * @returns {Function} - 返回一个绑定了上下文和预设参数的新函数
 */
Function.prototype.myBind = function (context) {
  // 1. 检查调用者是否为函数,如果不是则抛出错误
  if (typeof this !== 'function') {
    throw new TypeError(
      'Function.prototype.bind - what is trying to be bound is not callable'
    );
  }

  // 2. 保存调用 myBind 的原始函数(`this`)和预设参数
  var self = this; // self 指向原始函数
  var args = Array.prototype.slice.call(arguments, 1); // 获取 myBind 时传入的除 context 外的参数

  // 3. 创建一个中间函数 F,用于处理 `new` 操作符
  // 这样做是为了让返回的函数 fBound 的原型链能够连接到原始函数的原型链
  var F = function () {};

  // 4. 返回一个新的函数 fBound
  var fBound = function () {
    // 5. 合并预设参数和调用时传入的新参数
    var bindArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(bindArgs);

    // 6. 判断 fBound 是否被用作构造函数(通过 `new` 调用)
    // 如果是,`this` 应该指向 new 创建的实例 (this instanceof F)
    // 如果不是,`this` 应该指向 myBind 时传入的 context
    return self.apply(this instanceof F ? this : context, finalArgs);
  };

  // 7. 设置原型链,使得 fBound 的实例能够继承原始函数的原型属性
  // F.prototype = this.prototype;
  // fBound.prototype = new F();
  // 更健壮的写法是使用 Object.create,避免直接修改 F.prototype 带来的副作用
  if (this.prototype) {
    F.prototype = this.prototype;
  }
  fBound.prototype = new F();

  return fBound;
};

代码解析

  1. if (typeof this !== 'function'):首先确保调用 myBind 的是一个函数。
  2. var self = this;:在 myBind 内部,this 指向调用它的函数(例如 myFunction.myBind(...) 中的 myFunction)。用 self 变量保存它,以便在返回的函数 fBound 中使用。
  3. var args = Array.prototype.slice.call(arguments, 1);
    • arguments 是包含所有传递给 myBind 参数的类数组对象。
    • 使用 slice 从第二个参数开始(跳过第一个参数 context)提取所有预设参数。
  4. var fBound = function() { ... };
    • 这是返回的核心函数。
    • 闭包使 fBound 能够访问外部作用域的 selfcontextargs
  5. var finalArgs = args.concat(bindArgs);:将 myBind 时预设的 argsfBound 被调用时传入的 bindArgs 合并成最终的参数列表。
  6. this instanceof F ? this : context:处理 new 操作符的关键逻辑:
    • new fBound():当使用 new 调用时,fBound 内部的 this 指向新创建的对象,该对象原型指向 fBound.prototype。由于设置了 fBound.prototype = new F(),且 F.prototype 指向原始函数原型,因此 this instanceof Ftrueapply 的第一个参数为新创建的实例 this
    • boundFunc():作为普通函数调用时,this 默认为全局对象(严格模式下为 undefined),this instanceof Ffalseapply 的第一个参数为 bind 时指定的 context
  7. fBound.prototype = new F();
    • fBound 的原型链与原始函数 self 的原型链连接。
    • 这样,当 new fBound() 创建实例时,实例可通过原型链访问 self.prototype 上的属性和方法。例如,若 Person 是构造函数,boundPerson = Person.bind(...),则 new boundPerson()Person 的实例。
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""