题目
请详细说明 JavaScript 的数据类型,以及如何检测数据类型(typeof、instanceof、Object.prototype.toString.call() 的区别)。
📝 标准答案
核心要点
基本数据类型(7种):存储在栈内存,按值访问
- Number、String、Boolean、Undefined、Null、Symbol、BigInt
引用数据类型:存储在堆内存,按引用访问
- Object(包括 Array、Function、Date、RegExp 等)
内存存储区别:
- 栈内存:存储基本类型的值和引用类型的地址
- 堆内存:存储引用类型的实际数据
类型检测方法:
typeof:适合基本类型(null 除外)instanceof:适合引用类型和原型链检测Object.prototype.toString.call():最准确的类型检测
详细说明
栈内存 vs 堆内存
// 基本类型 - 栈内存
let a = 10;
let b = a; // 复制值
b = 20;
console.log(a); // 10(互不影响)
// 引用类型 - 堆内存
let obj1 = { name: 'Alice' };
let obj2 = obj1; // 复制引用地址
obj2.name = 'Bob';
console.log(obj1.name); // 'Bob'(共享同一对象)内存分配示意:
栈内存:
a: 10
b: 20
obj1: 0x001 (地址)
obj2: 0x001 (地址)
堆内存:
0x001: { name: 'Bob' }🧠 深度理解
typeof 的特点和缺陷
// ✅ 正确的情况
typeof 123 // 'number'
typeof 'hello' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof 10n // 'bigint'
typeof function(){} // 'function'
// ❌ 特殊情况
typeof null // 'object' (历史遗留bug)
typeof [] // 'object'
typeof {} // 'object'
typeof new Date() // 'object'
typeof /regex/ // 'object'为什么 typeof null === 'object'?
- JavaScript 最初版本中,值以 32 位存储
- 前 3 位表示类型标签,000 表示对象
- null 的所有位都是 0,因此被误判为对象
instanceof 的原理
// instanceof 检查原型链
[] instanceof Array // true
[] instanceof Object // true
[] instanceof String // false
// 原理:检查右边构造函数的 prototype 是否在左边对象的原型链上
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 测试
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof([], Object)); // trueinstanceof 的局限性:
// 1. 不能检测基本类型
1 instanceof Number // false
'hello' instanceof String // false
// 2. 可以被修改原型链欺骗
function Foo() {}
let obj = new Foo();
console.log(obj instanceof Foo); // true
Object.setPrototypeOf(obj, Array.prototype);
console.log(obj instanceof Foo); // false
console.log(obj instanceof Array); // true
// 3. 跨 iframe 问题
// iframe 中的数组在父页面用 instanceof 检测会失败Object.prototype.toString.call() - 最准确的方法
// 获取精确的类型字符串
Object.prototype.toString.call(123) // '[object Number]'
Object.prototype.toString.call('hello') // '[object String]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(function(){}) // '[object Function]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(/regex/) // '[object RegExp]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(10n) // '[object BigInt]'
// 封装通用类型检测函数
function getType(value) {
const type = Object.prototype.toString.call(value);
return type.slice(8, -1).toLowerCase();
}
console.log(getType([])); // 'array'
console.log(getType(null)); // 'null'
console.log(getType(new Date())); // 'date'常见误区
误区:认为 typeof 能准确检测所有类型
javascript// ❌ 错误 typeof null === 'null' // false,实际是 'object' typeof [] === 'array' // false,实际是 'object'误区:认为 instanceof 能检测基本类型
javascript// ❌ 错误 123 instanceof Number // false // ✅ 正确(包装对象) new Number(123) instanceof Number // true误区:忽略引用类型的共享特性
javascriptfunction updateUser(user) { user.age = 30; // 会修改原对象 } let person = { age: 25 }; updateUser(person); console.log(person.age); // 30(被修改了)
进阶知识
1. 包装对象
// 基本类型会临时转换为包装对象
let str = 'hello';
console.log(str.length); // 5
console.log(str.toUpperCase()); // 'HELLO'
// 等价于:
let temp = new String('hello');
console.log(temp.length);
temp = null; // 使用后立即销毁2. Symbol.toStringTag 自定义类型
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const obj = new MyClass();
console.log(Object.prototype.toString.call(obj)); // '[object MyClass]'3. 类型转换规则
// 隐式转换
console.log(1 + '2'); // '12' (number → string)
console.log('5' - 2); // 3 (string → number)
console.log(true + 1); // 2 (boolean → number)
console.log([] + []); // '' (array → string)
console.log({} + []); // '[object Object]'
// 显式转换
Number('123') // 123
String(123) // '123'
Boolean(0) // false💡 面试回答技巧
🎯 一句话回答(快速版)
JavaScript 有 7 种基本类型(string、number、boolean、null、undefined、symbol、bigint)和 1 种引用类型(object)。检测类型用 typeof(基本类型)、instanceof(引用类型)或 Object.prototype.toString.call()(最准确)。
📣 口语化回答(推荐)
面试时可以这样回答:
"JavaScript 的数据类型分为基本类型和引用类型两大类。
基本类型有 7 种:string、number、boolean、null、undefined,还有 ES6 新增的 symbol 和 bigint。它们存储在栈内存,按值访问,赋值时会复制一份新的值。
引用类型就是 object,包括普通对象、数组、函数等。它们存储在堆内存,变量里存的是地址,赋值时复制的是地址,所以多个变量可能指向同一个对象。
类型检测的话,有三种常用方法:
typeof 适合检测基本类型,但有个坑是
typeof null返回 'object',这是历史遗留 bug。instanceof 适合检测引用类型,原理是检查原型链,比如
[] instanceof Array返回 true。Object.prototype.toString.call() 是最准确的方法,能区分所有类型,比如数组返回 '[object Array]',null 返回 '[object Null]'。"
推荐回答顺序
先说分类:
- "JavaScript 有 7 种基本数据类型和 1 种引用数据类型"
- "基本类型存储在栈内存,引用类型存储在堆内存"
再说区别:
- "基本类型按值访问,赋值时复制值"
- "引用类型按引用访问,赋值时复制地址"
然后说检测方法:
- "typeof 适合基本类型,但 null 会返回 'object'"
- "instanceof 适合引用类型,检查原型链"
- "Object.prototype.toString.call() 最准确"
最后举例说明:
- 用代码演示栈内存和堆内存的区别
- 展示三种检测方法的使用场景
重点强调
- ✅ typeof null 的历史 bug
- ✅ 引用类型的共享特性
- ✅ instanceof 的原型链检测原理
- ✅ Object.prototype.toString.call() 的准确性
可能的追问
Q1: 为什么基本类型存储在栈,引用类型存储在堆?
A:
- 栈内存分配和释放速度快,适合存储大小固定的基本类型
- 堆内存可以动态分配,适合存储大小不固定的引用类型
- 栈内存空间有限,引用类型可能很大,存储在堆中更合理
Q2: 如何判断一个变量是数组?
A: 有 4 种方法:
// 1. Array.isArray()(推荐)
Array.isArray([]) // true
// 2. instanceof
[] instanceof Array // true
// 3. constructor
[].constructor === Array // true
// 4. Object.prototype.toString.call()
Object.prototype.toString.call([]) === '[object Array]' // trueQ3: null 和 undefined 的区别?
A:
undefined:变量声明但未赋值,或对象属性不存在null:表示"空对象",需要手动赋值typeof undefined是 'undefined'typeof null是 'object'(历史 bug)
Q4: 如何实现一个准确的类型检测函数?
A:
function typeOf(value) {
// 处理 null
if (value === null) return 'null';
// 基本类型用 typeof
const type = typeof value;
if (type !== 'object') return type;
// 引用类型用 toString
return Object.prototype.toString.call(value)
.slice(8, -1)
.toLowerCase();
}
// 测试
console.log(typeOf(null)); // 'null'
console.log(typeOf([])); // 'array'
console.log(typeOf(new Date())); // 'date'Q5: BigInt 和 Number 的区别?
A:
Number:双精度浮点数,安全整数范围 -(2^53-1) 到 2^53-1BigInt:可以表示任意大的整数,后缀n- 两者不能直接运算,需要转换
const big = 9007199254740991n;
const num = 123;
// ❌ 错误
// big + num // TypeError
// ✅ 正确
big + BigInt(num) // 9007199254741114n💻 代码示例
完整的类型检测工具库
const TypeChecker = {
// 获取精确类型
getType(value) {
if (value === null) return 'null';
if (value === undefined) return 'undefined';
const type = typeof value;
if (type !== 'object') return type;
return Object.prototype.toString.call(value)
.slice(8, -1)
.toLowerCase();
},
// 类型判断方法
isNumber: (val) => typeof val === 'number' && !isNaN(val),
isString: (val) => typeof val === 'string',
isBoolean: (val) => typeof val === 'boolean',
isUndefined: (val) => val === undefined,
isNull: (val) => val === null,
isSymbol: (val) => typeof val === 'symbol',
isBigInt: (val) => typeof val === 'bigint',
isFunction: (val) => typeof val === 'function',
isObject: (val) => val !== null && typeof val === 'object',
isPlainObject: (val) => Object.prototype.toString.call(val) === '[object Object]',
isArray: (val) => Array.isArray(val),
isDate: (val) => val instanceof Date,
isRegExp: (val) => val instanceof RegExp,
// 空值判断
isEmpty(value) {
if (value === null || value === undefined) return true;
if (typeof value === 'string') return value.length === 0;
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object') return Object.keys(value).length === 0;
return false;
},
// 深度相等比较
isEqual(a, b) {
if (a === b) return true;
const typeA = this.getType(a);
const typeB = this.getType(b);
if (typeA !== typeB) return false;
if (typeA === 'array') {
if (a.length !== b.length) return false;
return a.every((item, index) => this.isEqual(item, b[index]));
}
if (typeA === 'object') {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key => this.isEqual(a[key], b[key]));
}
return false;
}
};
// 测试
console.log(TypeChecker.getType([])); // 'array'
console.log(TypeChecker.isPlainObject({})); // true
console.log(TypeChecker.isEmpty([])); // true
console.log(TypeChecker.isEqual([1,2], [1,2])); // true内存分配演示
// 演示栈内存和堆内存的区别
function memoryDemo() {
// 基本类型 - 栈内存
let num1 = 10;
let num2 = num1; // 复制值
num2 = 20;
console.log('基本类型:');
console.log('num1:', num1); // 10
console.log('num2:', num2); // 20
console.log('互不影响\n');
// 引用类型 - 堆内存
let obj1 = { value: 10 };
let obj2 = obj1; // 复制引用
obj2.value = 20;
console.log('引用类型:');
console.log('obj1.value:', obj1.value); // 20
console.log('obj2.value:', obj2.value); // 20
console.log('共享同一对象\n');
// 如何避免引用共享
let obj3 = { value: 10 };
let obj4 = { ...obj3 }; // 浅拷贝
obj4.value = 30;
console.log('浅拷贝后:');
console.log('obj3.value:', obj3.value); // 10
console.log('obj4.value:', obj4.value); // 30
console.log('互不影响');
}
memoryDemo();