上节课我们学了 call 和 apply,它们的核心是「立即执行函数 + 改变 this 指向」;而 bind 不一样 —— 它的核心是「不立即执行,返回一个绑定了 this 指向的新函数」,还支持 “柯里化传参”(分多次传参)。
function sayHi(name, age) {
console.log(`我是${this.name},你好${name},我${age}岁`);
return `结果:${this.name}-${name}`;
}
const person = { name: "张三" };
// 1. bind 不立即执行,返回新函数
const boundSayHi = sayHi.bind(person, "小明"); // 先绑定 this+第一个参数
// 2. 后续调用新函数时,传入剩余参数
boundSayHi(20); // 输出:我是张三,你好小明,我20岁(this 已绑定 person)
// 柯里化传参:分两次传参,最终合并
boundSayHi(22); // 输出:我是张三,你好小明,我22岁
bind 最核心的特点是 “返回新函数”,而不是立即执行 —— 这也是手写 bind 和 call/apply 最大的区别!
Function.prototype.myBind = function (context) {
// 步骤1:判断调用者是不是函数(和 call/apply 一致)
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 步骤2:保存原函数引用(this 就是原函数,比如 sayHi)和第一次传入的参数
const fn = this; // 保存原函数,避免后续调用时 this 丢失
const args = [...arguments].slice(1); // 截取 bind 调用时的参数(除了 context),比如 ["小明"]
// 步骤3:返回新函数(延迟执行的核心,这是和 call/apply 最大的区别)
return function Fn() {
// 步骤4.1:合并两次传入的参数(bind 时的参数 + 新函数调用时的参数)
// arguments 是新函数调用时的参数,比如 [20]
const combinedArgs = args.concat(...arguments); // 合并为 ["小明", 20]
// 步骤4.2:判断新函数是否被当作构造函数调用(用 new 实例化)
// 关键:this instanceof Fn → 新函数被 new 时,this 是 Fn 的实例
const bindContext = this instanceof Fn ? this : context;
// 步骤4.3:用 apply 执行原函数(绑定 this+传合并后的参数)
return fn.apply(bindContext, combinedArgs);
};
};
function sayHi(name, age) {
console.log(`我是${this.name},你好${name},我${age}岁`);
return `结果:${this.name}-${name}`;
}
const person = { name: "张三" };
const boundSayHi = sayHi.myBind(person, "小明"); // 绑定 this+第一个参数
// 新函数调用,传入剩余参数
boundSayHi(20); // 输出:我是张三,你好小明,我20岁(this 正确,参数合并正确)
const res = boundSayHi(22);
console.log(res); // 结果:张三-小明(返回值正确)
function Person(name, age) {
this.name = name;
this.age = age;
}
// 用 bind 绑定 context(这里绑定 window,测试是否兼容 new)
const BoundPerson = Person.myBind(window, "李四");
// 用 new 实例化新函数
const p = new BoundPerson(25);
console.log(p.name); // 李四(this 指向实例,而非 window,兼容成功)
console.log(p.age); // 25(参数合并正确)
console.log(p instanceof Person); // true(实例属于原函数 Person,符合原生行为)