mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-16 17:21:37 +00:00
114 lines
4.3 KiB
Markdown
114 lines
4.3 KiB
Markdown
## 值与引用
|
||
|
||
在许多编程语言中,赋值和参数传递可以通过值赋值(value-copy)或者引用复制(reference-copy)来完成。
|
||
|
||
例如在 C 中,传递一个引用值可以通过声明类似于这样的`int* num`参数来按引用传递,如果传递一个变量 x,那么`num`就是指向 x 的引用。引用就是指向变量的指针,如果不声明为引用的话,参数值总是通过值来传递的。即便是复杂的对象值也是如此(C++)。
|
||
|
||
与 C/C++ 不同的是,JavaScript 没有指针这一概念,值的传递方式完全由值来决定。JavaScript 中变量不可能成为指向另一个变量的指针。
|
||
|
||
基本类型(简单类型)的值总是通过以值复制的方式来赋值/传递,这些类型包括:`null`、`undefined`、字符串、数字、布尔和`symbol`。
|
||
|
||
而复合值,也就是对象(以及对象的子类型,数组、包装对象等)和函数,则总是以引用复制的方式来赋值/传递。
|
||
|
||
在了解了基本类型和引用类型的值之后,先来看下他们传递有什么不同:
|
||
|
||
基本类型:
|
||
|
||
由于基本类型是按值传递的,所以 a 与 b 是分别在内存中两处保存了自己的值。a 有在内存中有自己的空间,b 也有自己单独的空间,他们互不影响。
|
||
|
||
```js
|
||
let a = 123;
|
||
let b = a; // 按值进行传递
|
||
|
||
a += 1; // 修改 a
|
||
console.log(a); // 124
|
||
console.log(b); // 123 b 不受影响
|
||
```
|
||
|
||
引用类型:
|
||
|
||
引用值的情况正好相反,所谓按引用传递,就是`arr1`与`arr2`指向的是内存中的同一块地址,**修改**任何一个变量的值,都会立即反应到另一个变量上。因为他们对应的是同一块内存。
|
||
|
||
```js
|
||
let arr1 = [1, 2, 3];
|
||
let arr2 = arr1;
|
||
|
||
arr1.push(99); // 修改 aar1
|
||
console.log(arr1); // [ 1, 2, 3, 99 ]
|
||
console.log(arr2); // [ 1, 2, 3, 99 ]
|
||
```
|
||
|
||
但是引用值还有个特性容易犯错,那就是修改:
|
||
|
||
这里咋一看是修改了`arr1`的值,但为什么没有反应到`arr2`身上呢?说好的一起变呢?
|
||
|
||
仔细回想一下引用值的定义,他们是因为指向同一块内存地址,所以修改这段地址中的值时,就会同时反应在两个变量上。但是这里的`arr1 = { name: 'xfy' }`并不是修改内存中的值,而是修改了`arr1`的指向,使其指向一块新的内存地址。而`arr2`还是指向以前的地址,所以`arr2`没有改变。
|
||
|
||
```js
|
||
let arr1 = [1, 2, 3];
|
||
let arr2 = arr1;
|
||
|
||
arr1 = { name: 'xfy' };
|
||
console.log(arr1); // { name: 'xfy' }
|
||
console.log(arr2); // [ 1, 2, 3, 99 ]
|
||
```
|
||
|
||
### 使用函数修改值
|
||
|
||
由于上述值的传递特性,这也会导致在传递给函数参数时发生个中问题。
|
||
|
||
修改引用值:
|
||
|
||
```js
|
||
function changeValue(value) {
|
||
// 按引用传递,可以直接修改
|
||
value.push(99);
|
||
// 重新赋值,并没有修改内存中的值
|
||
value = { name: 'xfy' };
|
||
}
|
||
|
||
let arr = [1, 2, 3];
|
||
changeValue(arr);
|
||
console.log(arr); // [ 1, 2, 3, 99 ]
|
||
```
|
||
|
||
修改基本值:
|
||
|
||
```js
|
||
function changeValue(value) {
|
||
// 按值传递,value 获取到 num 的值
|
||
// 但是他们分别保存在两个内存中
|
||
value++;
|
||
}
|
||
|
||
let num = 123;
|
||
changeValue(num);
|
||
console.log(num); // 123
|
||
```
|
||
|
||
这也就是为什么在 Vue3 的 Composition API 中使用 ref 封装的响应式变量必须要有`.value`属性。
|
||
|
||

|
||
|
||
## 强制类型转换
|
||
|
||
将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。
|
||
|
||
也可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
|
||
|
||
### 抽象值操作
|
||
|
||
在了解强制类型之前,我们需要先掌握类型之间转换的基本规则。ES5 规范第 9 节定义了一些“抽象操作”和转换规则。
|
||
|
||
#### ToString
|
||
|
||
ToString 负责非字符串到字符串的强制类型转换操作。
|
||
|
||
基本值转换为字符串的规则为直接添加双引号:null 转换为`"null"`,true 转换为`"true"`等。数字也遵循这种规则,不过极大或极小的数字使用指数形式。
|
||
|
||
```js
|
||
(1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000).toString()
|
||
// "1.07e+21"
|
||
```
|
||
|