引言
你有没有发现,每次 JavaScript 面试,面试官总爱问你 call
、bind
和 apply
的区别?好像这三个方法成了通关密码,掌握了它们,就能顺利过关。其实不难理解,面试官问这些问题,不仅是在考察你对 this
的理解,更是在考验你是否能灵活运用这些工具。接下来,我们就一起来解锁这三位面试“常客”的秘密,确保你下次遇到它们时,不再慌乱。
有关this值的执行机制可以看:【js八股文】this值大揭秘 - 掘金
我们先来看一段代码
var a = {
name: 'Cherry',
fn: function(a, b) {
console.log(this.name)
return a + b;
}
}
var b = a.fn;
console.log(b.apply(a, [1, 2]))
console.log(b.call(a, 1, 2))
console.log(b.call(a, [1, 2]))
console.log(b.bind(a, 1, 2)())
输出结果
可以看到,当分别使用.apply
与.call
传递参数,第一个参数是指定this的值,而传递a,b
的值时,.apply
以数组的形式传递可以正确输出a+b
的值为3
,而使用.call
却输出1,2undefiend
,并且以.bind
传递时后面要多加一个()
,这是为什么呢?
这就需要考虑:
.call()
和 .apply()
.bind()
的区别:参数传递方式
1. .call()
— 逐个传递参数
.call()
方法接收 逐个列出的参数,它的第一个参数是函数执行时的this
,后续的参数是逐一传递给函数的。- 也就是说,参数是单独列出的,每个参数通过逗号分隔传递给目标函数。
var b = a.fn;
console.log(b.call(a, 1, 2)); // 输出:Cherry 3
console.log(b.call(a, [1, 2]))//输出:1,2undefiend
-
当我们使用数组传递参数时
call(a, [1, 2])
实际上传递了一个单独的参数[1, 2]
,而不是拆开这个数组。所以,在fn
中: -
a
的值是整个数组[1, 2]
。 -
b
的值是undefined
,因为没有传递第二个参数。 -
JavaScript 会自动将数组转换成字符串
1, 2
,并与undefined
相加,所以输出结果是"1,2undefined"
。
2. .apply()
— 以数组的形式传递参数
.apply()
方法的参数与.call()
类似,但它要求第二个参数是一个数组或类数组对象,数组中的每一项会作为单独的参数传递给目标函数。
console.log(b.apply(a, [1, 2])); // 输出:Cherry 3
.apply()
将this
指定为a
,并将数组[1, 2]
作为参数传入目标函数。- 执行结果与
.call()
类似:输出this.name
的值"Cherry"
,并计算a + b
得到3
。
区别:
.call()
通过逗号分隔的形式单独列出每一个参数,而.apply()
则将多个参数封装成一个数组传递给函数。通常当函数的参数数量不确定时,.apply()
更加灵活。
3..bind()
:—返回一个新函数,参数以数组的形式传递
.bind()
方法会返回一个新的函数,新的函数绑定了指定的 this
和传递给它的初始参数。当该新函数被调用时,this
和参数会被传递给原函数。
console.log(b.bind(a, 1, 2)()); // 输出:Cherry 3
.bind(a, 1, 2)
创建了一个新的函数,并将this
指定为a
,初始参数为1
和2
。- 由于
.bind()
返回的是一个新函数,必须通过()
来调用它,才能触发函数的执行。 - 执行时,
this.name
输出"Cherry"
,并计算a + b
得到3
。
区别:
.bind()
不会立即执行函数,而是返回一个新的函数,只有在手动调用新函数时,this
和参数才会被传入目标函数。
执行时机的区别
除了参数传递方式的不同,.call()
、.apply()
和 .bind()
之间最显著的差异还在于它们的执行时机。
1..call()
和 .apply()
:立即执行函数
.call()
和 .apply()
的最大特点是它们会立即执行目标函数,并且在执行时确定 this
上下文和传入的参数。
代码示例:
function greet(age, city) {
console.log(`${this.name} is ${age} years old and lives in ${city}`);
}
const person = { name: 'Cherry' };
greet.call(person, 30, 'New York'); // 立即执行,输出:Cherry is 30 years old and lives in New York
greet.apply(person, [30, 'New York']); // 立即执行,输出:Cherry is 30 years old and lives in New York
- 这两种方法都会在调用时立即执行函数,确保函数内的
this
指向指定的对象,并使用传入的参数。
2..bind()
:返回一个新函数,延迟执行
.bind()
与 .call()
和 .apply()
最大的区别是它并不会立即执行函数,而是返回一个新的函数,这个函数在后续的调用中执行。
代码示例:
function greet(age, city) {
console.log(`${this.name} is ${age} years old and lives in ${city}`);
}
const person = { name: 'Alice' };
const boundGreet = greet.bind(person, 30, 'New York');
boundGreet(); // 延迟执行,输出:Alice is 30 years old and lives in New York
- 在调用
.bind()
时,它并不会立即执行b
函数,而是返回一个新的函数,这个新函数绑定了this
和初始参数。 - 只有在通过
()
调用新函数时,函数才会被执行。
3. 实际应用示例
让我们再看一个更复杂的例子,展示 .call()
、.apply()
和 .bind()
在异步代码中的实际应用,特别是在 setTimeout
中的表现。
代码示例: 使用 .call()
(会立即执行)
var obj = {
name: "Cherry",
func1: function() {
console.log(this.name); // 输出 obj 的 name 属性
},
func2: function() {
setTimeout(function() {
this.func1(); // 默认 this 是 setTimeout 的上下文,而非 obj
}.call(obj), 5000);
}
}
obj.func2(); // 由于 call 是立即执行的,仍然会输出Cherry,但并没有在5秒后执行
解释:
.call(obj)
:这里,.call(obj)
会立即执行setTimeout
内部的回调函数,并将回调函数的this
设置为obj
。- 因为
.call()
是立即执行的,它不会等到 5 秒后执行,而是立刻执行回调。回调中的this
被绑定为obj
,因此this.func1()
会正确调用obj.func1()
并输出Cherry
。
修正:使用 .bind()
(延迟执行)
var obj = {
name: "Cherry",
func1: function() {
console.log(this.name); // 输出 obj 的 name 属性
},
func2: function() {
setTimeout(function() {
this.func1(); // 使用 .bind() 保证 this 永远指向 obj
}.bind(obj), 5000);
}
}
obj.func2(); // func1 的 this 会正确指向 obj,并且在5秒后输出:Cherry
解释:
.bind(obj)
:在这个例子中,我们使用.bind(obj)
来确保回调函数的this
被绑定为obj
。- 通过
.bind(obj)
,我们返回了一个新的函数,这个新函数会在 5 秒后执行,并且在执行时确保this
始终指向obj
。因此,this.func1()
会正确输出Cherry
。
总结
特性 | .call() | .apply() | .bind() |
---|---|---|---|
执行时机 | 立即执行函数 | 立即执行函数 | 返回一个新函数,需要手动调用 |
参数传递方式 | 单独列出参数 | 参数以数组的形式传递 | 参数以数组的形式传递,返回新函数 |
返回值 | 返回函数执行的结果 | 返回函数执行的结果 | 返回一个新的函数 |
主要用途 | 确定 this ,并立即调用函数 | 确定 this ,并立即调用函数 | 确定 this ,并返回一个新的函数,在后续调用时执行 |
.call()
和.apply()
的主要区别在于它们的参数传递方式:.call()
逐个传递参数,而.apply()
传递一个数组。.bind()
则与.call()
和.apply()
最大的区别在于 延迟执行,它返回一个新函数,只有在手动调用时才会执行。
掌握这些差异将帮助你在开发中根据具体需求灵活使用这三种方法,特别是在处理异步操作和函数调用上下文时。
如果这篇文章对你有帮助就点个赞再走吧