Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

解析一道JS面试题 #12

Open
Pomelo1213 opened this issue Feb 13, 2018 · 0 comments
Open

解析一道JS面试题 #12

Pomelo1213 opened this issue Feb 13, 2018 · 0 comments
Labels

Comments

@Pomelo1213
Copy link
Owner

更新(1/23/2018)

写在前面,本文让读者产生了误会。有这样一些原因:

  1. JS为何能取到地址值。
  2. a.x = a未解释清楚。
  3. .运算符是否会对赋值运算有所干扰。

首先:本文用addr只是一个代称,表达的是该地址对应的那块内存。
关于2.3点就是本次更新的原因

  • a.x = a赋值表达式先确定左值(可以这样理解,如果不确定我要去的地方,取到值又有什么用呢?左边的值在执行赋值之前就已经确定了),然后再将右边表达式的返回值给到左值。那么a.x = a = {n : 2}就是从左往右先确定a.x再确定a,然后将返回值从右往左依次赋值给左边。
  • .运算符会对赋值运算符有优先级干扰吗?(会因为a.x优先级高于a = {n : 2}而先执行a.x = a吗?)下面我来尝试一下:
将题目改写成:
var a = {n: 1};
var b = a;
a.x = a = a.y = {n: 2};
//改写成这样,那么怎么确定优先级呢?

现在按照我文章的思路来:

  1. 先确定所有左边的地址值(再次强调指的是对应的内存!):addr(a.x) = addr(a) = addr(a.y) = addr({n : 2})
  • 我们假设:
addr(a) = 0x100, 
addr(a.x) = 0x101, 
addr(a.y) = 0x102,
addr({n : 2}) = 0x888,
addr({n : 1}) = 0x999

2.(从右往左)将右边的值赋值给左值,然后将其作为该赋值表达式的值返回:

1. 先执行:addr(a.y) = addr({n : 2}),将{n : 2}的地址值存放在addr(a.y)这个地址值对应的内存!中。
(别用箭头指,容易混淆,简单的当做赋值就好了。本文就是犯得这个错,导致没有说清楚。)
2. 然后将addr(a.y) = addr({n : 2})的右值作为该表达式的返回值N返回,在这里会作为下一个赋值表达式的右值。
3. 接着执行addr(a) = N,同上返回N。
4. 接着执行addr(a.x) = N,返回N。
然后我们执行
console.log(a); // {n : 2}
console.log(a.x); // undefined
console.log(a.y); // undefined
console.log(b.x); // {n : 2}
console.log(b.y); // {n : 2}

不对呀,为什么给a.x和a.y的地址值对应的内存赋值了,但却没有东西,你是不是讲错了?

因为此时a(0x100)这块内存存的是对{n : 2}的引用,它会去0x888这块内存中找,这样肯定找不着x和y,因为他们在0x999内存中。

而b在一开始var b = a的时候,就将a(0x100)这块内存中存的(0x999)拷贝过来。而y和x就在0x999对应的内存下。所以b找的到。

希望本次更新对有困惑朋友有所帮助!(也许是我讲复杂了,希望多多提问。)



有这样一道面试题

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); //  undefined
alert(b.x); //  [object, Object]

一开始没有太好思路,或者说是没有想明白,经过一番折腾,算是整理清楚了思路,接下来会一一讲明白,希望能对其他人有所帮助。

开始之前,需要清楚赋值表达式是怎么执行的。首先先明白什么是什么是右结合性和什么是赋值表达式:


右结合性

赋值运算符是右结合性的,如果不知道,就请记住啦!
形如:

A = B = C = D

等价于

A = (B = (C = D))


赋值表达式

A = B这就是一个赋值表达式,并且一个赋值表达式存在一个左值和一个右值,这可不是胡编乱造的,咱们说话有理有据:
引用链接(11.13.1 Simple Assignment ( = ) )

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let rref be the result of evaluating AssignmentExpression.
  3. Let rval be GetValue(rref).
  4. Throw a SyntaxError exception if the following conditions are all true:
  • Type(lref) is Reference is true
  • IsStrictReference(lref) is true
  • Type(GetBase(lref)) is Environment Record
  • GetReferencedName(lref) is either "eval" or "arguments"
  1. Call PutValue(lref, rval).
  2. Return rval.

翻译过来:

大体来说 赋值表达式:左边的表达式 = 赋值表达式 具体评判的步骤如下:

  1. 将比左边的表达式的值称为'lref'
  2. 将右边赋值表达式的值称为'rref'
  3. 将'rval' 作为GetValue(rref)'的返回值
  4. 如果下列情况为true就会报错
  • balala~~~~
  1. 调用 PutValue(lref, rval)
  2. 返回'rval'

由上面可以清楚的知道表达式运算的流程:

  1. 先计算左边的表达式(很重要,先计算左边的!这里计算的是左边表达式在内存中的地址值)
  2. 在计算右边的表达式(先左后右,这里计算的是右边的值,这里的值就是值,并不特指地址!若是对象则为地址值)
  3. 计算右边的值

(等等,这里有疑问,为什么运算两遍右边?第一遍是计算右边表达式的(+ — * /)所得到的值,第二遍是返回这个结果值,也就是每个表达式都有返回值!)

  1. 这里balala~~~
  2. 调用函数PutValue,这个做的才是给值,就是将右边的返回值rval放进左边的lref对应的地址值(也就是对应的内存)
  3. 然后返回这个表达式的值,也就是rval(这里再次说明表达式有返回值,而且还可以看出这个值是GetValue计算出的右边的值,说白了就是等号右边的值)

如果还没理解的话,没关系,往下看,我会画图帮助理解。


有了以上两个知识点,下面来分析一下题目。

var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); //  undefined
alert(b.x); //  [object, Object]

真正难以理解的是在第三句代码a.x = a = {n: 2}
下面咱们开工吧!


  • 第一、二句代码执行之后,内存图如下:

  • 第三句代码先进行改写

a.x = a = {n: 2};
//先右结合性
a.x = (a = {n : 2})
//在计算等号左边的值
addr(a.x) = (addr(a) = {n : 2})
//在计算等号右边的值
addr(a.x) = ( addr(a) = value( {n : 2} ) )

如图:


如上图来进行运算
addr(a.x) = ( addr(a) = value( {n : 2} ) )

  1. 先计算所有等号左边的值:addr(a.x)的值为:0x8889、addr(a)的值为:0x0001
    (0x8889 <-- (0x0001 <-- (0x9999)))
  2. 在计算{n : 2}的值为:0x9999(因为{n : 2}是一个对象,所以在这里计算得到的是地址值)
  3. 将0x9999作为当前{n : 2}的返回值(也就是当前表达式的右边的值)
  4. 将返回值赋值给0x0001(对应的内存)
  5. 返回( addr(a) = value( {n : 2} ) )的值:0x9999
  6. 将返回值赋值给0x8889(对应内存)
  7. 返回addr(a.x) = ( addr(a) = value( {n : 2} ) )的值: 0x9999

一开始找到当前表达式左右左边的值,也就是他们地址值。然后依次将右值逐个放到对应的内存!

最后因为a的地址是指向0x9999的,且其不存x这个属性,故返回undefined。而b的地址指向里面存在一个保存{n:2}的地址的x属性,故返回的是对象。

@Pomelo1213 Pomelo1213 added JavaScript JavaScript 学到了知识 learning labels Aug 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant