1.call,apply,bind相同点和区别总结
相同点 : 作用一致,修改函数this指向
不同点 :
传参方式不同 :
call是按照顺序传参 sum.call(obj,1,2,3),
apply是数组/伪数组传参sum.apply(obj,[1,2,3]),
bind按照顺序传参sum1=sum.bind(obj,1,2,3)(),返回一个新的函数sum1(4,5)可以后续补充参数
执行机制不同 :
call和apply会立即执行函数,而bind不会立即执行而是得到修改this的新函数
柯里化特性
bind 支持 “预传部分参数 + 后续补充参数” 的柯里化特性
call和apply“立即绑定上下文(this)并调用函数”,参数必须在调用时一次性传全(或传部分,但后续无法补充)
2.call,apply,bind底层原理
2.1 call
①.绑定this
// 绑定this
Function.prototype.call2 = function(context) {
// 首先要获取调用call2的函数,用this可以获取,这里是bar
context.fn = this;//foo.fn=bar
context.fn();//执行bar(),通过foo调用
delete context.fn;//垃圾回收
}
// 测试一下
let foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
根据前面发的执行上下文和this,我们可以知道
当执行 bar.call2(foo) 时,发生了这几件事:
call2函数内部的this指向bar(因为是bar调用了call2);context指向传入的foo对象;context.fn = this→ 相当于foo.fn = bar(给foo临时加了一个fn属性,值为bar函数);context.fn()→ 相当于foo.fn()(通过foo对象调用bar函数);- 因为函数是通过
foo对象调用的,所以bar内部的this指向foo,因此this.value就是foo.value(1); - 最后
delete context.fn→ 删除foo上的临时属性fn,恢复foo的原始状态(避免污染)。
②.传参
Function.prototype.call2 = function(context,...arg) {
context.fn = this;
context.fn(...arg)
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'aria', 28);
// aria
// 28
// 1
③.this传入null
JavaScript 中 call(null) 的核心是:
- 非严格模式:
this绑定全局对象(window/global); - 严格模式:
this为null; - 箭头函数:
this不受影响(继承外层作用域)。
最实用的场景是调用不依赖 this 的函数并传递参数,明确表示 “无需改变 this 指向”,语义更清晰。若函数依赖 this,则需传入具体的绑定对象(而非 null)。
Function.prototype.call2 = function (context, ...args) {
// 如果 context 是 null 或 undefined,globalThis 就是正确的全局对象
// 在浏览器里,globalThis 就是 window
// 在 Node.js 里,globalThis 就是 global
const ctx = context ?? globalThis;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
//原生 call 的逻辑是:执行目标函数后,会将函数的返回值作为自己的返回值返回。
};
相关知识点
globalThis
globalThis是 ECMAScript 2020 (ES11) 引入的一个标准内置对象,它提供了一种统一的方式来访问全局对象,无论代码运行在什么环境中。
- 在浏览器环境中:
globalThis指向window对象。- 在 Node.js 环境中:
globalThis指向global对象。- 在 Web Workers 环境中:
globalThis指向 worker 全局作用域。- 在严格模式下:即使在模块或函数中,
globalThis依然指向全局对象,这与this的行为不同。
|| 和?? 区别
||逻辑或运算符
- 判断标准:只要左侧是「假值(falsy)」,就返回右侧值;否则返回左侧值。
- JavaScript 中的假值:
undefined、null、false、0、-0、NaN、空字符串""。
??空值合并运算符
- 判断标准:只有左侧是「空值(nullish)」,才返回右侧值;否则返回左侧值。
- JavaScript 中的空值:仅
undefined和null(这是它与||的核心区别)。
2.2 apply
apply 的实现跟 call 类似,只是入参不一样,apply为数组,call为参数
Function.prototype.call2 = function (context, args) {
const ctx = context ?? globalThis;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
};
2.3 bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
①.this绑定 上下文
关于指定 this 的指向,我们可以使用 call 或者 apply 实现
Function.prototype.bind2 = function (context) {
var self = this;
return function () {
return self.apply(context);
}
}
②.传参 柯里化(预传部分参数 + 后续补充参数)
Function.prototype.bind2 = function (context, ...args) {
// 剩余参数提取预传参数
const self = this;
// 返回的函数也用剩余参数提取“补充参数”
return function (...bindArgs) {
// 合并预传参数和补充参数,传给apply(第二个参数必须是数组)
return self.apply(context, [...args, ...bindArgs]);
}
}
③bind返回的函数作为构造函数(预传部分参数 + 后续补充参数)
bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。
a.原始的bind函数
通过把" bind 返回的函数 "作为构造函数,实现柯里化, 预传部分参数 + 后续补充参数
function Product(name, price) {
this.name = name;
this.price = price;
}
// 创建一个绑定了预存参数的构造函数
const DiscountedProduct = Product.bind(null, 'Discount Item');
// 使用 new 创建实例,只需要提供价格
const product1 = new DiscountedProduct(9.99);
console.log(product1); // 输出: Product { name: 'Discount Item', price: 9.99 }
b.模拟bind,判断是否用作构造函数,并改变原型指向
Function.prototype.bind2 = function (context,...args) {
//边界校验,确保调用者是函数,call,apply,bind都需要确保调用者是函数,否则报错
if (typeof this !== 'function') {
throw new TypeError(`${this} is not a function`);
}
var self = this;
var fBound = function (...bindArgs) {
//this instanceof fBound 判断fBound是否在原型链上,是不是被当做构造函数,
//是构造函数就this,不是构造函数就绑定传入的context
// [...args,...bindArgs] 合并:bind2 时的预传参数 + 新函数执行时的参数
return self.apply(this instanceof fBound ? this : context, [...args,...bindArgs]);
}
//继承原型
fBound.prototype = Object.create(this.prototype);
//修复 fBound.prototype.constructor 指向,防止constructor被修改过
fBound.prototype.constructor = this
//返回函数
return fBound;
}
最后
这是JavaScript系列第7篇,将持续更新。
小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!
您对我的关注、点赞和收藏,是对我最大的支持!欢迎关注、评论、讨论和指正!