数据的类型
ECMAScript 有 6 种简单数据类型(也称为原始类型):Undefined、Null、Boolean、Number、String 和 Symbol。
Symbol(符号)是 ES6 新增的。
变量的类型
JS 的变量具有动态数据类型 → 变量的类型由变量值决定。
可以使用 typeof
操作符查看变量的数据类型,会返回下列字符串之一:
-
"undefined"
表示值未定义 -
"boolean"
表示值为布尔值 -
"string"
表示值为字符串 -
"number"
表示值为数值 -
"object"
表示值为对象(不是函数)或null
或正则表达式 -
"function"
表示值为函数 -
"symbol"
表示值为符号
const a = 123;
console.log(typeof a); // "number"
注意:typeof
在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用 typeof null
返回的是 "object"
。这是因为特殊值 null
被认为是一个对空对象的引用。
严格来讲,函数在 ECMAScript 中也属于一种对象。因为函数有特殊属性
[[Call]]
,为此 就有必要通过typeof
操作符来区分函数和其他对象。
函数不仅是对象,还可以拥有属性。函数对象的 length 属性是其声明的参数的个数。
可以使用 Object 的原型对象上挂载的 toString 方法查看变量的类型:(需要改变该方法的 this 指向)
const typeOf = function (obj) {
console.log(Object.prototype.toString.call(obj).slice(8, -1).toLowerCase());
};
typeOf('superman'); // "string"
typeOf([]); // "array"
typeOf(new Date()); // "date"
typeOf(null); // "null"
typeOf(true); // "boolean"
typeOf(() => {}); // "function"
基本数据类型
基本类型的数据,保存在栈内存中。
Number 类型
console.log(typeof NaN); // "number"
String 类型
使用双引号 / 单引号 / 反引号包裹的值。
const str1 = '1';
const str2 = `1`;
console.log(typeof str1); // "string"
注意:以某种引号作为字符串的开头,就必须仍然以该种引号作为字符串的结尾。
Boolean 类型
Boolean 类型只有 2 个字面值:true
和 false
。
console.log(typeof true); // "boolean"
Undefined 类型
Undefined 类型只有 1 个值,就是特殊值 undefined
。当使用 var
或 let
声明了变量但没有初始化时,就相当于给该变量赋予了 undefined
值:
let message;
console.log(message == undefined); // true
一般来说,永远不用显式地给某个变量赋值为
undefined
。字面量undefined
用于明确区分空对象指针(null
)和未初始化变量。
对未初始化的变量调用 typeof
会返回 "undefined"
,对未声明的变量调用 typeof
也会返回 "undefined"
,这就有点让人看不懂了。比如下面的例子:
let message; // 这个变量被声明了,只是值为 undefined
// let age // 确保没有声明过这个变量
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"
即使未初始化的变量会被自动赋予
undefined
值,但我们仍然建议在声明变量的同时进行初始化。这样,当typeof
返回"undefined"
时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化。
请注意上例中,虽然 age
是一个 undeclared 变量,但 typeof age
并没有报错。这是因为 typeof
有一个特殊的安全防范机制。
该安全防范机制对在浏览器中运行的 JS 代码来说很有帮助,可以在程序中检查全局变量 XXX 而不出现 ReferenceError 错误:
// 这样会抛出错误
if (atob) {
atob = function () {
/* ... */
};
}
// 这样是安全的
if (typeof atob === 'undefined') {
atob = function () {
/* ... */
};
}
Null 类型
Null 类型同样只有一个值,即特殊值 null
。逻辑上讲,null
值表示一个空对象指针,这也是给 typeof
传一个 null
会返回 "object"
的原因:
let car = null;
console.log(typeof car); // "object"
在定义将来要保存对象值的变量时,建议使用 null
来初始化。这样,只要检查这个变量的值是不是 null
就可以知道这个变量是否在后来被重新赋予了一个对象的引用。
undefined
由 null
派生而来,表面上它们是相等的:
console.log(undefined == null); // true
console.log(undefined === null); // false
对数据的操作
基本类型数据
- 基本类型数据存储在 栈(stack) 中。
- 占用的内存空间一般较小,运行效率相对较高。
- 栈内存由系统自动分配、自动回收,分配的内存大小是固定的。
const a = 10; // 申请 stack 内存, 存放 [基本类型数据 10]
let b = a; // JS 操作的是 stack 内存中存放的 [基本类型数据 10]
b += 10;
console.log(a); // 10
console.log(b); // 20
引用类型数据
- 引用类型数据存储在 堆(heap) 中。
- 占用的内存空间一般较大,运行效率相对较低。
- 内存由代码指定分配,分配的大小不定,可动态调整。
const obj1 = {}; // 申请 heap 内存, 存放引用类型数据 {}; 申请 stack 内存, 存放 [引用类型数据 {} 对应的 heap 内存的地址]
const obj2 = obj1; // JS 操作的是 stack 内存中存放的 [引用类型数据 {} 对应的 heap 内存的地址]
obj2.name = 'superman';
console.log(obj1.name); // superman
console.log(obj2.name); // superman
可以看到,修改 obj1
的属性值,obj2
的属性值也会变。
注意:引用类型数据里面的基本类型数据,也是存储在栈中:
const obj = { name: 'superman' };
对象 obj
中 name
的属性值 "superman"
也是存储在 stack 内存中的。
深拷贝 & 浅拷贝
浅拷贝数组:
-
arr.concat()
:用于合并数组,不会改变原数组
const arr = [1, 2, 3];
const copyArr = [].concat(arr);
console.log('arr', arr); // arr [1, 2, 3]
console.log('copyArr', copyArr); // copyArr [1, 2, 3]
copyArr.push(4, 5); // 对拷贝的数组添加数据
console.log('arr', arr); // arr [1, 2, 3]
console.log('copyArr', copyArr); // copyArr [1, 2, 3, 4, 5]
-
arr.slice()
:用于切取指定数组元素,不会改变原数组
const arr = [1, 2, 3];
const copyArr = arr.slice();
-
Array.from(arr)
:用于把类数组对象转成数组
const arr = [1, 2, 3];
const copyArr = Array.from(arr);
- 扩展运算符 & 解构赋值
const arr = [1, 2, 3];
const copyArr = [...arr];
浅拷贝对象:
-
Object.assign()
用于合并对象
const obj = { name: 'superman' };
const copyObj = Object.assign({}, obj);
copyObj.name = 'monster';
console.log('obj', obj); // obj {name: 'superman'}
console.log('copyObj', copyObj); // copyObj {name: 'monster'}
- 扩展运算符 & 解构赋值
const obj = { name: 'superman' };
const copyObj = { ...obj };
- 这两个方法都会 覆盖前面的同名属性。
深拷贝对象 & 数组:
- 使用 JSON 的
parse
和stringify
方法:
const arr = [{ name: 'superman' }];
const copyArr = JSON.parse(JSON.stringify(arr));
copyArr[0].name = 'super';
console.log('arr', arr); // arr [ {name: 'superman'} ]
console.log('copyArr', copyArr); // copyArr [ {name: 'super'} ]
- 使用
structuredClone
方法:
const obj = { name: '张三', age: { real: 21, fake: 18 } };
const cloneObj = structuredClone(obj);
cloneObj.age.real = 40;
console.log('cloneObj', cloneObj); // { name: '张三', age: { real: 40, fake: 18 } }
console.log('obj', obj); // { name: '张三', age: { real: 21, fake: 18 } }
- 注意:
structuredClone
无法拷贝函数