基础版本
基础版本的实现原理请大家参考站内的优秀文章
Function.prototype.bind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
const fn = this;
const fBound = function (this: any, ...args: any[]) {
const context = this instanceof fBound ? this : thisArg;
return fn.apply(context, [...argArray, ...args]);
};
if (fn.prototype) {
fBound.prototype = Object.create(fn.prototype);
}
return fBound;
};
测试用例
根据MDN文档设计测试用例:
import { describe, it, expect } from "vitest";
describe("Function.prototype.bind() 测试", () => {
// 1. 基本 this 绑定测试
describe("1. 基本 this 绑定", () => {
it("1.1 应该正确绑定 this 上下文", () => {
function greet(this: { name: string }) {
return `你好, ${this.name}!`;
}
const person = { name: "张三" };
const boundGreet = greet.bind(person);
expect(boundGreet()).toBe("你好, 张三!");
});
it("1.2 应该能绑定到 null", () => {
function getThis(this: any) {
return this;
}
const boundToNull = getThis.bind(null);
expect(boundToNull()).toBe(null);
});
});
// 2. 参数预设测试
describe("2. 参数预设", () => {
it("2.1 应该能预设参数", () => {
function add(a: number, b: number, c: number) {
return a + b + c;
}
const addWith5 = add.bind(null, 5);
expect(addWith5(3, 2)).toBe(10); // 5 + 3 + 2
const addWith5And3 = add.bind(null, 5, 3);
expect(addWith5And3(2)).toBe(10); // 5 + 3 + 2
});
it("2.2 应该按正确顺序处理预设参数和调用时参数", () => {
function concat(a: string, b: string, c: string) {
return `${a}-${b}-${c}`;
}
const boundConcat = concat.bind(null, "第一");
expect(boundConcat("第二", "第三")).toBe("第一-第二-第三");
});
});
// 3. 多重绑定测试
describe("3. 多重绑定", () => {
it("3.1 应该忽略后续绑定的 this 值", () => {
function getThis(this: any) {
return this;
}
const obj1 = { name: "对象1" };
const obj2 = { name: "对象2" };
const bound1 = getThis.bind(obj1);
const bound2 = bound1.bind(obj2); // 这个 this 绑定会被忽略
expect(bound2()).toBe(obj1); // 仍然是第一次绑定的对象
});
it("3.2 应该累积预设参数", () => {
function multiply(a: number, b: number, c: number) {
return a * b * c;
}
const bound1 = multiply.bind(null, 2);
const bound2 = bound1.bind(null, 3);
expect(bound2(4)).toBe(24); // 2 * 3 * 4
});
});
// 4. 构造函数测试
describe("4. 构造函数行为", () => {
it("4.1 应该能作为构造函数使用", () => {
function Person(this: any, name: string, age: number) {
this.name = name;
this.age = age;
}
const BoundPerson = Person.bind(null, "默认名字");
const person = new (BoundPerson as any)(25);
expect(person.name).toBe("默认名字");
expect(person.age).toBe(25);
expect(person instanceof Person).toBe(true);
});
it("4.2 构造时应该忽略绑定的 this 值", () => {
function Constructor(this: any, value: string) {
this.value = value;
}
const obj = { existing: "属性" };
const BoundConstructor = Constructor.bind(obj);
const instance = new (BoundConstructor as any)("测试值");
expect(instance.value).toBe("测试值");
expect((instance as any).existing).toBeUndefined();
expect((obj as any).value).toBeUndefined();
});
});
// 5. 绑定函数属性测试
describe("5. 绑定函数属性", () => {
it("5.1 应该有正确的 length 属性", () => {
function testFunc(a: any, b: any, c: any) {}
expect(testFunc.length).toBe(3);
const bound1 = testFunc.bind(null);
expect(bound1.length).toBe(3);
const bound2 = testFunc.bind(null, 1);
expect(bound2.length).toBe(2);
const bound3 = testFunc.bind(null, 1, 2, 3);
expect(bound3.length).toBe(0); // 最小值为 0
});
it("5.2 应该有正确的 name 属性", () => {
function originalFunction() {}
const boundFunction = originalFunction.bind(null);
expect(boundFunction.name).toBe("bound originalFunction");
});
});
// 6. Class 构造函数绑定测试
describe("6. Class 构造函数绑定", () => {
it("6.1 应该正确绑定类构造函数", () => {
class Person {
constructor(
public name: string,
public age: number
) {}
getInfo(): string {
return `${this.name} (${this.age}岁)`;
}
}
// 直接绑定类构造函数
const BoundPerson = Person.bind(null, "John");
const p = new BoundPerson(20);
expect(p.name).toBe("John");
expect(p.age).toBe(20);
expect(p instanceof Person).toBe(true);
expect(p.getInfo()).toBe("John (20岁)");
});
it("6.2 应该正确绑定带有默认参数的类构造函数", () => {
class User {
constructor(
public name: string,
public age: number = 18,
public role: string = "user"
) {}
}
// 绑定第一个参数
const BoundUser = User.bind(null, "Alice");
const user1 = new BoundUser();
const user2 = new BoundUser(25, "admin");
expect(user1.name).toBe("Alice");
expect(user1.age).toBe(18);
expect(user1.role).toBe("user");
expect(user2.name).toBe("Alice");
expect(user2.age).toBe(25);
expect(user2.role).toBe("admin");
});
it("6.3 应该正确绑定继承类的构造函数", () => {
class Animal {
constructor(
public name: string,
public species: string
) {}
}
class Dog extends Animal {
constructor(
name: string,
public breed: string
) {
super(name, "狗");
}
}
// 绑定子类构造函数
const BoundDog = Dog.bind(null, "旺财");
const dog = new BoundDog("金毛");
expect(dog.name).toBe("旺财");
expect(dog.species).toBe("狗");
expect(dog.breed).toBe("金毛");
expect(dog instanceof Dog).toBe(true);
expect(dog instanceof Animal).toBe(true);
});
});
// 7. Class 方法绑定测试
describe("7. Class 方法绑定", () => {
it("7.1 应该正确绑定类的实例方法", () => {
class Calculator {
private value: number = 0;
constructor(initialValue: number = 0) {
this.value = initialValue;
}
add(num: number): number {
this.value += num;
return this.value;
}
getValue(): number {
return this.value;
}
}
const calc = new Calculator(10);
const boundAdd = calc.add.bind(calc);
const boundGetValue = calc.getValue.bind(calc);
expect(boundAdd(5)).toBe(15);
expect(boundGetValue()).toBe(15);
});
it("7.2 应该正确绑定类的静态方法", () => {
class MathUtils {
static multiply(a: number, b: number): number {
return a * b;
}
}
const boundMultiply = MathUtils.multiply.bind(null, 10);
expect(boundMultiply(5)).toBe(50); // 10 * 5
});
});
// 8. 特殊情况测试
describe("8. 特殊情况", () => {
it("8.1 应该处理箭头函数(this 不会被改变)", () => {
const obj = { value: 42 };
const arrowFunc = () => obj.value;
const boundArrow = arrowFunc.bind({ value: 100 });
// 箭头函数的 this 不会被 bind 改变
expect(boundArrow()).toBe(42);
});
it("8.2 应该处理类的箭头函数方法", () => {
class EventHandler {
private message: string = "来自类的消息";
// 传统方法
handleEvent() {
return this.message;
}
// 箭头函数方法(自动绑定 this)
handleEventArrow = () => {
return this.message;
};
}
const handler = new EventHandler();
// 传统方法需要绑定
const boundHandleEvent = handler.handleEvent.bind(handler);
expect(boundHandleEvent()).toBe("来自类的消息");
// 箭头函数方法已经自动绑定,但仍然可以调用 bind
const boundHandleEventArrow = handler.handleEventArrow.bind({
message: "其他消息",
});
expect(boundHandleEventArrow()).toBe("来自类的消息"); // this 不会改变
});
});
// 9. Prototype 行为测试
describe("9. Prototype 行为", () => {
it("9.1 绑定函数没有 prototype 属性", () => {
function originalFunction() {}
originalFunction.prototype.customMethod = function () {
return "custom";
};
const boundFunction = originalFunction.bind(null);
// 原函数有 prototype 属性
expect(originalFunction.prototype).toBeDefined();
expect(originalFunction.prototype.customMethod).toBeDefined();
// 绑定函数没有 prototype 属性
expect(boundFunction.prototype).toBeUndefined();
});
it("9.2 绑定函数不能作为 extends 的基类", () => {
function Parent() {}
Parent.prototype.parentMethod = function () {
return "parent";
};
const BoundParent = Parent.bind(null);
// 尝试继承绑定函数会失败
expect(() => {
class Child extends (BoundParent as any) {}
}).toThrow();
});
it("9.3 instanceof 仍然有效,访问目标函数的 prototype", () => {
function Person(this: any, name: string) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
const BoundPerson = Person.bind(null, "John");
const instance = new (BoundPerson as any)();
// instanceof 仍然有效,因为它访问目标函数的 prototype
expect(instance instanceof Person).toBe(true);
expect(instance.getName()).toBe("John");
// 验证 prototype 链
expect(Object.getPrototypeOf(instance)).toBe(Person.prototype);
});
it("9.4 绑定函数的 instanceof 行为", () => {
function Animal(this: any, name: string) {
this.name = name;
}
function Dog(this: any, name: string, breed: string) {
Animal.call(this, name);
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const BoundDog = Dog.bind(null, "旺财");
const dog = new (BoundDog as any)("金毛");
// instanceof 检查会访问目标函数的 prototype
expect(dog instanceof Dog).toBe(true);
expect(dog instanceof Animal).toBe(true);
// 验证属性
expect(dog.name).toBe("旺财");
expect(dog.breed).toBe("金毛");
});
it("9.5 类的绑定构造函数 instanceof 行为", () => {
class Shape {
constructor(public name: string) {}
}
class Circle extends Shape {
constructor(public radius: number) {
super("圆形");
}
}
const BoundCircle = Circle.bind(null, 5);
const circle = new BoundCircle();
// instanceof 仍然有效
expect(circle instanceof Circle).toBe(true);
expect(circle instanceof Shape).toBe(true);
expect(circle.name).toBe("圆形");
expect(circle.radius).toBe(5);
});
});
});
现有版本存在的问题
❯ bind.test.ts (22 tests | 9 failed) 12ms
❯ Function.prototype.bind() 测试 (22)
✓ 1. 基本 this 绑定 (2)
✓ 1.1 应该正确绑定 this 上下文 1ms
✓ 1.2 应该能绑定到 null 0ms
✓ 2. 参数预设 (2)
✓ 2.1 应该能预设参数 0ms
✓ 2.2 应该按正确顺序处理预设参数和调用时参数 0ms
✓ 3. 多重绑定 (2)
✓ 3.1 应该忽略后续绑定的 this 值 0ms
✓ 3.2 应该累积预设参数 0ms
✓ 4. 构造函数行为 (2)
✓ 4.1 应该能作为构造函数使用 0ms
✓ 4.2 构造时应该忽略绑定的 this 值 0ms
❯ 5. 绑定函数属性 (2)
× 5.1 应该有正确的 length 属性 4ms
× 5.2 应该有正确的 name 属性 1ms
❯ 6. Class 构造函数绑定 (3)
× 6.1 应该正确绑定类构造函数 0ms
× 6.2 应该正确绑定带有默认参数的类构造函数 0ms
× 6.3 应该正确绑定继承类的构造函数 0ms
✓ 7. Class 方法绑定 (2)
✓ 7.1 应该正确绑定类的实例方法 0ms
✓ 7.2 应该正确绑定类的静态方法 0ms
✓ 8. 特殊情况 (2)
✓ 8.1 应该处理箭头函数(this 不会被改变) 0ms
✓ 8.2 应该处理类的箭头函数方法 0ms
❯ 9. Prototype 行为 (5)
× 9.1 绑定函数没有 prototype 属性 1ms
× 9.2 绑定函数不能作为 extends 的基类 1ms
× 9.3 instanceof 仍然有效,访问目标函数的 prototype 1ms
✓ 9.4 绑定函数的 instanceof 行为 0ms
× 9.5 类的绑定构造函数 instanceof 行为 0ms
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 9 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 5. 绑定函数属性 > 5.1 应该有正确的 length 属性
AssertionError: expected +0 to be 3 // Object.is equality
- Expected
+ Received
- 3
+ 0
❯ bind.test.ts:155:29
153|
154| const bound1 = testFunc.bind(null);
155| expect(bound1.length).toBe(3);
| ^
156|
157| const bound2 = testFunc.bind(null, 1);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 5. 绑定函数属性 > 5.2 应该有正确的 name 属性
AssertionError: expected 'fBound' to be 'bound originalFunction' // Object.is equality
Expected: "bound originalFunction"
Received: "fBound"
❯ bind.test.ts:168:34
166| const boundFunction = originalFunction.bind(null);
167|
168| expect(boundFunction.name).toBe("bound originalFunction");
| ^
169| });
170| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 6. Class 构造函数绑定 > 6.1 应该正确绑定类构造函数
TypeError: Class constructor Person cannot be invoked without 'new'
❯ new fBound bind.test.ts:30:15
28| const fBound = function (this: any, ...args: any[]) {
29| const context = this instanceof fBound ? this : thisArg;
30| return fn.apply(context, [...argArray, ...args]);
| ^
31| };
32|
❯ bind.test.ts:188:17
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 6. Class 构造函数绑定 > 6.2 应该正确绑定带有默认参数的类构造函数
TypeError: Class constructor User cannot be invoked without 'new'
❯ new fBound bind.test.ts:30:15
28| const fBound = function (this: any, ...args: any[]) {
29| const context = this instanceof fBound ? this : thisArg;
30| return fn.apply(context, [...argArray, ...args]);
| ^
31| };
32|
❯ bind.test.ts:207:21
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 6. Class 构造函数绑定 > 6.3 应该正确绑定继承类的构造函数
TypeError: Class constructor Dog cannot be invoked without 'new'
❯ new fBound bind.test.ts:30:15
28| const fBound = function (this: any, ...args: any[]) {
29| const context = this instanceof fBound ? this : thisArg;
30| return fn.apply(context, [...argArray, ...args]);
| ^
31| };
32|
❯ bind.test.ts:238:19
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 9. Prototype 行为 > 9.1 绑定函数没有 prototype 属性
AssertionError: expected originalFunction{} to be undefined
- Expected:
undefined
+ Received:
originalFunction {}
❯ bind.test.ts:343:39
341|
342| // 绑定函数没有 prototype 属性
343| expect(boundFunction.prototype).toBeUndefined();
| ^
344| });
345|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 9. Prototype 行为 > 9.2 绑定函数不能作为 extends 的基类
AssertionError: expected [Function] to throw an error
❯ bind.test.ts:357:10
355| expect(() => {
356| class Child extends (BoundParent as any) {}
357| }).toThrow();
| ^
358| });
359|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 9. Prototype 行为 > 9.3 instanceof 仍然有效,访问目标函数的 prototype
AssertionError: expected Person{} to be { Object (getName) } // Object.is equality
- Expected
+ Received
- Person {
- "getName": [Function anonymous],
- }
+ Person {}
❯ bind.test.ts:376:47
374|
375| // 验证 prototype 链
376| expect(Object.getPrototypeOf(instance)).toBe(Person.prototype);
| ^
377| });
378|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/9]⎯
FAIL bind.test.ts > Function.prototype.bind() 测试 > 9. Prototype 行为 > 9.5 类的绑定构造函数 instanceof 行为
TypeError: Class constructor Circle cannot be invoked without 'new'
❯ new fBound bind.test.ts:30:15
28| const fBound = function (this: any, ...args: any[]) {
29| const context = this instanceof fBound ? this : thisArg;
30| return fn.apply(context, [...argArray, ...args]);
| ^
31| };
32|
❯ bind.test.ts:417:22
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/9]⎯
Test Files 1 failed (1)
Tests 9 failed | 13 passed (22)
Start at 16:23:14
Duration 304ms (transform 32ms, setup 0ms, collect 30ms, tests 12ms, environment 0ms, prepare 60ms)
改进
下面我们根据MDN文档进行修改
目标函数的 length 减去被绑定的参数个数(不包括 thisArg 参数),最小值为 0
Object.defineProperty(fBound, "length", {
value: Math.max(0, fn.length - argArray.length),
configurable: true,
enumerable: false,
writable: false,
});
目标函数的 name 前加上 "bound " 前缀
Object.defineProperty(fBound, "name", {
value: `bound ${fn.name}`,
configurable: true,
enumerable: false,
writable: false,
});
绑定函数可以使用 new 运算符进行构造
const fBound = function (this: any, ...args: any[]) {
const allArgs = [...argArray, ...args];
if (new.target) {
return Reflect.construct(fn, allArgs);
}
return fn.apply(thisArg, allArgs);
};
绑定函数没有 prototype 属性
Object.defineProperty(fBound, "prototype", {
value: undefined,
writable: false,
configurable: false,
enumerable: false,
});
当将绑定函数用作 instanceof 运算符右操作数时,instanceof 会访问绑定函数内部存储的目标函数,并读取其 prototype 属性
if (Symbol.hasInstance) {
Object.defineProperty(fBound, Symbol.hasInstance, {
value: (instance: unknown) => {
if (!fn.prototype) {
return false;
}
return instance instanceof fn;
},
configurable: true,
});
}
最终实现
Function.prototype.bind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
const fn = this;
const fBound = function (this: any, ...args: any[]) {
const allArgs = [...argArray, ...args];
if (new.target) {
return Reflect.construct(fn, allArgs);
}
return fn.apply(thisArg, allArgs);
};
Object.defineProperty(fBound, "prototype", {
value: undefined,
writable: false,
configurable: false,
enumerable: false,
});
if (Symbol.hasInstance) {
Object.defineProperty(fBound, Symbol.hasInstance, {
value: (instance: unknown) => {
if (!fn.prototype) {
return false;
}
return instance instanceof fn;
},
configurable: true,
});
}
Object.defineProperty(fBound, "length", {
value: Math.max(0, fn.length - argArray.length),
configurable: true,
enumerable: false,
writable: false,
});
Object.defineProperty(fBound, "name", {
value: `bound ${fn.name}`,
configurable: true,
enumerable: false,
writable: false,
});
return fBound;
};