如何实现 JavaScript 的 bind 方法?
bind 是 JavaScript 中显式绑定 this 的三种方式之一,与 call、apply 不同,bind 不会立即执行函数,而是返回一个绑定了 this 和预设参数的新函数。面试中考察 bind 的实现,重点在于:this 绑定、参数柯里化、new 调用兼容、原型链维护。
最简实现:绑定 this + 预设参数
bind 的核心行为只有两件事:绑定 this 上下文,预设部分参数。
javascriptFunction.prototype.myBind = function(context, ...args) { const fn = this; return function(...newArgs) { return fn.apply(context, [...args, ...newArgs]); }; };
这段代码保存了原函数引用 fn,返回的新函数在调用时将预设参数 args 和新传入参数 newArgs 合并,通过 apply 将 this 绑定到 context 执行。
验证:
javascriptconst obj = { name: 'Alice' }; function greet(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; } const bound = greet.myBind(obj, 'Hello'); console.log(bound('!')); // "Hello, Alice!"
兼容 new 调用
上面的实现在 new bound() 时会出错——new 创建的实例本应作为 this,但被 bind 的 context 覆盖了。bind 的规范要求:new 的优先级高于 bind 绑定的 this。
javascriptFunction.prototype.myBind = function(context, ...args) { const fn = this; return function bound(...newArgs) { // new 调用时 this instanceof bound 为 true,应使用 new 创建的实例 return fn.apply( this instanceof bound ? this : context, [...args, ...newArgs] ); }; };
this instanceof bound 是判断是否通过 new 调用的关键:当 new bound() 执行时,this 指向新创建的实例对象,该实例的原型链上有 bound.prototype,因此 instanceof 返回 true。
验证:
javascriptfunction Person(name) { this.name = name; } const BoundPerson = Person.myBind({ name: 'ignored' }); const p = new BoundPerson('Bob'); console.log(p.name); // "Bob",而非 "ignored"
维护原型链
上面的实现仍有一个问题:通过 new bound() 创建的实例无法访问原函数原型上的属性和方法。
javascriptPerson.prototype.sayHi = function() { return `Hi, I'm ${this.name}`; }; const p2 = new BoundPerson('Carol'); console.log(p2.sayHi()); // TypeError: p2.sayHi is not a function
原因是 bound 函数的 prototype 并没有指向 fn.prototype。需要用一个空对象作为桥接:
javascriptFunction.prototype.myBind = function(context, ...args) { if (typeof this !== 'function') { throw new TypeError('Bind must be called on a function'); } const fn = this; const fNOP = function() {}; const bound = function(...newArgs) { return fn.apply( this instanceof bound ? this : context, [...args, ...newArgs] ); }; // 维护原型链:让 bound 的实例能访问原函数的原型 fNOP.prototype = fn.prototype; bound.prototype = new fNOP(); return bound; };
为什么用 fNOP 做中介而不是直接 bound.prototype = fn.prototype?因为直接赋值后,修改 bound.prototype 会同步影响 fn.prototype,这不符合预期。中介对象隔断了这种引用关系。
验证:
javascriptconst p3 = new BoundPerson('Dave'); console.log(p3.sayHi()); // "Hi, I'm Dave" console.log(p3 instanceof Person); // true
边界情况与细节
this 不是函数时抛出错误
原生 bind 在非函数值上调用会抛出 TypeError,实现中应当对齐这一行为,已在上面的完整实现中包含。
箭头函数的 bind 无效
箭头函数没有自己的 this,其 this 由外层词法作用域决定。对箭头函数调用 bind 虽然不会报错,但绑定的 this 不会生效。
javascriptconst arrow = () => this; const boundArrow = arrow.myBind({ x: 1 }); console.log(boundArrow()); // 仍是外层 this,而非 { x: 1 }
bind 返回的函数没有 prototype 属性
原生 bind 返回的绑定函数 prototype 为 undefined,不能作为构造函数使用(虽然通过 new 仍可调用,但原型链由内部 [[Constructor]] 处理)。这一点在深度追问时需要注意。
多次 bind 不会叠加 this
bind 返回的函数内部 this 已固定,再次 bind 无法改变。但预设参数会叠加。
javascriptfunction fn(a, b, c) { return [a, b, c]; } const bound1 = fn.bind({ x: 1 }, 1); const bound2 = bound1.bind({ x: 2 }, 2); console.log(bound2(3)); // [1, 2, 3],this 仍是 { x: 1 }
追问
bind 返回的函数再用 new 调用会发生什么?
new 优先级高于 bind。new 创建的新对象会作为 this,bind 指定的 this 被忽略,但预设参数仍然生效。这是 ES5 规范明确规定的优先级:new > bind > apply/call > 默认绑定。
call、apply、bind 三者区别?
call(thisArg, arg1, arg2, ...):逐个传参,立即执行apply(thisArg, [arg1, arg2]):数组传参,立即执行bind(thisArg, arg1, arg2):预设 this 和参数,返回新函数,不立即执行
三者的核心差异在于执行时机和传参方式。实际开发中 bind 常用于事件回调保留上下文,call/apply 常用于借用方法或类数组转数组。
为什么要用 fNOP 做中介而不是直接赋值 prototype?
直接 bound.prototype = fn.prototype 会导致两者引用同一个对象,后续对 bound.prototype 的修改(如添加属性)会污染原函数的原型。fNOP 中介创建了一个以 fn.prototype 为原型的新对象,既保证 instanceof 正确,又隔离了修改影响。