今天看到微博上大家在讨论一个 JavaScript 的小问题,问题虽小,还是有思考的价值。我看到不少人对其展开了讨论,有很多答案,也有很多有意思的观点。学 JavaScript 的人很多,但是学好真不容易,哪怕就是基础的部分。
先看一下下面的代码:
var value = 500; var obj = { value: 0, increment: function() { this.value++; var innerFunction = function() { alert(this.value); } innerFunction(); } } obj.increment();
好,在往下阅读以前,想一想,你觉得会打印的结果是多少?
————————————————-思考———————————————————
不少人都觉得是 1,因为 obj 对象的 value 初始值是 0,打印的 “this.value” 在 “this.value++” 以后就应该变成 1 了。
其实答案是 500,语句”innerFunction()”,正是使用了 Function Invocation Patterns 这种方式去调用一个方法,这个方法调用没有显式指定 “this”,那么如果你按照一些其它编程语言的思路去思考的话,没有显式指定 this,this 就应该是当前对象吧……那你就错了。这正是 JavaScript 设计中一个很糟糕的部分,或者说,是一个坑——在没有显式指定 this 的时候,这个 this 其实是一个 global 对象,在这里就是 window。
还有很多人解释说这是 “闭包” 的一个应用,有人又说不是,我来分析一下:闭包简单说就是 “包含自由变量的代码块”——也就是说,一个条件是代码块,这点毫无疑问满足;另一个条件是自由变量,这就有争议了,代码中的那个全局变量 value 算不算所谓的 “自由变量”,如果算,那就符合闭包的要求,如果不算(很多人认为 global 变量不能算作闭包的自由变量),那就不算闭包。
解决方法呢,其中的一个比较简单的办法就是持有一个当前正确 this 的引用,在这里保存到一个名为 “that” 的变量里,再让目标方法来调用:
var value = 500; var obj = { value: 0, increment: function() { var that = this; that.value++; var innerFunction = function() { alert(that.value); } innerFunction(); } } obj.increment();
这样结果就是 1 了。
还没完,如果我把程序改成这样(即把原来的 “this.value++” 改成 “value++”):
var value = 500; var obj = { value: 0, increment: function() { value++; var innerFunction = function() { alert(this.value); } innerFunction(); } } obj.increment();
结果又是多少呢?
————————————————-思考———————————————————
结果应该是 501。因为没有使用 this.value 的时候,“value++” 这一行的这个 value 是全局的 value。
那我把全局的 value 屏蔽掉吧:
//var value = 500; var obj = { value: 0, increment: function() { value++; var innerFunction = function() { alert(this.value); } innerFunction(); } } obj.increment();
这样的结果又是多少,这回总该是 1 了吧?
————————————————-思考———————————————————
不,这回会报错:Uncaught ReferenceError: value is not defined。因为在这样使用的时候,obj 对象的 value 对 increment 方法内部来说直接不可见。
其实,在 JavaScript 中,有这样几种函数调用模式:
- 方法调用:Fethod Invocation
- 函数调用:Function Invocation
- 构造器调用:Constructor Invocation
- Apply 调用:Apply Invocation
这些模式其实你都用过,如果你想知道更多,请阅读这篇文章,上面的例子代码也是从它里面引用来的。
如果你对上面说到的都理解了,那么看看这段代码,结果应该是多少?
var value = 500; var obj = function(){ var value = 0; return function() { value++; alert(value); } } obj()();
————————————————– 思考——————————————————–
结果应该是 1(其实这道题只是一个简单的闭包使用而已,并没有 Function Invocation Patterns,我挖了个坑让你跳,嘿嘿),你对了吗?
最后一题,把典型的闭包和 Function Invocation Patterns 结合起来,你要毫无压力地挺住啊:
var value = 500; var obj = function(){ var value = 0; var getValue = function(){ return this.value; }; return function() { value++; alert( getValue(value) ); } } obj()();
结果是多少呢?
————————————————– 思考——————————————————–
正确结果是 500,这次你对了吗?
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》
以我有限的 JS 经验来讲,这应该是属性和变量的区别。通过 this 调用的都是属性,所以会沿着 prototype 的路线往上搜索,最终找到了 global 变量;反之则是对变量的使用,沿着 closure 的路线往上搜索。
个人浅见,仅供参考。