Javascript中this指向丢失原因及解决办法详解

大家都知道 JS 中的 this 关键字通常出现在函数或者方法中,用来指向调用该函数或者方法的对象。但是在很多时候 this 的指向却并不总是如我们所愿,这一篇文章就一起来看看到底该如何判断 this 所指向的对象,同时在 this 指向丢失情况下如何恢复。

this指向丢失

相信有过面向对象编程经验的朋友对于 this 的使用不会陌生,来看两个例子

这里的 this 指向的是构造函数生成的对象 zhangsan,对象调用自身的方法 sayHello(),其中的 this 自然不会有什么指向问题。

这里只是把构造函数换成了 class 语法的方式,this 指向的是类实例 xiaofu,实例调用自身的方法,其中的 this 也不会有什么指向问题。

但是再看下面这个例子

注意 setTimeout 的第一个参数是一个函数名,而并不是具体的函数调用。

所以这里并不能直接传递 zhangsan.sayHello(),不然会马上执行。

本意是想等待 2 秒之后再打印,结果打印完发现 this.name 并没有打印出来,this 指向丢失了。按照 this 指向调用函数的对象的逻辑,说明 2 秒后调用 sayHello() 这个方法的已经不是 zhangsan 这个对象了。

如果在方法中打印一下 this,就会发现此时 this 指向的是 Window。也就是说最后一句可以像如下改写

执行异步操作的时候是将一个函数丢给浏览器,2 秒以后,浏览器去直接执行该函数。

这时候可以引出一个重要的结论:包含 this 的函数无法在定义的时候,而只有在被真正执行的时候才能知道this指向哪个对象。

再看下面的例子就很容易理解了

因为 sayHello 这个方法真正执行的时候是被 lisi 这个对象调用,所以 this 指向的是 lisi 这个对象,this.name 打印了出来也是 lisi

多重调用以及箭头函数

可能有朋友又要问了,那我直接执行 zhangsan.sayHello() 不也是相当于在 Window 中去执行这个函数吗?

让我们再看下面这个例子

这里调用的 sayHello 函数是 info 对象下的,可以看到函数中的 this 指向的是 info 对象,而并不是 zhangsan 对象。这里又可以引出另外一个重要的结论:多重调用下,函数中的 this 只会指向函数的上一级对象。这里函数的上一级对象是 info,所以虽然 zhangsan 中也有一个 name,但是并不会被引用。

但是这里需要注意的是箭头函数。

箭头函数在 ES6 中被引入,写起来简洁明了,但是有一个特点需要注意,就是箭头函数没有独立的 this,其中的this 会自动从上一级继承。

所以如果改写下上面的代码

可以看出,箭头函数中使用 this 就和直接在 info 中使用 this 效果一样,都是指向 zhangsan 对象。

this指向丢失解决办法

再把话题回到 this 丢失上面来。

想要恢复 this 指向,根本逻辑就是想办法还是让 this 定义时候的对象来调用 this 所在的函数,回到上面的例子就是让 zhangsan 来调用 sayHello()

有两种方式可以来实现,第一种是多添加一层函数调用

这里的最后一句相当于 Window.zhangsan.sayHello(),根据上面的规则,this 指向的是上一级对象,也就是zhangsan,所以可以成功打印出来。

并且这里使用箭头函数同样有效果,因为这里的函数只是起到多加一层包装的作用,并没有实际作用。

这里要特别说明一下特殊情况:

这个情况不是很好理解,为什么箭头函数包装的 this 就可以传递过去?

其实,更方便理解的等价的写法如下:

 注意如下函数:

这样修改会更方便理解。其实本质上箭头函数本质上就是一个对象,这个对象保持了对外部对象的引用,因此不会出现 this 丢失的情况。

第二种方式是利用函数的 bind 方法,使用语法如下

这里就是将函数 func 绑定到了 context 这个上下文上,返回一个新的函数。不管被谁调用,这个新的函数里面的this 永远指向 context

这里就是将 sayHello() 这个方法绑定到了 zhangsan 这个对象上,以后不管这个返回的新函数被谁调用,都可以成功返回 zhangsan 中的 this.name

但是这里要注意的是,只能绑定到构造函数返回的具体对象上,而不能直接绑定到类名 Student 上。

同时要注意 bind 并不支持级联操作

这里首先将函数f绑定到一个对象,然后马上级联操作绑定到另一个对象,可以看出只有第一个 bind 起了效果。

同时这里也可以看到只有在函数执行的时候才会将 this 指向具体的对象

bind传递函数参数

这里再提一个 bind 方法的进阶用法,就是固定函数传递的一部分参数值,有一点类似 python 中的 partial 函数。因为 bind 方法除了第一个参数是上下文,后面还可以接函数的默认参数值

这里修改了 sayHello() 方法,必须要传递一个参数,如果想以后每次执行该方法的时候都是传递参数 99 就可以像上面那样。

下面来一个更通用的例子。

有一个需要传递两个参数的函数如下

通过 bind 方法将第一个参数值默认为 99,并返回一个新函数。这里因为没有 context 需要传递,所以第一个参数放 null,不能省略

注意这里只能是按照参数的先后顺序进行默认值传递,例如这里就不能跨过 agename 传递默认值。

总结

JS 中的 this 使用起来并不像其他 OOP 语言中的类似关键字方便(例如 python 中的 self),因为有指代丢失的问题出现,只能是在实际使用的时候多多练习,熟能生巧了。

参考链接


Javascript中this指向丢失原因及解决办法详解

发布者

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注