赋值运算

赋值运算

在我们写 js 代码的时候,使用最多的估计就是赋值运算符(Assignment operators)了,赋值运算符的作用将它右边操作数的值赋给它左边的操作数。最简单的赋值运算符是等于(=),它将右边的操作数值赋给左边的操作数。那么 x = y 就是将 y 的值赋给 x。

但是大家真的知道赋值运算的具体执行过程吗? 我们以var a = "1"这个赋值运算为例,当我们执行var a = "1"这段代码时,js引擎的处理过程是这样的:

  1. 找到变量 a 的内存地址,准备赋值
  2. 运算右侧表达式代码,得到表达式结果
  3. 将右侧表达式的结果存入 a 的内存地址指向的内存中
  4. 返回整个表达式的结果为右侧表达式的结果

这个有点像把大象装进冰箱里,先把冰箱门打开,然后把大象放进去,然后把冰箱门关上。

下面我们来详细讲讲这个过程:

第一步 为要赋值的数据准备内存地址

这个很好理解,我要给一个变量赋值,那么我就要开辟一块内存控件用来存变量的数据,以 var a = "1"举例,a 是一个全局变量,所以会先检查是否已经有全局变量 a 了,发现没有,则开辟一块内存,以便于后面存放 a 的值。

第二步 计算右侧的表达式

等号右侧不管写的是什么,最终都会计算出一个结果,而这个结果,就是最好要存入 a 的数据。

第三步 将右侧表达式的结果存放到 a 中

上面那个例子,最终右侧表达式计算出来的结果是字符串”1“,我们都知道 JS 中的字符串属于引用类型,所以会把 ”1“ 这个数据存放到堆里,然后把地址存到为 a 变量开辟的控件中。

第四步 返回右侧表达式的结算结果

这一点大家容易被大家忽略掉,js 的赋值运算是有返回值的,而返回结果就是右侧表达式的计算结果,而不是左侧变量的值,只不过这是时候左侧变量的值恰好等于右侧的计算结果罢了。

console.log(a = "1") // "1"

看一道题目

var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };

console.log(a.x);
console.log(b.x);

来试着算一下最终打印的结果是什么,结果是:

undefined{n: 2}

我们试着按照上面的过程论证一下:

第一行代码 给变量 a 复制

这一步很简单,按照上面说的过程,给 a 开辟一个空间,然后把右侧计算的数据的结果存放的地址存到里面。

第二行代码 把变量 a 赋值给变量 b

和第一步一样,先为 b 开辟一个空间用来存放数据,然后把 a 赋给 b,由于 a 里面存放的是一个地址,所以会是直接把地址存给 b。

第三行代码 继续赋值过程

第三行代码比较复杂,我们一步一步来。

首先是给 a.x 赋值,然后我们发现 a 里面现在只有一个属性 n,并没有 x,但是 js 是允许添加属性的,所以会先添加一个属性,并等待赋值。

然后继续计算右侧的表达式,发现右侧表达式仍然是一个赋值语句,没关注,那就执行这个赋值语句:a = { n: 2 }

首先,我们发现已经有 a 这个全局变量了,所以不需要再为它开辟空间了。开始计算等号右边,等号右边是一个对象,那么我们就在堆上存放这个对象,然后把地址存给 a,注意,这个时候 a 里面存放的地址已经是新对象的地址了。

这个时候 a = { n: 2 } 已经计算完毕了,然后还需要把返回值赋值给 a.x,我们上面已经讲过了,表达式的返回值就是等号右侧的计算结果,所以会把 { n: 2 }的地址赋值给 a.x,最后再作为返回值返回。

至此,整个赋值过程已经完毕。 通过上面的图我们可以很清晰得看出,最终 a 和 b 的结果为:

a = {n: 2}
b = {n: 1, x: {n: 2}}

所以打印的 a.x 和 b.x 的结果为 undefined{n: 2}