提问:
初次接触 js 的时候,被 this 小小地坑了一下。现在每次写代码的时候也总会想,这里的 this 与主流 OOP 里的 this 或 self 不一样,是可能变成任何东西的。
前段时间看王垠的日志,讲到 lisp 第一代被淘汰的 dynamic scoping 的时候 js 中了枪,大概就是讲这个 this 有点不靠谱。作为一个函数式语言爱好者,觉得 this 所引用的对象会随着调用方式不同而变化也是很违反直觉的。
this 不能作为闭包里的变量传递。直接调用一个函数,this 会是 global 对象,这一点也很奇怪。使得每次想要把一段比较长的代码的一部分放进一个闭包里再调用以减少名字空间污染,即(function() {...})()的写法,都会因为 this 指针会变化导致后面不得不写成 .call(this) 的形式。还是略冗余。 我觉得 this 指向 lexical scoping 上最近的一个对象。才是符合直觉的。
但是这样等于是限制了原型语言的能力,以致于实现委托或者做很多事情变得麻烦,有很多精彩的代码不能用了。
其实就一句话,部分的 dynamic scoping 有存在的价值吗?
贺师俊:
目前的几个答案倾向于认为this的设计是缺陷,不过好几个答案还不如楼主自己的提问的内容。
其实这个问题分好几个方面。
-
JavaScript的this在直接调用时会是global,这是不是个错误?
答:是设计错误。所以ES5的strict模式改为了返回undefined。实际上曾有提案建议变为外层的this(也就是题主认为符合直觉的方式),但是这样的改变也存在问题。比如对于采用call/apply调用的函数,其this的行为总是应该和以前一样吧?所以,这样半吊子的lexical scoping其实反而增加了程序员的理解负担。
但是在ES6中,arrow function中的this确实变成了lexical scoping的,且arrow function不允许call/apply重新绑定this。参见http://tc39wiki.calculist.org/es6/arrow-functions/ 。
-
部分的dynamic scoping是不是有价值?
答:在动态语言里是有用的。比如你看看groovy的closure设计,允许部分的dynamic scoping提供了很牛掰的DSL的能力。参考http://blackdragsview.blogspot.com/2012/10/owner-delegate-and-implicit-this-in.html 。注意,groovy里区分了this(只对应object的this)、owner(对应外层scope)和delegate(相当于JS通过call/apply应用的this),比单一的this要清晰了许多。不过也只有新语言能做这样的设计。
其实在传统JS中,with构造也能提供部门dynamic scoping的能力,因此也可以基于此构造DSL。只不过传统JS本身可用于DSL的能力实在不足(比如缺乏方法和属性调用的捕捉),且with构造本身也不是为此设计,导致实用价值不够抵消副作用的,因此实践中极少看到有如此的应用。
-
JS编程里是否应该使用this?
答:有些人(包括本题内的一些回答)因为this的一些问题所以宁可不用this。这当然总是可以的。因为支持closure,你总可以全都用closure做。只使用语言的一个子集是很常见的实践。不过因为你还要用其他人的库,一些库(尤其是UI组件库)的扩展方式可能依赖于this,所以用不用也不是全由你说了算的。另外,在性能极端重要的场合,对于类似的构造,使用this的方式会比完全closure的方式速度快、占用内存少。
说到底,JS的this虽然存在坑,但是JS本来坑就多,this真的不算最坑的那些。
我的观点:
记住三点就不会被坑了:
- 当函数作为对象的方法调用时,this就是该对象;
- 当函数作为单纯函数调用时,严格模式下,this是undefined,非严格模式下是全局对象,浏览器中就是window;
- this不是变量,嵌套函数中的this不会从外层继承this值!
也就是说,如果一个函数中含有this,那这个函数只能被当做对象的方法调用:
obj.fn();
fn.call(obj, ...);
fn.apply(obj, ...);
fn.bind(obj, ...);
这才是JS标准的this使用方式。