先来看下面的例子输出什么?
|
|
1处输出[1, 2, 3, 4];2处输出[4, 5, 6, 7];3处输出[1, 2, 3, 4],为什么不是[4, 5, 6, 7]?
我们知道在最新的ECMAScript标准中定义了7种数据类型,其中,
6种原始类型:Boolean,Null,Undefined,Number,String,Symbol(ECMAScript 6新定义);
和Object。
而Javascript対值的引用和赋值或者传递完全由值的类型来决定。
对于简单值,总是通过值复制的方式来赋值或传递,包括null、undefined、字符串、数字、布尔以及ES6中的symbol;对于复合值总是通过引用复制的方式赋值或传递,包括对象(数组或封装的对象)、函数。
我们再来看上面的例子,变量arr是数组,属于复合值类型,它的赋值和传递都是通过引用的方式,来看看执行foo函数的时候发生了什么。
|
|
从这个例子中我们可以得出:
- 运算符
=就是创建或修改变量在内存中的指向。 - 初始化变量时为创建,重新赋值即为修改。
- 我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
下面我们再尝试从内存占用的角度来解释。
首先看一个简单的例子:
|
|
前两句代码的内存区情况如下:
| 栈 | 堆 |
|---|---|
| o1, o2 | [[Object]] (指向对象m,m为1) |
接下来:
- 创建变量o1指向对象{m: 1};
- 创建变量o2指向对象{m: 1};
- 修改o1重新指向常量2;
现在再来看看内存区情况:
| 栈 | 堆 | 常量区 |
|---|---|---|
| o1 | 2 | |
| o2 | [[Object]] (指向对象m,m为1) |
所以o2从头到尾都是指向对象{m: 1}。
修改上面一个例子:
|
|
前两句代码执行完o1, o2, m在内存中的分布:
| 栈 | 堆 | 常量区 |
|---|---|---|
| o1, o2 | [[Object]] (指向对象m,m为1) | |
| m | 1 |
执行完o1.m = 2后:
| 栈 | 堆 | 常量 |
|---|---|---|
| o1, o2 | [[Object]] (指向对象m,m为2) | |
| m | 2 |
执行o1.m = 2,相当于通过o1的间接引用改变了m的值,那么所有原来指向m的引用的值都改变了,在这里就是o2所引用的值也改变了。o1和o2从始至终都未改变过其引用,只是m改变了。
根据上面的理论知识,就会想到工作中为了分离变量间的引用所用的操作:
|
|
总结:
简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合值(对象等)通过引用复制来赋值 / 传递。
JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值。