JavaScript bind 遇到 new:接收者已经固定,为什么字段却写进了新实例
先把Ticket绑定到一个已有对象receiver,再通过new调用绑定结果,this上的字段却没有写进receiver,而是出现在新实例里。若把bind理解成“无论怎么调用都只用这个对象”,工厂函数或测试替身就容易在这里出现状态归属错误。
普通调用绑定函数时,保存的接收者会参与调用;可构造的绑定函数经new调用时,走的是构造路径,预先绑定的this不被采用。预设参数仍会放在后续实参前面,因此同一次bind保存的接收者与参数,在两种调用方式中并不是完全一起生效。
把下面程序保存为demo.mjs,运行node demo.mjs。已在Node.js v24.19.0实跑,只创建内存对象。Ticket没有显式返回另一个对象,便于直接观察常规实例创建;同一BoundTicket会先普通调用一次,再构造一次。
AI模型生成的概念插图:模具产出新的票形块,旁边原有票形块仍留在自己的托盘,提示构造时的新实例与已有接收者分开;不是内存截图。
完整可运行程序
import assert from 'node:assert/strict';
function Ticket(prefix, number) {
this.code = `${prefix}-${number}`;
this.targetIsTicket = new.target === Ticket;
}
const receiver = {code: 'original'};
const BoundTicket = Ticket.bind(receiver, 'R');
BoundTicket(1);
assert.equal(receiver.code, 'R-1');
assert.equal(receiver.targetIsTicket, false);
console.log('ordinary call:', receiver.code, receiver.targetIsTicket);
const created = new BoundTicket(2);
assert.equal(created.code, 'R-2');
assert.equal(created.targetIsTicket, true);
assert.equal(receiver.code, 'R-1');
assert.notEqual(created, receiver);
assert.equal(Object.getPrototypeOf(created), Ticket.prototype);
assert.equal(created instanceof Ticket, true);
assert.equal(created instanceof BoundTicket, true);
console.log('constructed:', created.code, created.targetIsTicket);
console.log('saved receiver:', receiver.code);
console.log('instance checks:', created instanceof Ticket,
created instanceof BoundTicket);
const boundArrow = (() => 1).bind(null);
assert.throws(() => new boundArrow(), TypeError);
console.log('bound arrow remains non-constructible');本次实际输出
ordinary call: R-1 false constructed: R-2 true saved receiver: R-1 instance checks: true true bound arrow remains non-constructible
普通调用先证明绑定确实成立
ordinary call输出R-1和false。预先绑定的R进入prefix,调用时提供的一进入number;this就是receiver,所以它的code变为R-1。此时没有使用new,new.target不是Ticket,记录下来的比较结果为false。
如果只测试这一行,确实容易以为这个包装函数以后永远修改receiver。但接下来的new BoundTicket(2)改变了调用方式,不能继续沿用普通调用时的接收者推断。排查时要查看最终入口,而不只查看bind出现的那一行。
构造调用保住参数,创建新对象
constructed输出R-2和true,saved receiver仍为R-1。断言确认created与receiver不是同一个对象,说明二被用于新实例的编号,之前保存的receiver没有被这次构造改成R-2。R依然保留,证明预设参数没有随绑定this一起被忽略。
本例直接以BoundTicket作为new目标。绑定函数的构造规则把这个new目标转回原始Ticket,因此Ticket内部看到new.target === Ticket为true。这个观察限定在示例的直接构造方式;不要把复杂的Reflect.construct或继承情形也简化为永远相同。
原型与instanceof也要跟着构造路径看
created的原型是Ticket.prototype,两个instanceof检查都为true。对普通绑定函数,instanceof会沿绑定目标处理,不能因为变量名是BoundTicket,就认定实例必须来自另一份独立的类型定义。绑定包装和新建一个类不是同一种操作。
最后把箭头函数绑定后尝试new,仍得到TypeError。bind只在原目标具备可构造能力时提供相应构造路径,不会把任何可调用函数都变成构造函数。这条测试可以拦住“为了支持new,先给函数bind一下”的错误修补。
实际接口最好明确接收普通回调还是构造器。若只需要固定参数的对象工厂,可以暴露一个清楚的创建函数;若必须交给要求构造器的框架,就要分别验证新实例字段、原型和外部对象是否被意外修改。
本例用相同前缀、不同编号对照两次调用,避免把结果差异归因于传错参数。验收时保留普通调用与new调用两条路径,再加一个不可构造函数样本,就能把接收者规则、参数预填和可构造性三个问题分开定位。
官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,全部断言通过,退出状态为零。


