面试官问"JS的类型"时,到底想听到什么?
这不是一篇基础教程,而是一次思维升级。 如果你只停留在"知道有哪些类型"的层面,那你和面试官之间还差一个V8。
引子:一个最常见的面试场景
面试官问:"JavaScript 有哪些数据类型?"
初级回答(大部分人):
"有 string、number、boolean、null、undefined、object......还有 symbol?"
面试官(内心):背八股呢?
进阶回答(你):
"JS 的类型可以分为两大类------基本类型和引用类型。这种分类不是拍脑袋定的,而是由 V8 引擎的内存管理机制决定的。基本类型体积小,直接存在栈里;引用类型体积不定,存在堆里,栈里只存 8 字节的地址指针。这样做的好处是------"
面试官(眼睛亮了):这人懂底层。
差距在哪里? 前者只是列举 ,后者是解释为什么。
一、先搞定分类------别让 null 坑了你
1.1 两张大表记清楚
基本类型(7种)------值直接存栈里
| 类型 | 例子 | 常见坑 |
|---|---|---|
string |
'hello' |
--- |
number |
42 |
NaN 也是 number 类型 |
boolean |
true |
!! 是布尔转换的快捷方式 |
undefined |
let a |
声明未赋值 |
null |
null |
⚠️ typeof null → 'object' |
bigint |
10n |
ES2020 才加入 |
Symbol |
Symbol('id') |
每次调用都是唯一值 |
引用类型(常见4种)------值存堆里,栈存地址
| 类型 | 例子 | 说明 |
|---|---|---|
Object |
{ name: 'Alice' } |
一切引用类型的祖宗 |
Array |
[1, 2, 3] |
typeof [] → 'object' |
Function |
function() {} |
JS 中函数也是对象 |
Date |
new Date() |
⚠️ Date() 不是 Date 类型,是字符串! |
1.2 终极记忆法
七个基本用一个口诀记住:
"宋(string)数(number)不(boolean)定(undefined)空(null)大(bigint)象(Symbol)"
七种基本类型,一个不少。
引用类型只记一句话:"除了以上七种,其他全是对象"
二、核心区别------赋值行为见真章
自己跑一下这三行代码,你就懂了:
javascript
// 场景 A:基本类型赋值
let a = 10;
let b = a;
b = 20;
console.log(a); // 猜猜是 10 还是 20?
javascript
// 场景 B:引用类型赋值
let objA = { count: 10 };
let objB = objA;
objB.count = 20;
console.log(objA.count); // 猜猜是 10 还是 20?
答案:
- 场景 A:
10✅ --- 基本类型赋值是复制值,a 和 b 没关系了 - 场景 B:
20⚠️ --- 引用类型赋值是复制地址,objA 和 objB 指向同一个对象
记法:基本类型像复印件------改一个不影响另一个;引用类型像快捷方式------不管从哪个入口进去,改的都是同一个文件。
三、为什么 JS 要这么设计?------V8 的智慧
这是很多人知道"是什么"却说不清"为什么"的关键。
3.1 来,在脑子里画张图
ini
执行这段代码时,V8 在内存里干了什么:
let name = 'GGBond';
let age = 20;
let girlFriend = {
name: 'feifei',
hobbies: ['coding', 'reading']
};
画出来是这样的:
css
┌────────── 栈(Call Stack)──────────┐
│ │
│ name: 'GGBond' ← 字符串直接存 │
│ age: 20 ← 数字直接存 │
│ girlFriend: ● ------------------------┐ │
│ │ │
└─────────────────────│────────────────┘
│ 引用地址(8 字节)
▼
┌────────── 堆(Heap)────────────────┐
│ │
│ { name: 'feifei', │
│ hobbies: ● ------------→ ['coding', │
│ } 'reading'] │
│ │
└──────────────────────────────────────┘
3.2 为什么不能反过来?------V8 的真正考量
❌ 错误方案:所有数据都放栈里
如果 girlFriend 这个大对象也放进栈:
- 栈的大小是固定的(通常 1-2MB)
- 一个大对象可能就占几百 KB
- 多来几个对象 → 爆栈(Stack Overflow)
❌ 错误方案:所有数据都放堆里
如果连 age = 20 也要去堆里分配:
- 堆的访问速度比栈慢 10 倍以上
- 一个数字才 8 字节,为了它去堆里开辟空间,得不偿失
✅ V8 的选择:混合存储
基本类型(小、轻) → 栈 --- 快就完了
引用类型(大、不定)→ 堆 --- 栈不会爆
引用地址(8字节) → 栈 --- 查询时通过地址找数据
一句话总结:栈管速度,堆管容量。V8 用最小代价换来了最大效率。
四、类型检测------避开常见的坑
4.1 三种检测方法
typeof ------ 适合基本类型(除 null)
javascript
typeof 'hello' // 'string'
typeof 42 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof 10n // 'bigint'
typeof null // 'object' ← 历史遗留bug
typeof [] // 'object' ← 数组也是object
typeof {} // 'object'
规律:除了 null,基本类型的 typeof 都能对上。
instanceof ------ 适合引用类型
javascript
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
'hello' instanceof String // false ❌ 基本类型不行
注意:
[] instanceof Object也是 true!因为原型链上能找到 Object。
Object.prototype.toString ------ 万能方法
javascript
function getType(v) {
return Object.prototype.toString.call(v).slice(8, -1);
}
getType('hello') // 'String'
getType(null) // 'Null'
getType([]) // 'Array'
getType(new Date()) // 'Date'
getType(new Map()) // 'Map'
4.2 面试常问:手写一个类型判断函数
javascript
function typeOf(value) {
// null 单独处理
if (value === null) return 'null';
// 基本类型用 typeof
const base = typeof value;
if (base !== 'object' && base !== 'function') return base;
// 引用类型用 toString
const tag = Object.prototype.toString.call(value);
return tag.slice(8, -1).toLowerCase();
}
// 测试
typeOf('hello') // 'string'
typeOf(null) // 'null' ✅ 修复了 typeof null 的 bug
typeOf([]) // 'array'
typeOf(new Date())// 'date'
typeOf(new Map()) // 'map'
五、V8 执行全过程------把知识串起来
5.1 完整流程
javascript
let num = 10;
let str = 'hello';
let obj = { key: 'value' };
function add(x, y) {
return x + y;
}
let result = add(num, 20);
V8 的执行步骤:
less
Step 1:创建调用栈(Call Stack)
Step 2:创建全局执行上下文,压入栈底
Step 3:执行声明(创建阶段)
├── num → 栈中存 10
├── str → 栈中存 'hello'
├── obj → 堆中创建 {key:'value'},栈存地址 @001
├── add → 堆中存函数体,栈存地址 @002
└── result → 栈中存 undefined
Step 4:执行 add(num, 20)
├── 创建函数执行上下文,入栈
├── x=10, y=20 存入栈中(函数的栈)
├── 执行 x + y → 返回 30
└── 函数执行上下文出栈
Step 5:result = 30
5.2 这段代码会爆栈吗?
javascript
function infinite() {
return infinite();
}
infinite();
会。 每调一次就创建一个执行上下文入栈,栈是有限的,最终:
scss
┌───────── 调用栈 ─────────┐
│ infinite() // 第10000次 │
│ infinite() // 第9999次 │
│ ... │
│ infinite() // 第1次 │
│ 全局执行上下文 │
└──────────────────────────┘
RangeError: Maximum call stack size exceeded
这就是"爆栈"------引用类型放堆里而不是栈里,正是为了防止这种溢出。
写在最后
这篇文章没有讲什么高深的东西,而是用"为什么"把你知道的碎片知识串了起来。
JS 类型的核心其实就三句话:
- 基本类型存值(栈),引用类型存地址(栈→堆)
- 这样设计是为了平衡速度(栈)和容量(堆)
- typeof 看基本,toString 看所有
能说出这三句,面试官就知道你不仅学过,还思考过。
觉得有用的话点赞 👍 收藏 ⭐,让更多人看到。 你的面试路上还遇到过什么离谱的 JS 类型题?评论区告诉我。