说说JavaScript中的数据类型?存储上的差别?
问题解析
在 JavaScript 中,数据类型可以分为两大类:基本类型(Primitive Types)和引用类型(Reference Types)。理解这两种类型的区别,特别是它们在内存中的存储方式,是掌握 JavaScript 核心概念的关键。
核心概念
基本类型(Primitive Types)
基本类型是值类型,在内存中直接存储值本身,共有 7 种:
| 类型 | 说明 | 示例 |
|---|---|---|
Number |
数值类型(整数和浮点数) | 42, 3.14 |
String |
字符串 | "hello" |
Boolean |
布尔值 | true, false |
Undefined |
未定义 | undefined |
Null |
空值 | null |
Symbol |
符号(ES6新增) | Symbol('desc') |
BigInt |
大整数(ES2020新增) | 9007199254740991n |
引用类型(Reference Types)
引用类型是对象类型,在内存中存储的是指向堆内存的引用地址:
Object- 普通对象Array- 数组Function- 函数Date- 日期RegExp- 正则表达式Map/Set/WeakMap/WeakSet
详细解答
1. 基本类型的特点
// Number 类型
let intNum = 55; // 十进制
let octNum = 070; // 八进制(0开头)= 56
let hexNum = 0xA; // 十六进制(0x开头)= 10
let floatNum = 3.125e7; // 科学计数法 = 31250000
// 特殊值 NaN
console.log(0 / 0); // NaN
console.log(-0 / +0); // NaN
// Undefined 类型
let message;
console.log(message); // undefined
console.log(typeof message); // "undefined"
// Null 类型
let car = null;
console.log(typeof car); // "object"(历史遗留bug)
console.log(null == undefined); // true
console.log(null === undefined); // false
// String 类型
let str = "hello";
// 字符串不可变
str = str + " world"; // 实际上是创建新字符串
// Symbol 类型
let sym1 = Symbol('foo');
let sym2 = Symbol('foo');
console.log(sym1 === sym2); // false(每个Symbol都是唯一的)
2. 引用类型的特点
// Object
let person = {
name: "Nicholas",
age: 29
};
// Array - 可以存储任意类型,动态大小
let colors = ["red", 2, { age: 20 }];
colors.push("blue");
// Function - 实际上是 Function 类型的实例
function sum(a, b) {
return a + b;
}
// 等价于
let sum2 = function(a, b) {
return a + b;
};
// 箭头函数
let sum3 = (a, b) => a + b;
3. 存储区别详解
基本类型的存储
基本类型存储在**栈(Stack)**中,直接保存值:
let a = 10;
let b = a; // 复制值,b 得到的是 10 的副本
b = 20;
console.log(a); // 10(a 不受影响)
内存示意:
栈内存
┌─────────┐
│ a: 10 │
│ b: 20 │ ← 独立的存储空间
└─────────┘
引用类型的存储
引用类型在堆(Heap)中存储对象,在栈中存储引用地址:
let obj1 = { name: "Xxx" };
let obj2 = obj1; // 复制引用地址,指向同一个对象
obj2.name = "Yyy";
console.log(obj1.name); // "Yyy"(obj1 也被修改了)
内存示意:
栈内存 堆内存
┌──────────┐ ┌─────────────────┐
│ obj1: ───┼──────→│ { name: "Yyy" } │
│ obj2: ───┘ └─────────────────┘
└──────────┘ (两个变量指向同一对象)
深入理解
1. 类型检测方法对比
// typeof - 适合检测基本类型
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"(bug)
console.log(typeof []); // "object"
console.log(typeof function(){}); // "function"
// instanceof - 适合检测引用类型
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
// Object.prototype.toString - 最准确的方法
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
2. 赋值与传参的本质
// 基本类型传值
function changeValue(x) {
x = 100;
}
let num = 10;
changeValue(num);
console.log(num); // 10(不变)
// 引用类型传引用
function changeProperty(obj) {
obj.name = "changed";
}
let person = { name: "original" };
changeProperty(person);
console.log(person.name); // "changed"(被修改了)
// 但重新赋值不会影响原对象
function replaceObject(obj) {
obj = { name: "new" }; // 只是改变了局部变量的指向
}
replaceObject(person);
console.log(person.name); // "changed"(不变)
3. 深拷贝与浅拷贝
// 浅拷贝 - 只复制一层
let obj = { a: 1, b: { c: 2 } };
let shallow = { ...obj }; // 或 Object.assign({}, obj)
shallow.b.c = 3;
console.log(obj.b.c); // 3(原对象也被修改)
// 深拷贝 - 递归复制所有层级
let deep = JSON.parse(JSON.stringify(obj));
deep.b.c = 4;
console.log(obj.b.c); // 3(原对象不受影响)
最佳实践
- 使用严格相等(===)进行比较,避免类型转换带来的意外结果
- 判断 null 时直接使用
=== null,不要使用 typeof - 处理引用类型时注意共享引用的问题,需要独立副本时使用深拷贝
- 使用 const 声明不会重新赋值的变量,用 let 声明会改变的变量
- 避免意外创建全局变量,使用严格模式
"use strict"
// 推荐:判断 null
if (value === null) { }
// 推荐:深拷贝简单对象
const deepClone = JSON.parse(JSON.stringify(obj));
// 推荐:使用 const 和 let
const PI = 3.14159;
let count = 0;
面试要点
-
JavaScript 有哪些数据类型?
- 基本类型:Number、String、Boolean、Undefined、Null、Symbol、BigInt
- 引用类型:Object(包括 Array、Function 等)
-
基本类型和引用类型的区别?
- 存储位置:基本类型在栈中,引用类型对象在堆中
- 赋值行为:基本类型复制值,引用类型复制引用地址
- 比较行为:基本类型比较值,引用类型比较引用地址
-
为什么 typeof null 返回 "object"?
- 这是 JavaScript 的历史遗留 bug,null 实际上是基本类型
-
如何判断一个变量是否为数组?
Array.isArray(arr)arr instanceof ArrayObject.prototype.toString.call(arr) === "[object Array]"