面试导航 是一个专注于前、后端技术学习和面试准备的 免费 学习平台,提供系统化的技术栈学习,深入讲解每个知识点的核心原理,帮助开发者构建全面的技术体系。平台还收录了大量真实的校招与社招面经,帮助你快速掌握面试技巧,提升求职竞争力。如果你想加入我们的交流群,欢迎通过微信联系:
yunmz777
。

JavaScript 数据类型
在 JavaScript 中,所有的值都归属于某种类型。理解这些类型及其特性,是掌握 JavaScript 的基础。
📌 类型分类
JavaScript 的类型系统分为两大类:
-
原始类型(Primitive Types):不可变,按值传递。
-
引用类型(Reference Types):可变,按引用传递。
🧱 原始类型(Primitive Types)
共有 7 种原始类型:
1. Undefined
表示变量已声明但尚未赋值,或访问了未定义的属性。
js
let a;
console.log(a); // undefined
2. Null
表示"无值"或"空对象引用"。通常用来主动清空某个变量。
js
let b = null;
console.log(typeof b); // "object"(这是历史遗留 bug)
3. Boolean
表示逻辑值,仅有两个:true
和 false
。
js
let isLoggedIn = false;
常用于条件判断:
js
if (isLoggedIn) {
console.log("欢迎回来!");
}
4. Number
JavaScript 中的数字类型包括整数和浮点数,统一为 number
类型。
js
let count = 42;
let pi = 3.14159;
支持特殊数值:
js
Infinity;
-Infinity;
NaN; // Not-a-Number,表示非数字
5. String
用于表示文本,使用 '单引号'
、"双引号"
或 模板字符串
。
js
let name = "Alice";
let greeting = `Hello, ${name}!`;
6. Symbol(ES6 引入)
表示独一无二的值,常用于对象属性的私有键。
js
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false
7. BigInt(ES2020 引入)
用于表示超过 Number.MAX_SAFE_INTEGER
的大整数。
js
const big = 900719925474099100000n;
console.log(typeof big); // "bigint"
🧩 引用类型(Reference Types)
引用类型可以存储多个值或复杂结构。它们通过引用进行赋值和比较。
Object(通用对象)
js
const user = {
name: "Alice",
age: 25,
};
Array(数组)
js
const numbers = [1, 2, 3];
Function(函数)
js
function greet() {
console.log("Hello!");
}
其他内建对象
-
Date
-
RegExp
-
Map
/Set
-
WeakMap
/WeakSet
-
Error
等
⚖️ 原始类型 vs 引用类型
特性 | 原始类型 | 引用类型 |
---|---|---|
可变性 | 不可变 | 可变 |
赋值方式 | 按值传递 | 按引用传递 |
比较方式 | 值比较(===) | 引用地址比较(===) |
存储位置 | 栈内存 | 堆内存 |
📚 小贴士与陷阱
-
typeof null
返回"object"
是 JavaScript 的一个历史 bug。 -
原始类型是不可扩展的,不能给它们添加属性(尽管可以临时包装成对象)。
-
使用
===
(严格等于)可以避免类型转换带来的误解。 -
判断是否是引用类型可以用:
jstypeof value === "object" && value !== null;
typeof
typeof
是 JavaScript 中的一个一元运算符,用于检测变量或值的类型。它会返回一个字符串,表示该值的类型。
我们来看一些例子:
js
console.log(typeof 777); // "number"
console.log(typeof 3.14); // "number"
console.log(typeof 0); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof Number("moment")); // "number"
console.log(typeof 77n); // "bigint"
console.log(typeof "1"); // "string"
console.log(typeof typeof 1); // "string"(typeof 返回的是字符串)
console.log(typeof String(777)); // "string"
console.log(typeof true); // "boolean"
console.log(typeof false); // "boolean"
console.log(typeof Boolean(5)); // "boolean"
console.log(typeof !!1); // "boolean"
console.log(typeof Symbol()); // "symbol"
console.log(typeof Symbol("foo")); // "symbol"
console.log(typeof Symbol.iterator); // "symbol"
console.log(typeof { a: 1 }); // "object"
console.log(typeof [1, 2, 4]); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof /regex/); // "object"
console.log(typeof null); // "object"(这是 JavaScript 的一个历史 bug)
console.log(typeof function () {}); // "function"
console.log(typeof class T {}); // "function"
为什么 typeof null 是 "object"?
这是 JavaScript 的一个广为人知的历史遗留问题。
在最初的实现中,JavaScript 使用一种内部表示来存储类型信息。每个值都有一个"类型标签",用于表示它属于哪一类。对象的类型标签是 0
。由于 null
被表示为空指针(在底层通常是 0x00),其类型标签也被错误地解析为 0,因此 typeof null
返回 "object"
。
虽然这个行为被认为是一个 bug,但由于兼容性问题,它从未被修复。
函数是对象的子类型
虽然 typeof function () {}
返回 "function"
,但从规范角度看,函数本质上是对象的一个子类型。函数是"可调用对象",它们具有一个内部属性 [[Call]]
,使其可以被调用。
js
function f() {}
console.log(f.__proto__.constructor === Function); // true
console.log(f.__proto__.__proto__.constructor === Object); // true
// 函数是对象,也可以拥有属性
console.log(f.name); // "f"
console.log(f.arguments); // null(在非调用环境中为 null)
数组也是对象的子类型
数组在 JavaScript 中也被归为对象类型。它具有特殊的结构:按数字索引、具备 length
属性。
js
const foo = [];
console.log(foo.__proto__.constructor === Array); // true
console.log(foo.__proto__.__proto__.constructor === Object); // true
虽然 typeof foo
返回 "object"
,但更推荐使用:
js
Array.isArray(foo); // true
来判断一个值是否为数组。
typeof 返回值总结
值类型 | typeof 返回值 |
---|---|
Number | "number" |
BigInt | "bigint" |
String | "string" |
Boolean | "boolean" |
Symbol | "symbol" |
Undefined | "undefined" |
Null | "object"(历史 bug) |
Object | "object" |
Array | "object" |
Function | "function" |
new
操作符详解
在 JavaScript 中,new
是一个关键字,用于通过构造函数创建并返回一个新的对象实例。
🔧 new
的基本用途
通过 new
关键字调用一个函数,会执行以下几件事:
js
const instance = new ConstructorFunction();
-
创建一个全新的对象;
-
将这个新对象的
__proto__
链接到构造函数的prototype
; -
将构造函数内部的
this
绑定到这个新对象; -
执行构造函数中的代码;
-
如果构造函数返回一个对象,则使用这个对象作为
new
表达式的结果;否则返回创建的新对象。
📦 new
调用的返回值
使用 new
调用构造函数时,返回的永远是一个 引用类型(对象或函数)。
js
const str = new String("hello");
const num = new Number(123);
const bool = new Boolean(true);
const func = new Function("return 42");
console.log(typeof str); // "object"
console.log(typeof num); // "object"
console.log(typeof bool); // "object"
console.log(typeof func); // "function"
✅ 注意:构造函数
Function
返回的是一个可调用的函数,因此typeof
为"function"
,但它本质上仍然是对象的子类型。
🧠 构造函数返回值行为
构造函数中可以显式使用 return
返回值。如果返回的是一个对象类型(引用) ,它会覆盖默认返回的新实例;如果返回的是原始类型,则会被忽略,仍然返回新实例。
js
function A() {
this.name = "A";
return { msg: "custom object" };
}
function B() {
this.name = "B";
return 123;
}
const a = new A();
const b = new B();
console.log(a); // { msg: "custom object" }
console.log(b); // B { name: "B" }
🔍 new
运算符的底层执行流程
当你执行 new Constructor()
,内部大致相当于以下逻辑:
js
function customNew(Constructor, ...args) {
// 1. 创建一个新对象,并设置其原型
const obj = Object.create(Constructor.prototype);
// 2. 执行构造函数,将 this 指向新对象
const result = Constructor.apply(obj, args);
// 3. 如果构造函数显式返回对象,则返回该对象,否则返回新对象
return result !== null && typeof result === "object" ? result : obj;
}
使用示例:
js
function Person(name) {
this.name = name;
}
const p1 = customNew(Person, "Alice");
console.log(p1.name); // "Alice"
🚫 使用 new
的注意事项
-
不要对非构造函数使用
new
,否则会抛出错误。 -
避免滥用
new
包装原始类型(如new Number()
),可能导致类型混乱。 -
自定义构造函数必须使用大写开头(惯例)以示区分。
✅ new
与构造函数的关系总结
构造函数 | 返回类型 | typeof 返回值 |
---|---|---|
new Object() |
对象 | "object" |
new Array() |
数组(对象) | "object" |
new Function() |
函数 | "function" |
new String() |
包装对象 | "object" |
new Number() |
包装对象 | "object" |
new Boolean() |
包装对象 | "object" |
new
是 JavaScript 中构造对象的重要机制。掌握它的执行原理,有助于你更深入理解构造函数、原型链、继承机制等高级概念。在 ES6 引入 class
语法之后,new
的使用更加普遍,了解它的本质仍然是非常必要的。
undefined 和 undeclared
变量在未持有值的时候为 undefined
。此时 typeof
返回 undefined
:
js
var a;
console.log(tyoeof a); // undefined
大多数的开发者倾向于将 undefined
等同于 undeclared(未声明)
,但在 JavaScript
中它们完全是两回事。在作用域中声明但是还没有赋值的变量,是 undefined
。相反,还没有在作用域中声明过的变量,是undeclared
的。

在上列中, bar is not defined
容易让人误以为是 bar is undefined
。但是 undefined
和 is undefined
是两码事,但是 typeof
处理 undeclared
返回的结果竟然是 undefined
,例如:
js
var foo;
console.log(typeof foo); // undefined
console.log(typeof bar); // undefined
它们两个原样返回 "undefined"
,并且 typeof bar
并没有报错,这是因为 typeof
有一个特殊的安全防范机制。
内部属性 [[class]]
在前面的例子中,使用 typeof
进行判断,无论是 null
、Object
、Array
等类型,都返回的是 "object"
,那么是否有一种机制可以判断它具体为什么类型的值呢?答案是有的。
所有 typeof
返回值为 object
的对象(如数组)都包含一个内部属性 [[class]]
,我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类。这个属性无法直接访问,一般通过 Object.prototype.toString(...)
来查看。例如:
js
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call("moment")); //[object String]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(function f() {})); // [object Function]
console.log(Object.prototype.toString.call(class C {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call(new Boolean(1))); // [object Boolean]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]
上例中,数组内部[[class]]属性值是 Array
,正则表达式的值是 RegExp
。多数情况下,对象的内部 [[class]]
属性和创建该对象的内建原生构造函数相对应,但并不是所有的情况都是这样,例如一些基本类型,例如 null
和 undefined
,虽然 Null()
和 undefined()
这样的原生构造函数并不存在,但是内部 [[class]]
属性值仍然是 Null
和 Undefined
。
其他基本类型,例如 字符串、数值和布尔值 的情况有所不同,由于基本类型值没有 .length
和 .toString()
这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript
会自动为基本类型封装为一个对象,例如 var foo = 'moment';
,实际上进行的是 var foo =new String('moment');
,使其变成一个对象,让其拥有自己的属性和方法,如果想要得到封装对象中的基本类型值,可以使用 valueOf()
函数,例如:
js
var foo = new String("moment");
console.log(foo); // [String: 'moment']
console.log(foo.valueOf()); // moment
console.log(typeof foo.valueOf()); // string
手写 typeof
typeof
是非常有用的,但它不像需要的那样万能。例如,typeof []
是 "object"
,以及 typeof new Date()
、typeof /abc/
等。
为了明确地检查类型, mdn
上提供了一个自定义的 type(value)
函数,它主要模仿 typeof
的行为,但对于非基本类型(即对象和函数),它在可能的情况下返回更详细的类型名。
js
function type(value) {
// 如果传入的值是 null ,则返回 null
if (value === null) {
return "null";
}
const baseType = typeof value;
// 如果是基本类型
if (!["object", "function"].includes(baseType)) {
return baseType;
}
// Symbol.toStringTag 通常指定对象类的"display name"
const tag = value[Symbol.toStringTag];
if (typeof tag === "string") {
return tag;
}
// 如果他是一个函数,其源代码以 class 关键字开头的
if (
baseType === "function" &&
Function.prototype.toString.call(value).startsWith("class")
) {
return "class";
}
// 构造函数的名称;例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
const className = value.constructor.name;
if (typeof className === "string" && className !== "") {
return className;
}
// 没有合适的方法来获取值的类型,直接返回
return baseType;
}
Symbol.toStringTag 与类型判断
在 JavaScript 中,Symbol.toStringTag
是一个内置的 Symbol 属性,它在类型判断中起着核心作用。这个特殊的 Symbol 允许自定义对象在被Object.prototype.toString()
方法调用时返回的字符串标签。
Symbol.toStringTag 的工作原理
当我们调用Object.prototype.toString.call(value)
时,JavaScript 引擎会查找该值是否具有Symbol.toStringTag
属性:
- 如果对象有
Symbol.toStringTag
属性,则使用它的值作为类型标识 - 如果没有,则回退到默认的内部
[[Class]]
属性值
js
// 创建一个自定义对象,并定义Symbol.toStringTag
const myObject = {};
Object.defineProperty(myObject, Symbol.toStringTag, {
value: "CustomType",
});
console.log(Object.prototype.toString.call(myObject)); // "[object CustomType]"
// 内置对象也使用Symbol.toStringTag
console.log(Object.prototype.toString.call(new Map())); // "[object Map]"
console.log(Object.prototype.toString.call(Promise.resolve())); // "[object Promise]"
利用 Symbol.toStringTag 实现精确的类型检测
我们可以创建一个更强大的类型检测函数,它能够识别包括内置类型和自定义类型在内的所有对象类型:
js
function getExactType(value) {
if (value === null) {
return "null";
}
if (value === undefined) {
return "undefined";
}
// 处理原始类型
if (typeof value !== "object" && typeof value !== "function") {
return typeof value;
}
// 从Object.prototype.toString中提取类型信息
const objectString = Object.prototype.toString.call(value);
const type = objectString.slice(8, -1); // 移除 "[object " 和 "]"
return type;
}
// 测试各种类型
console.log(getExactType(42)); // "number"
console.log(getExactType("hello")); // "string"
console.log(getExactType(true)); // "boolean"
console.log(getExactType(undefined)); // "undefined"
console.log(getExactType(null)); // "null"
console.log(getExactType(Symbol())); // "Symbol"
console.log(getExactType([])); // "Array"
console.log(getExactType({})); // "Object"
console.log(getExactType(new Date())); // "Date"
console.log(getExactType(new Map())); // "Map"
console.log(getExactType(new Set())); // "Set"
console.log(getExactType(() => {})); // "Function"
为自定义类型实现 Symbol.toStringTag
我们可以为自定义类添加Symbol.toStringTag
属性,使其能够被准确识别:
js
class Person {
constructor(name) {
this.name = name;
}
// 定义Symbol.toStringTag getter
get [Symbol.toStringTag]() {
return "Person";
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
get [Symbol.toStringTag]() {
return "Student";
}
}
const person = new Person("Alice");
const student = new Student("Bob", 12);
console.log(Object.prototype.toString.call(person)); // "[object Person]"
console.log(Object.prototype.toString.call(student)); // "[object Student]"
console.log(getExactType(person)); // "Person"
console.log(getExactType(student)); // "Student"
Symbol.toStringTag 与类型检查的实际应用
使用Symbol.toStringTag
进行类型检查在实际开发中十分有用,尤其是在以下场景:
- 检测环境内置对象:例如检查是否支持某些特定的 API
js
function isArrayBuffer(obj) {
return Object.prototype.toString.call(obj) === "[object ArrayBuffer]";
}
function isBlob(obj) {
return Object.prototype.toString.call(obj) === "[object Blob]";
}
// 更通用的解决方案
function isTypeOf(obj, typeName) {
return Object.prototype.toString.call(obj) === `[object ${typeName}]`;
}
console.log(isTypeOf(new Blob(), "Blob")); // true
console.log(isTypeOf(new ArrayBuffer(8), "ArrayBuffer")); // true
- 创建类型安全的函数:确保传入参数符合预期类型
js
function processCollection(collection) {
const type = getExactType(collection);
if (type === "Array") {
// 处理数组...
return collection.length;
}
if (type === "Set") {
// 处理Set...
return collection.size;
}
if (type === "Map") {
// 处理Map...
return collection.size;
}
throw new TypeError(`Expected Array, Set or Map, got ${type}`);
}
- 实现多态行为:根据对象类型执行不同操作
js
function stringify(value) {
const type = getExactType(value);
switch (type) {
case "Date":
return value.toISOString();
case "RegExp":
return value.toString();
case "Array":
case "Object":
return JSON.stringify(value);
case "Map":
case "Set":
return JSON.stringify(Array.from(value));
default:
return String(value);
}
}
通过正确使用Symbol.toStringTag
和Object.prototype.toString.call()
,我们可以实现比原生typeof
运算符更精确可靠的类型检测系统,这在复杂应用的开发中尤为重要。
总结
typeof
运算符用于检查变量的类型,返回一个表示数据类型的字符串。对于基本类型(如 number
、string
、boolean
、undefined
、symbol
、bigint
),它的返回值非常明确。然而,对于所有对象类型(如数组、正则表达式、日期、null
等),typeof
都返回 "object"
,这在实际开发中可能造成误判,比如 typeof null === "object"
就是一个广为人知的历史遗留问题。
为了解决 typeof
在判断复杂对象类型时的局限性,可以使用 Object.prototype.toString.call(value)
方法。该方法返回更精确的类型标签,例如:
js
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(new Date()); // "[object Date]"
这种方式可以准确识别对象的原始内部类型标签([[Class]]
),是进行类型判断的推荐方案。
此外,ES6 引入了 Symbol.toStringTag
,允许我们自定义 Object.prototype.toString.call()
的返回值。通过在对象上定义该 Symbol 属性,可以"伪装"成其他类型:
js
const customType = {
[Symbol.toStringTag]: "Custom",
};
console.log(Object.prototype.toString.call(customType)); // "[object Custom]"
这在库设计中非常有用,可提升类型信息的可读性和表达力。
总之,typeof
适合快速判断基本类型,而对于复杂类型,应优先使用 Object.prototype.toString.call()
,在需要自定义类型表现时,可结合 Symbol.toStringTag
提升语义清晰度和可控性。