前言:探索 JavaScript 函数绑定的多样世界
在我深入研究 setTimeout
时,遇到了一个常见的挑战:如何确保在异步回调中正确地绑定 this
的指向。最初,我总是依赖于 call
方法来解决这个问题,因为它简单直接,能够立即执行函数并显式地设置 this
。然而,随着项目的复杂度逐渐增加,我发现仅仅依靠 call
并不足以应对所有情况。这让我开始思考:JavaScript 中是否还有其他方法可以实现类似的 this
绑定?它们与 call
又有着怎样的区别和各自的优势呢?
在这篇文章中,我们将一起探讨这些不同的函数绑定方法,包括:
apply
:与call
类似,但它接受参数数组,非常适合处理未知数量或动态数量的参数。call
:允许你显式地设置函数内部的this
值,并立即执行函数,适用于已知参数数量和顺序的情况。bind
:创建并返回一个新的函数,该函数的this
被永久绑定到提供的对象,不会立即执行,非常适合预设部分参数和处理异步回调。
执行时间:
-
call
和apply
:两者都会立即调用函数,并且可以在调用时指定this
的值。 -
bind
:创建并返回一个新的函数,但不会立即执行。新函数的this
被永久绑定到提供的对象,可以稍后调用。
案例:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var obj = {
name: "Cherry",
func1: function() {
console.log(this.name)
},
func2: function() {
setTimeout(function() {
this.func1()
}.call(obj), 10000) // 立即执行
},
func3: function() {
setTimeout(function() {
this.func1()
}.apply(obj), 1000000) // 立即执行
},
func4: function() {
setTimeout(function() {
this.func1()
}.bind(obj), 1000)
}
}
obj.func2()
obj.func3()
obj.func4()
</script>
</body>
</html>
结果:
在这个代码片段中,func2
、func3
和 func4
都试图通过 setTimeout
来延迟执行一个匿名函数,并确保该匿名函数内部的 this
指向 obj
。然而,func2
和 func3
的实现存在一个问题:.call(obj)
和 .apply(obj)
会立即执行 匿名函数,而不是将其作为回调传递给 setTimeout
,所以会马上输出obj.name
, 而.bind
会返回一个新的函数不会马上执行,会传递给 setTimeout
等待时间后,再输出。
参数传入
call的参数传入
案例:
js
var a={
name:"zhangsan",
fn:function(a,c){
console.log(this.name);
console.log(a+c);
}
}
var b=a.fn;
b.call(a,1,2);
解释: 这段代码定义了一个对象 a
,它有一个属性 name
和一个方法 fn
。方法 fn
接受两个参数 a
和 c
,并打印出当前上下文(即调用时的 this
)的 name
属性以及这两个参数的和。
接着,创建了变量 b
,它引用了对象 a
的 fn
方法。此时,b
是一个独立于对象 a
的函数引用。如果直接调用 b()
,那么在非严格模式下,this
将指向全局对象(例如浏览器中的 window
),而在严格模式下,this
将是 undefined
。因此,直接调用 b()
不会访问到对象 a
的 name
属性。
通过call:
b.call(a, 1, 2)
中的call
方法让b
函数内部的this
指向了对象a
。- 参数
1
和2
分别作为fn
方法的参数a
和c
的值传递给函数。 - 当
fn
方法被执行时,它首先打印this.name
,也就是对象a
的name
属性"zhangsan"
。 - 然后它打印参数
a
和c
的和,即1 + 2
的结果3
。
最终: call
的第一个参数总是用来设置函数执行时的 this
值,而后续的参数则是目标函数的实际参数。每个参数都是单独列出的,以逗号分隔。call
一个个给 call(thisBinder,a,b,c,....)
。
apply的参数传入
案例
js
var a={
name:"zhangsan",
fn:function(a,c){
console.log(this.name);
console.log(a+c);
}
}
var b=a.fn;
b.apply(a,[1,2]);
解释: 其他和call
其实一样,我就不做过多的赘述,我们可以看到b.apply(a,[1,2]);
这段代码就是apply
和call
的区别所在:apply
接受一个参数数组(或类数组对象) 作为它的第二个参数,这个数组中的元素将作为参数传递给目标函数。apply(thisBinder,[a,b,c,....],[a,b,c,....])
bind的不同之处:
讲实话,bind有点像call的私生子,和call有点像,但是有自己的不同的地方。
案例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var a={
name:"zhangsan",
fn:function(a,b){
console.log(this.name);
console.log(a+b);
}
}
var b=a.fn;// 普通函数
console.log( b.bind(a,1,2) ) // 返回新的函数,但是不立即执行, 酷似call()
b.bind(a,1,2)()
</script>
</body>
</html>
输出:
js
ƒ (a,b){
console.log(this.name);
console.log(a+b);
}
zhangsan
3
结果: 我们可以看到,.bind
的传入方式和call
一样,但是它只返回了一个新的函数,没有马上执行。 这意味着你可以延迟执行,甚至可以在后续调用中添加更多参数。
总结:使用时机
call
和apply
:当你需要立即执行函数并显式设置this
值时,根据是否已有参数数组来选择call
或apply
。bind
:当你需要创建一个具有特定this
绑定和预设参数的新函数,并且可能在不同的时间点调用它时,bind
是更好的选择,特别是对于异步回调和事件监听器等场景。
有趣的现象:
当我用,传入apply
参数的方式,传给call
,会发生什么呢?通过这段代码大家可以猜猜结果是什么?
js
var a={
name:"zhangsan",
fn:function(a,b){
console.log(this.name);
console.log(a+b);
}
}
var b=a.fn;
b.call(a,[1,2])
最终结果:
js
zhangsan
1,2undefined
为什么是这个是1+2undefined
呢?
由于传递的是一个数组 [1,2]
作为一个单独的参数,这意味着在函数 fn
内部,a
参数会接收到整个数组 [1,2]
,而 b
参数将是 undefined
(因为没有提供第二个参数)。因此,console.log(a + b)
实际上会尝试将数组 [1,2]
与 undefined
字符串拼接,因为 JavaScript 无法直接相加数组和 undefined
时,会自动转化为字符串。
实际运行: a.toString()+String(b)
为什么b不使用toString(),就留给大家思考了。
如果本文对你有帮助,可以留下小小的赞,如果有错误,敬请指出,谢谢您的指教。