带你迈过js赋值运算的坑(附带面试题)
面试题
话不多说,我们先上面试题:
var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
console.log(a.x);
console.log(b.x);
初见这道面试题,我相信所有的小伙伴们都可以看懂,但是解答(说出答案还要阐述过程)出来可能会有些费劲。我们先来看看答案:
undefined
{ n: 2 }
我们先来分析,这道题看上去是考察的 引用赋值 或者 循环引用 什么的,实际上不是。这道题就是考察的赋值运算符,所以我们先要搞清楚赋值运算符是怎么算的。
赋值运算符的计算
我们众所周知的是,赋值运算符 =
用于将右侧表达式的值赋给左侧的变量。这其实是一个笼统的概括,并不准确,正确的说法应该分为四个步骤:
- 找到变量
a
的内存地址,准备赋值。 - 运算
=
右侧表达式的代码,得到要赋值的数据。 - 将右侧运算的数据放入之前找到的内存地址
- 返回整个表达式的结果,即左侧变量的新值。
前面三步都很好理解的,那第四步怎么理解呢?来看看这个简单的代码:
var a
console.log(a = 10); // 10
这段代码输出结果为10, 有些小伙伴们可能会认为我们在输出变量 a
,其实并不是,我们输出的是 a = 10
这个表达式的返回结果。好了清楚了这四步之后,我们再来看这个面试题就清晰多了。
解题思路
对于 var a = { n: 1 }
和 var b = a
不用多说什么,会形成这样一个内存的结构:
接下来看关键的一步 a.x = a = { n: 2 }
连续赋值,我们先从总体来看,这其实就是一个赋值运算,将右侧表达式的值赋给左侧的变量,这样我们就能套用上面讲的那四步来做了。
首先找到 a.x
的内存空间,我们发现这里好像不存在 a.x
的内存空间,但 js 是允许 动态添加对象属性 ,所以我们直接创建一块内存空间就好了。
接下来第二步:运算 =
右侧表达式的代码,这时我们会发现,右侧表达式还是一个赋值,所以我们接着套用那四步,找到 a
的内存空间,算出右侧表达式,结果为一个对象,第三步,将对象的地址值放进去。所以内存结构就为:
这时候注意,别忘了还有第四步,返回整个表达式的结构。在这里,整个表达式的结果是什么?就是 { n: 2 }
这个对象的地址。所以它会将这个地址赋值给表达式左边的 a.x
。最终这个内存结构为:
看着这个图,我们再来判断 console.log(a.x)
console.log(b.x)
就很简单了。
总结
恭喜你又解决了一道面试题,看完面试题之后,我们可以拓展了解下 js 中的赋值运算。赋值运算符是=
,简单来说赋值运算是将一个值赋给一个变量的过程,以下是一些基本的赋值运算示例:
- 简单赋值:
let a = 10; // 将数字10赋给变量a
let b = "Ywis"; // 将字符串"Ywis"赋给变量b
- 复合赋值:
- 加等于(
+=
) - 减等于(
-=
) - 乘等于(
*=
) - 除等于(
/=
) - 模等于(
%=
) - 位与等于(
&=
) - 位或等于(
|=
) - 位异或等于(
^=
) - 左移等于(
<<=
) - 右移等于(
>>=
) - 无符号右移等于(
>>>=
)
let x = 5;
x += 3; // x现在是8
x -= 1; // x现在是7
x *= 2; // x现在是14
x /= 4; // x现在是3.5
x %= 2; // x现在是1
......
- 并行赋值:可以在一行中对多个变量进行赋值。
let x, y, z
x = y = z = 10 // x, y, z都被赋值为10
- 解构赋值:从数组或对象中提取值并赋给变量。
let [a, b] = [1, 2]; // a是1,b是2
let {name, age} = {name: "Ywis", age: 18}; // name是"Ywis",age是18
- 默认值赋值:当尝试赋值的变量未定义或值为
undefined
时,可以使用默认值。
let a = 10; let b;
let c = a ?? 20; // c为10,因为a不是null或undefined
let d = b ?? 20; // d为20,因为b为undefined
最后祝你也祝我在今后日子里能够登高望远,心向彼岸。
转载自:https://juejin.cn/post/7370275556904468489