这几天翻看了下被传的神乎其神的《你不知道的JS》这本书,其实以前就看过一次,不过当时的level并不高,而且感觉这本书讲的有点绕,所以看了一点就没坚持下去。
这次翻看感觉还是比较轻松的,有些地方写的很好,有的地方还是感觉讲的有点绕(可能是翻译的问题),但总的来说这本书还是很不错的,基本都是JS中有坑、新手难以理解的点,简直就是《JS:The Bad Parts》(哈,开个玩笑~)。
这个小记不是打算记录书中内容的笔记,而是想补充纠正书中的讲的不完善的地方。
this的问题
在第二部分2.2.2隐式绑定
一节中,提到了setTimeout
的传入函数this的问题,书里说传入回调函数在执行的时候context
为全局对象,所以this
指向了全局对象:
1 | setTimeout(function() { |
在Chrome56中测试上面的一段代码确实输出为window
全局对象,符合书中的描述,然而如果你在Node.js中测试这段代码你会发现输出是这样的:
1 | ➜ Desktop node -v |
What? 输出的是一个Timeout实例对象!
打开node的源码,在node\timers.js
中有着setTimeout
的实现,这里大概的讲一下,有兴趣的可以自己再去看看代码:
有一个Timeout
构造函数,用来构造定时器对象,用一个链表存着所有的Timeout
的实例对象,也就是每次执行暴露出来的setTimeout
都会在链表中插入一个Timeout
实例timer
。下面是Timeout
构造函数的代码:
1 | function Timeout(after, callback, args) { |
定时的部分TimerWrap
则由C++来做处理,这里不是现在我们关注的关键点也脱离了JS的范畴暂且不细谈,在定时结束后,通过ontimeout
函数来处理timer
对象:
1 | function ontimeout(timer) { |
ontimeout
函数中正藏着this
指向问题的真相:callback.call(timer, ...)
。
在JS中,setTimeout
应该是属于Event Loop
的Macro Tasks
,与I/O
等Tasks
同等级,在浏览器中有Web APIs
规范来定义这部分的实现,node没有(或者是我没找到,还请告知)。但我大概翻了下Web APIs
的规范,也没有找到对this
context
的规定。虽然不理解为什么node这样做,但是好歹也找出了与Chrome浏览器不同的原因。
所以,关于setTimeout
传入函数的this
,我的建议是即使你写的代码只会在浏览器里运行,也最好不要依赖this
会自动绑定到全局对象上去,而是应该手动借助bind
绑定。当然使用ES6的箭头函数是没什么问题的,因为没有创建新的context
,this
都会毫无疑问的绑定在当前的context
上:
1 | let that = this |