好了,我们 继续上次留下的问题 。
var x = 10;
var foo = {
x: 20,
bar: function () {
var x = 30;
return this.x;
}
};
console.log(
foo.bar(), // 1.
(foo.bar)(), // 2.
(foo.bar = foo.bar)(), // 3.
(foo.bar, foo.bar)() // 4.
);
我们考虑语句 3. 和上面的两个语句有什么不同
(foo.bar = foo.bar)(), // 3.
相比语句 2.,语句 3. 中的 Grouping Operator 中有赋值(「=」)语句。那么,我们首先得明白赋值语句干了啥,继续 参考对应的 ECMA 文档
11.13.1 Simple Assignment (= )
The production
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
is evaluated as follows:
1. Evaluate LeftHandSideExpression.
2. Evaluate AssignmentExpression.
3.Call GetValue(Result(2)).
4.Call PutValue(Result(1), Result(3)).
5.Return Result(3).
其中,最重要的步骤就是 PutValue,我们 继续刨根问底
8.7.2 PutValue(V, W)
1. If Type(V) is not Reference, throw a
ReferenceError exception.
2. Call GetBase(V).
3. If Result(2) is null, go to step 6.
4. Call the [[Put]] method of Result(2), passing GetPropertyName(V)
for the property name and W for the value.
5. Return.
6. Call the [[Put]] method for the global object, passing
GetPropertyName(V) for the property name and W for the value.
7. Return.
所以,我们根据上面的定义可以得知,语句返回的是 foo.bar 的函数值。因此,赋值操作符返回的是「值(Value)」而不是「引用(Reference)」。
因为函数体需要 this 值获取 x 属性的值,那么接下来我们考虑改函数时调用时的上下文作用域以及背后的具体流程。 尤其注意第七条规则
...
6. If Type(Result(1)) is Reference, Result(6) is GetBase( Result(1)).
Otherwise, Result(6) is null.
7. If Result(6) is an activation object, Result(7) is null. Otherwise,
Result(7) is the same as Result(6).
8. Call the [[Call]] method on Result(3), providing Result(7) as
the this value and providing the list Result(2) as the
argument values.
…
那么在这种情况下,GetBase
操作实际上返回的是 null
,因此此条语句函数执行的作用域为 global ,在浏览器中也就是 window 。
(foo.bar = foo.bar)()
那么,上面的语句中我们可以得知
- Grouping Operator 中的赋值语句返回的是 foot.bar 的函数值(「Value」)
- 该函数执行的上下文作用域为 window
那么,在该函数中执行获取 this.x 也就是获取 window.x 的值。因此,这条语句返回的就是 10 。如果还不理解,考虑下面的代码段
var x = 10;
(function() {
return this.x; // 这里会返回什么?
})();
如果理解了上面的语句的前因后果,那么题目中的语句 4. 就能举一反三给推导出来。首先我们来了解逗号运算符(「,」)的定义,我们就可以得之语句
(foo.bar, foo.bar)
返回的也是 foo.bar 的值「Value」而非引用「Reference」,那么接下来的事情其实就是和语句 3. 一样的了。因此,语句 4. 返回的液是 window.x 的值,也就是 10 。
总结下,那么上面的输出总的来说是
20 20 10 10
-- Split --
似乎目前为止,我们已经完全回答出了当初设定的问题。但恐怕会留下疑虑,就是传值「Value」和引用「Reference」之间到低有何不同、函数的作用域以及 this 的指向是否已经真正了解?
是的,这个题目已经完了,而我们的问题似乎还是没有怎麽搞清楚。OK,下次我们来详细讨论下这个问题…
-- To be continued --
测试下 typecho 升级到 0.8 是否完成
感觉讲的挺不错的,长见识了哈。
应该喝水了,辛苦~~
分析貌似有点不对
对于赋值操作来说,
3.Call GetValue(Result(2)).
4.Call PutValue(Result(1), Result(3)).
5.Return Result(3).
最后返回的是 Result(3), 因此需要关注的是 GetValue, 而不是 PutValue
对于 GetValue 来说,
If Type(V) is not Reference, return V.Call Getbase(V).If Result(2) is null, throw a ReferenceError exception.Call the [[Get]] method of Result(2), passing GetPropertyName( V) for the property name.Return Result(4).8.7.1 GetValue(V)
核心是第4步,第4步继续看 [[Get]]:
If O doesn't have a property with name P, go to step 4.Get the value of the property.Return Result(2).If the [[Prototype]] of O is null, return undefined.Call the [[Get]] method of [[Prototype]] with property name P.Return Result(5).8.6.2.1 [[Get]](P)
When the [[Get]] method of O is called with property name P, the following steps are taken:
这是一个递归调用,对于本例来说,关注点在第2步:value of the property
因此赋值操作返回的是 foo.bar 的值,这个值是一个function
If Type(Result(1)) is Reference, Result(6) is Getbase( Result(1)). Otherwise, Result(6) is null.If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6).Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values.函数的执行,可以参考:http://bclary.com/2004/11/07/#a-11.2.3 其中最关键的:
注意第 8 步里,是将 Result(7) 当作 this
追溯下去,能找到:http://bclary.com/2004/11/07/#a-10.2.3
关于 this 的描述:
The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object.
如果 this 不是一个 object,比如 null,则 this 为全局对象
到此,可以看出 (foo.bar = foo.bar) 返回的是一个函数值,这个函数执行时,传过去的 this 是 null,在真正执行时,指向了全局对象,也就是 window 对象
还有一个关键点是 Getbase, 不多说了,留给大家思考
看了两位的精彩解析,有点明白了。这个问题的关键在于this的指向。
x: 20, bar: function () { var x=30 return this.x; }var x = 10;
var foo = {
};
alert(foo.bar.apply());
alert(foo.bar.apply(foo));
alert(foo.bar.apply(foo.bar));
经过二位的详细解释总算明白点了。主要取决于this的指向问题。
alert(foo.bar=foo.bar.apply(foo));
你好,明城
我是Yibie,我想继续看看你更新的 Vimwiki 的笔记~
不过URL失效了
稍等,我整理下再放出来
有点晕晕的。 很少去想 赋值运算及逗号 返回的结果,原来要深入这么些内容。。
不过还是有收获的,很值得。呵呵。
你好,下面的代码,第一个显示20,第二个显示10,我有点不明白当foo引用bar那个函数时有没有把this替换为foo,假如替换的话那第二个应该显示20,假如没有替换的话那第一个应该显示10,但是正好相反 -_-
x: 20, bar: function () { var x = 30; alert(this.x); }var x = 10;
var foo = {
};
(foo.bar)();
setTimeout(foo.bar,1000);