除了 call,JS 还有哪些强大的函数绑定方式?

前言:探索 JavaScript 函数绑定的多样世界

在我深入研究 setTimeout 时,遇到了一个常见的挑战:如何确保在异步回调中正确地绑定 this 的指向。最初,我总是依赖于 call 方法来解决这个问题,因为它简单直接,能够立即执行函数并显式地设置 this。然而,随着项目的复杂度逐渐增加,我发现仅仅依靠 call 并不足以应对所有情况。这让我开始思考:JavaScript 中是否还有其他方法可以实现类似的 this 绑定?它们与 call 又有着怎样的区别和各自的优势呢?

在这篇文章中,我们将一起探讨这些不同的函数绑定方法,包括:

  • apply :与 call 类似,但它接受参数数组,非常适合处理未知数量或动态数量的参数。
  • call :允许你显式地设置函数内部的 this 值,并立即执行函数,适用于已知参数数量和顺序的情况。
  • bind :创建并返回一个新的函数,该函数的 this 被永久绑定到提供的对象,不会立即执行,非常适合预设部分参数和处理异步回调。

执行时间:

  • callapply :两者都会立即调用函数,并且可以在调用时指定 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>

结果:

在这个代码片段中,func2func3func4 都试图通过 setTimeout 来延迟执行一个匿名函数,并确保该匿名函数内部的 this 指向 obj。然而,func2func3 的实现存在一个问题:.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 接受两个参数 ac,并打印出当前上下文(即调用时的 this)的 name 属性以及这两个参数的和。

接着,创建了变量 b,它引用了对象 afn 方法。此时,b 是一个独立于对象 a 的函数引用。如果直接调用 b(),那么在非严格模式下,this 将指向全局对象(例如浏览器中的 window),而在严格模式下,this 将是 undefined。因此,直接调用 b() 不会访问到对象 aname 属性。

通过call:

  1. b.call(a, 1, 2) 中的 call 方法让 b 函数内部的 this 指向了对象 a
  2. 参数 12 分别作为 fn 方法的参数 ac 的值传递给函数。
  3. fn 方法被执行时,它首先打印 this.name,也就是对象 aname 属性 "zhangsan"
  4. 然后它打印参数 ac 的和,即 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]);这段代码就是applycall 的区别所在: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一样,但是它只返回了一个新的函数,没有马上执行。 这意味着你可以延迟执行,甚至可以在后续调用中添加更多参数。

总结:使用时机

  • callapply :当你需要立即执行函数并显式设置 this 值时,根据是否已有参数数组来选择 callapply
  • 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(),就留给大家思考了。

如果本文对你有帮助,可以留下小小的赞,如果有错误,敬请指出,谢谢您的指教。

相关推荐
WeiLai11125 分钟前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
浪九天24 分钟前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
qianmoQ1 小时前
第五章:工程化实践 - 第三节 - Tailwind CSS 大型项目最佳实践
前端·css
C#Thread1 小时前
C#上位机--流程控制(IF语句)
开发语言·javascript·ecmascript
椰果uu1 小时前
前端八股万文总结——JS+ES6
前端·javascript·es6
微wx笑2 小时前
chrome扩展程序如何实现国际化
前端·chrome
~废弃回忆 �༄2 小时前
CSS中伪类选择器
前端·javascript·css·css中伪类选择器
CUIYD_19892 小时前
Chrome 浏览器(版本号49之后)‌解决跨域问题
前端·chrome
IT、木易2 小时前
跟着AI学vue第五章
前端·javascript·vue.js
薛定谔的猫-菜鸟程序员2 小时前
Vue 2全屏滚动动画实战:结合fullpage-vue与animate.css打造炫酷H5页面
前端·css·vue.js