Skip to content

题目

请详细说明 JavaScript 的数据类型,以及如何检测数据类型(typeof、instanceof、Object.prototype.toString.call() 的区别)。

📝 标准答案

核心要点

  1. 基本数据类型(7种):存储在栈内存,按值访问

    • Number、String、Boolean、Undefined、Null、Symbol、BigInt
  2. 引用数据类型:存储在堆内存,按引用访问

    • Object(包括 Array、Function、Date、RegExp 等)
  3. 内存存储区别

    • 栈内存:存储基本类型的值和引用类型的地址
    • 堆内存:存储引用类型的实际数据
  4. 类型检测方法

    • typeof:适合基本类型(null 除外)
    • instanceof:适合引用类型和原型链检测
    • Object.prototype.toString.call():最准确的类型检测

详细说明

栈内存 vs 堆内存

javascript
// 基本类型 - 栈内存
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 的特点和缺陷

javascript
// ✅ 正确的情况
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 的原理

javascript
// 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));  // true

instanceof 的局限性:

javascript
// 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() - 最准确的方法

javascript
// 获取精确的类型字符串
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'

常见误区

  1. 误区:认为 typeof 能准确检测所有类型

    javascript
    // ❌ 错误
    typeof null === 'null'  // false,实际是 'object'
    typeof [] === 'array'   // false,实际是 'object'
  2. 误区:认为 instanceof 能检测基本类型

    javascript
    // ❌ 错误
    123 instanceof Number  // false
    
    // ✅ 正确(包装对象)
    new Number(123) instanceof Number  // true
  3. 误区:忽略引用类型的共享特性

    javascript
    function updateUser(user) {
      user.age = 30;  // 会修改原对象
    }
    
    let person = { age: 25 };
    updateUser(person);
    console.log(person.age);  // 30(被修改了)

进阶知识

1. 包装对象

javascript
// 基本类型会临时转换为包装对象
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 自定义类型

javascript
class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClass';
  }
}

const obj = new MyClass();
console.log(Object.prototype.toString.call(obj));  // '[object MyClass]'

3. 类型转换规则

javascript
// 隐式转换
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]'。"

推荐回答顺序

  1. 先说分类

    • "JavaScript 有 7 种基本数据类型和 1 种引用数据类型"
    • "基本类型存储在栈内存,引用类型存储在堆内存"
  2. 再说区别

    • "基本类型按值访问,赋值时复制值"
    • "引用类型按引用访问,赋值时复制地址"
  3. 然后说检测方法

    • "typeof 适合基本类型,但 null 会返回 'object'"
    • "instanceof 适合引用类型,检查原型链"
    • "Object.prototype.toString.call() 最准确"
  4. 最后举例说明

    • 用代码演示栈内存和堆内存的区别
    • 展示三种检测方法的使用场景

重点强调

  • typeof null 的历史 bug
  • 引用类型的共享特性
  • instanceof 的原型链检测原理
  • Object.prototype.toString.call() 的准确性

可能的追问

Q1: 为什么基本类型存储在栈,引用类型存储在堆?

A:

  • 栈内存分配和释放速度快,适合存储大小固定的基本类型
  • 堆内存可以动态分配,适合存储大小不固定的引用类型
  • 栈内存空间有限,引用类型可能很大,存储在堆中更合理

Q2: 如何判断一个变量是数组?

A: 有 4 种方法:

javascript
// 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]'  // true

Q3: null 和 undefined 的区别?

A:

  • undefined:变量声明但未赋值,或对象属性不存在
  • null:表示"空对象",需要手动赋值
  • typeof undefined 是 'undefined'
  • typeof null 是 'object'(历史 bug)

Q4: 如何实现一个准确的类型检测函数?

A:

javascript
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-1
  • BigInt:可以表示任意大的整数,后缀 n
  • 两者不能直接运算,需要转换
javascript
const big = 9007199254740991n;
const num = 123;

// ❌ 错误
// big + num  // TypeError

// ✅ 正确
big + BigInt(num)  // 9007199254741114n

💻 代码示例

完整的类型检测工具库

javascript
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

内存分配演示

javascript
// 演示栈内存和堆内存的区别
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();

🔗 相关知识点

📚 参考资料

基于 MIT 许可发布 | 隐私政策