该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2010-10-15
最后修改:2010-10-21
很喜欢蔡蔡 的这个标题,实际蔡蔡已经分析过了,这里借用了。或许有点标题党的意思。看完就知了。
一、引子var a = {n:1}; a.x = a = {n:2}; alert(a.x); // --> undefined
这是蔡蔡在看jQuery源码 时发现这种写法的。以上第二句 a.x = a = {n:2} 是一个连续赋值表达式。这个连续赋值表达式在引擎内部究竟发生了什么?是如何解释的?
二、猜想猜想1:从左到右赋值,a.x 先赋值为{n:2},但随后 a 赋值为 {n:2},即 a 被重写了,值为 {n:2},新的 a 没有 x属性,因此为undefined。步骤如下
1, a.x = {n:2};
这种解释得出的结果与实际运行结果一致,貌似是对的。注意猜想1中 a.x 被赋值过。
猜想2:从右到左赋值,a 先赋值为{n:2},a.x 发现 a 被重写后(之前a是{a:1}),a.x = {n:2} 引擎限制a.x赋值,忽略了。步骤如下:
1, a = {n:2};
等价于 a.x = (a = {n:2}),即执行了第一步,这样也能解释a.x为undefined了。注意猜想2中a.x压根没被赋值过。
三、证明上面两种猜想相信多数人都有,群里讨论呆呆认为是猜想1, 我认为是猜想2。其实都错了。我忽略了引用的关系。如下,加一个变量b,指向a。 var a = {n:1}; var b = a; // 持有a,以回查 a.x = a = {n:2}; alert(a.x);// --> undefined alert(b.x);// --> [object Object]
发现a.x仍然是undefined,神奇的是 b.x 并未被赋值过(比如:b.x={n:2}),却变成了[object Object]。b 是指向 a({n:1})的,只有a.x = {n:2}执行了才说明b是有x属性的。实际执行过程:从右到左,a 先被赋值为{n:2},随后a.x被赋值{n:2}。
1, a = {n:2};
与猜想2的区别在于a.x 被赋值了,猜想2中并未赋值。最重要的区别,第一步 a = {n:2} 的 a 指向的是新的对象{n:2} , 第二步 a.x = {n:2} 中的 a 是 {a:1}。即在这个连等语句 a.x = a = {n:2};
a.x 中的a指向的是 {n:1},a 指向的是 {n:2}。如下图
四:解惑这篇写完,或许部分人看完还是晕晕的。因为里面的文字描述实在是绕口。最初我在理解这个连等赋值语句时 var a = {n:1}; a.x = a = {n:2};
认为引擎会限制a.x的重写(a被重写后),实际却不是这样的。指向的对象已经不同了。引擎也没有限制a.x={n:2}的重写。
五:结束呵,以另一个连续赋值题结束。fun执行后,这里的 变量 b 溢出到fun外成为了全局变量。想到了吗? function fun(){ var a = b = 5; } fun(); alert(typeof a); // --> undefined alert(typeof b); // --> number
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-10-15
不是标题党。恐怕我写20年也不会这么赋值。
|
|
返回顶楼 | |
发表时间:2010-10-15
soni 写道 不是标题党。恐怕我写20年也不会这么赋值。
soni 所言极是,这里仅拿来讨论。 |
|
返回顶楼 | |
发表时间:2010-10-15
最后修改:2010-10-15
这个现象还真出乎意料之外,不看结果的话肯定以为是猜想2,但是a.x会被赋值。感觉像是js引擎为了效率,在执行
a.x = a = {n:2}; 1、a = {n:2}; 2、a.x = {n:2};第二步时没有重新判断a的指向是否已经改变,直接按之前的指向处理的。 ps:随便想请问LZ个问题,这么赋值的写法有何好处?赋值过后,a的指向为{n:2},之前的{n:1}这个具体对象也没有被变量引用了,它的x属性为多少,好像也没有意义了。如果后续要用到a.x还需要重新赋值,否则还是undefined,这为什么要搞个连续赋值呢? |
|
返回顶楼 | |
发表时间:2010-10-15
最后修改:2010-10-15
为了弄明白这个问题,专门去看了ECMAScript(3rd)文档,谈一谈我的理解
1、引用(Reference)与GetValue & PutValue 引用 A Reference is a reference to a property of an object. A Reference consists of two components, the base object and the property name.
“引用”是引用某个对象的一个属性(可能这个对象并没有这个属性),一个引用含“根对象”与“属性名”两个成员。 后面以“(根对象,属性名)”来表达一个引用 引用 GetValue (V)
1. If Type(V) is not Reference, return V. 2. Call GetBase(V). 3. If Result(2) is null, throw a ReferenceError exception. 4. Call the [[Get]] method of Result(2), passing GetPropertyName(V) for the property name. 5. Return Result(4). GetValue,即取值操作,返回的是确定的值,而不是引用。(可以理解为变量与变量的值,或指针与指针指向的对象) 引用 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. PutValue操作只对引用生效,在ECMAScript的描述中,修改对象的属性都是通过Refrence + PutValue进行的 (ECMAScript是为了便于表达而引入Reference这个类型,实际上JS语言中并无此类型。The internal Reference type is not a language data type. It is defined by this specification purely for expository purposes.) 2、成员表达式(MemberExpression)解释过程 引用 The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
1. Evaluate MemberExpression. 53 2. Call GetValue(Result(1)). 3. Evaluate Expression. 4. Call GetValue(Result(3)). 5. Call ToObject(Result(2)). 6. Call ToString(Result(4)). 7. Return a value of type Reference whose base object is Result(5) and whose property name is Result(6). 着重看第7步:a value of type Reference 3、赋值表达式解析 引用 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). 这里可以看到左侧得出的是引用,右侧调用GetValue取得的是确定值。 那么开始分析a.b = a = {n:2}这个表达式,先假设{n:1}这个对象为OBJ1,{n:2}为OBJ2,全局为GLOBAL。 它的解析如下: a.b = Expression1 Expression1为另一个赋值表达式: a = {} 首先计算a.b = Expression1,按(3)中赋值表达式运行步骤 step1先得到引用(OBJ1, "b") step2解析Expression1{ Expression1解析 step1得到引用(GLOBAL, "a") step2得到一个对象OBJ2 step3取值,仍是OBJ2 step4将引用(GLOBAL, "a")赋值为step3结果 step5返回OBJ2 } step3取值,结果同样为OBJ2 step4将(OBJ1, "b")赋值为OBJ2 step5返回OBJ2 最终结果: OBJ1: {n:1, b:OBJ2} OBJ2: {n:2} a : OBJ2 PS: 我们常说赋值运算是从右至左,是指右边先结合 所以a.b = a = {n:2}解析为了a.b = ( a = {n:2}),而不会解析为(a.b = a) = {n:2} 如果理解为右边先运算就会有误解了,虽然右边先赋值成功。 另外,测试了Java中同样的案例,发现结果也一样,赋值语句左侧先解析出来。 估计C++中也一样吧…… class O{ String id; O(String id){ this.id = id; } O a; } O o = new O("1"); O t = o; o.a = o = new O("2"); System.out.println(o.id + o.a + t.a); // ---------- console ---------- 2nullcom.****$O@16f0472 |
|
返回顶楼 | |
发表时间:2010-10-15
这个不错,我也一直没注意到这一点
|
|
返回顶楼 | |
发表时间:2010-10-15
看了两遍才明白。实际上最后alert(a.x);的a是{n:2},a.x=a的左边的a是{n:1}。
也就是说最后赋值的那个a,没有地方引用,所以alert(a.x);才会是未定义 |
|
返回顶楼 | |
发表时间:2010-10-15
最后修改:2010-10-15
|
|
返回顶楼 | |
发表时间:2010-10-16
JS不在行,用JAVA来看待这问题。
这分明就是一个JAVA基础问题。 搞清楚对象的引用及赋值顺序,是先右侧往左计算,一切不是很简单?? 这就是基础。 |
|
返回顶楼 | |
发表时间:2010-10-16
这个问题确实没有仔细思考过。
|
|
返回顶楼 | |