JavaScript中引用相等性的思考

279 阅读4分钟

读前声明

这篇文章以一个初学者的视角去写的,所以部分地方会显得啰嗦,当然也有一些需要了解的前置条件,javascript 里的数据类型。

虽然可能已经使用 js 多年,但也有可能有些点是约定俗成,彷佛天然如此。

这个系列的文章是我打算重新构建心智模型的第一篇,出发点是为了拨开心中的迷雾,当你真的塑造了正确的心智,一些问题都会豁然开朗。

当然更多是为了自己记录所用,如果行文缺失了一些必要的上下文,欢迎在评论告诉我。🙇

[a, b] = [b, a]的实现原理

在一个技术群里看到一个同学问了这样一个问题。 这个问题不是一个新颖的问题了,有些同学可能已经知道,有些同学也可能还未思考过。 如果你还没有不知道答案,我建议你可以先花几分钟思考一下。

拆解问题

首先这是一条语句,它由三个部分组成,左侧的是一个数组,中间是一个 = 运算符,右侧也是一个数组。

我们来看下 = 运算符 它代表了赋值操作,左侧必须为变量,右侧必须为一个值,因为表达式都需要返回值,所以右侧也可以是一个表达式,但无论如何,最终它必须是一个值。

上面提到了两个概念,一个是变量,一个是值。

变量的定义大家可以谷歌一下官方定义,这里就不再赘述,但什么是值? 数字 1 是值吗?字符串 hello world 是值吗?对象 { a: 1 } 是值吗? 这些都称为值,众所周知他们之间唯一的区别是,对象是引用类型,其他的都是基础类型。

他们的差别不言而喻,就像我们每天都会用到他们

let a = 1;
let b = a;
a = 2;

// 变量 a 的值变成了 2,b 仍然为 1

let obj = { a: 1 };
let anotherObj = obj;
obj.a = 2;

// obj 和 anotherObj 都变成了 2

为什么会出现这样的情况,大家脱口而出,a 是原始类型, obj 是引用类型,巴拉巴拉... 上面这段话当然本身是对的,大家有些时候也是习以为常的认为原始类型和引用类型应该存在不一样的心智模型,因为他们的行为不一致,这是很容易留下的印象。就像写代码进了不同的 if else 分支。

接下来让我们用画图的方式来讨论下这几种情况

原始类型图解.png 心智1还是心智2更符合你脑海里的印象?我们接下来来证伪那种心智模型是错误的? 我们把代码扩展一下变成了以下的样子

let a = 1;

function doubleNumber(num) {
	num = num * 2;
	return num;
}

doubleNumber(a);

console.log(a); // a 等于多少呢?

函数调用图解.png 这里实际执行一下函数大家可以很明显的得到心智1是错误的。至此我们也能总结出两个很重要的结论。区别很小,但很微妙。

  • 基础类型变量的赋值只有再分配,没有变更。
  • 变量只会指向值,而不会指向另一个变量。

讨论完了基础类型,我们来看看引用类型。

对象类型图解.png

引用类型的相等性与基础类型的分配在这种心智下没有分叉,只是扩展了。对象类型的属性复制可以修改,仅此而已。

终于把这个论证完了 🎉

回到问题本身

左边的数组明明是个值,但 = 左侧必须是个变量啊,这里面难道是编译器的魔法?其实不然,他其实只是把 let 省略了。 完整的语句应该是 let [a, b] = [b, a]; 至此一切问题都解释完了。

写在最后

上面的很多观点和论述都来自于我最近看的 Dan 的 justjavascript。我真的很爱 Dan 对于基础概念的拆解。他总是能将复杂拆解的如此清晰和通顺。很推荐大家去购买原版的书。记得加上中国区折扣,大概80多块。

参考链接

A Visual Guide to References in JavaScript

Just JavaScript