这几天翻看了下被传的神乎其神的《你不知道的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 |
Related Issues not found
Please contact @CodeDaraW to initialize the comment