javascript数据类型
ECMAScript 标准定义了 8 种数据类型,分为两大类:原始类型(Primitive Types) 和 对象类型(Object Type)。
- 7 种原始类型:
String、Number、BigInt、Boolean、Undefined、Null、Symbol - 1 种对象类型:
Object
1. 原始类型 (Primitive Types)
原始类型是不可变的值,直接存储在语言的最底层。
1.1 String (字符串)
- 描述: 用于表示文本数据,是由 16 位无符号整数值组成的序列,采用 UTF-16 编码。 字符串是不可变的,一旦创建,就无法修改。
- 常见问题:
- "Stringly-typing": 避免使用字符串来表示复杂的数据结构(如用特殊字符分隔的列表)。这会增加不必要的维护成本,应使用数组或对象等更合适的抽象。
- 字符长度:
length属性返回的是 UTF-16 码元的数量,可能不完全对应 Unicode 字符的实际数量(例如 emoji 表情)。
- 示例:js
let name = "Gemini"; let text = 'Hello, World!'; let template = `My name is ${name}`;
1.2 Number (数字)
- 描述: 采用双精度 64 位二进制格式(IEEE 754 标准)。 它可以表示整数和浮点数。
- 常见问题:
- 精度问题: 由于采用浮点数表示,涉及小数的运算可能不精确。例如
0.1 + 0.2并不严格等于0.3。 - 安全整数范围: js 只能安全地表示
-(2^53 - 1)到2^53 - 1之间的整数。 超出这个范围的整数可能会失去精度。 可以使用Number.isSafeInteger()来检查。 - 特殊值:
NaN(Not a Number): 表示一个无法用数字表达的操作结果。一个特殊之处在于NaN不等于它自身 (NaN !== NaN)。Infinity和-Infinity: 表示正负无穷大。
- 精度问题: 由于采用浮点数表示,涉及小数的运算可能不精确。例如
- 示例:js
let integer = 42; let float = 3.14; let notANumber = NaN; let infinity = Infinity;
1.3 BigInt (大整数)
- 描述: 用于表示任意精度的整数,可以安全地存储和操作超出
Number安全整数范围的大整数。 通过在整数末尾添加n或调用BigInt()函数创建。 - 常见问题:
- 类型混合: 不能将
BigInt和Number类型的值混合在算术表达式中,这会抛出TypeError。 - 小数:
BigInt无法表示小数。
- 类型混合: 不能将
- 示例:js
const bigNumber = 9007199254740991n; const alsoBig = BigInt("12345678901234567890");
1.4 Boolean (布尔)
- 描述: 表示逻辑实体,只有两个值:
true和false。 常用于条件判断。 - 常见问题:
- Truthy & Falsy: 在需要布尔值的上下文中(如
if语句),非布尔值会被隐式转换为布尔值。false、0、""、null、undefined和NaN都会被转换为false,其他所有值都被转换为true。
- Truthy & Falsy: 在需要布尔值的上下文中(如
- 示例:js
let isTrue = true; let isFalse = false;
1.5 Undefined
- 描述: 表示一个未被赋值的变量。 当一个变量被声明但没有初始化时,其默认值就是
undefined。 - 常见问题:
- 与
null的区别:undefined通常表示“值的缺失”,是语言的默认行为。而null是开发者主动赋值,表示“对象的缺失”或“空值”。 - 非关键词:
undefined是一个全局属性,而不是一个关键词。在现代 js 环境中虽然不能被重写,但在旧环境中可能存在被修改的风险。
- 与
- 示例:js
let uninitialized; // 值为 undefined
1.6 Null
- 描述: 表示一个“空”或“无”的值,由开发者主动赋值。 它是一个只有一个值
null的原始类型。 - 常见问题:
typeof null的结果:typeof null返回"object",这是一个历史遗留的 Bug。 因此,检查一个值是否为null时,应使用=== null。
- 示例:js
let emptyValue = null;
1.7 Symbol (符号)
- 描述:
Symbol是唯一且不可变的值,主要用于创建唯一的对象属性键,以避免命名冲突。 - 常见问题:
- 非字符串:
Symbol类型的值不能被自动转换为字符串,尝试拼接会抛出错误。 - 枚举: 使用
Symbol作为键的属性不会被for...in循环或Object.keys()枚举。需要使用Object.getOwnPropertySymbols()。
- 非字符串:
- 示例:js
const id = Symbol('unique id'); const obj = { [id]: 123 };
1.8 对象类型 (Object Type)
- 描述:
Object是一个复杂的数据类型,用于存储键值对的集合。 数组、函数、日期等都是特殊的对象。 - 常见问题:
- 引用传递: 对象的赋值是引用传递。当将一个对象赋给另一个变量时,两个变量指向同一个内存地址。修改其中一个会影响另一个。
typeof的限制:typeof对所有对象类型(包括数组、null)都返回"object",无法进行细致区分。
- 示例:js
const person = { firstName: "John", lastName: "Doe" }; const fruits = ["Apple", "Banana", "Orange"]; const today = new Date();
2. 数据类型鉴别方式
鉴别 js 中的数据类型有多种方法,每种方法都有其适用场景和局限性。
2.1 typeof 运算符
typeof 是最基础、最便捷的类型检测方式。它以字符串形式返回操作数的数据类型。
✨ 用法
typeof 42; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol("id"); // "symbol"
typeof 123n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof function(){}; // "function"🚨 常见问题与陷阱
typeof 虽然简单,但存在一些使其在某些场景下不可靠的“怪异”行为:
| 表达式 | 结果 | 原因/说明 |
|---|---|---|
typeof null | "object" | 这是 JavaScript 历史上一个著名的 bug,并因历史原因被保留下来。null 应该被认为是其自身的原始类型。 |
typeof [] | "object" | typeof 无法区分数组、对象、RegExp 等,因为它们在底层都被视为对象。 |
typeof new String("a") | "object" | 对于通过构造函数创建的包装对象,typeof 会返回 "object",而不是其对应的原始类型。 |
✅ 适用场景
- 主要用于判断原始类型(
string,number,boolean,undefined,symbol,bigint)。 - 用于判断一个变量是否为
function。 - 不适合用来精确判断
null或除function外的任何引用类型。
2.2 instanceof 运算符
instanceof 用于检测一个构造函数的 prototype 属性是否存在于某个实例对象的原型链上。简单来说,它回答的是:“这个对象是某个构造函数的实例吗?”
✨ 用法
const arr = [];
const obj = {};
const now = new Date();
arr instanceof Array; // true
arr instanceof Object; // true (因为 Array.prototype 继承自 Object.prototype)
obj instanceof Object; // true
now instanceof Date; // true🚨 常见问题与陷阱
| 问题 | 描述 |
|---|---|
| 对原始类型无效 | instanceof 只能用于对象,不能用于原始类型。"hello" instanceof String 的结果是 false。 |
| 多窗口/多框架问题 | 如果页面中有多个 iframe 或 window,每个执行环境都有自己的全局作用域和构造函数。在一个 iframe 中创建的数组,在另一个 iframe 中使用 instanceof Array 检测会返回 false,因为它们各自的 Array 构造函数不同。 |
✅ 适用场景
- 判断一个对象是否是某个特定类的实例,或者是否在某条特定的原型链上。
- 适用于自定义类的实例判断。
2.3 constructor 属性
每个实例对象都有一个 constructor 属性,它指向创建该实例的构造函数。
✨ 用法
const num = 1;
const str = "hi";
const arr = [];
console.log(arr.constructor === Array); // true
console.log(str.constructor === String); // true
console.log(num.constructor === Number); // true🚨 常见问题与陷阱
| 问题 | 描述 |
|---|---|
| 不稳定性 | constructor 属性是可以被轻易修改的。如果手动改变了一个对象的 prototype.constructor,那么检测结果将完全不可靠。 |
null 和 undefined | null 和 undefined 没有 constructor 属性,直接访问会报错。 |
function MyClass() {}
MyClass.prototype.constructor = Array; // 手动修改 constructor
const instance = new MyClass();
console.log(instance.constructor === Array); // true,但 instance 并非数组✅ 适用场景
- 在可以确保
constructor未被修改的受控环境下,可以作为一种判断方式。 - 通常不推荐作为首选的类型判断方法,因为它不可靠。
2.4 Object.prototype.toString.call() (终极方案)
这是目前公认的最准确、最可靠的类型鉴别方法。Object.prototype.toString() 是一个原生方法,当用 .call() 或 .apply() 在不同类型的变量上调用时,它会返回一个统一格式的字符串:"[object Type]",其中 Type 就是变量的精确类型。
✨ 用法
const toString = Object.prototype.toString;
toString.call(123); // "[object Number]"
toString.call("abc"); // "[object String]"
toString.call(true); // "[object Boolean]"
toString.call(undefined); // "[object Undefined]"
toString.call(null); // "[object Null]" (正确区分了 null)
toString.call([]); // "[object Array]" (正确区分了数组)
toString.call({}); // "[object Object]"
toString.call(new Date()); // "[object Date]"
toString.call(/a/); // "[object RegExp]"
toString.call(new Error()); // "[object Error]"
toString.call(window); // "[object Window]" (在浏览器中)封装成一个通用的 getType 函数
为了方便使用,通常会将它封装成一个工具函数。
function getType(value) {
if (value === null) {
return "null";
}
// 处理原始类型和函数
const type = typeof value;
if (type !== "object") {
return type;
}
// 处理引用类型
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
console.log(getType(null)); // "null"
console.log(getType([])); // "array"
console.log(getType(new Date())); // "date"✅ 适用场景
- 任何需要精确判断数据类型的场景。
- 能够区分普通对象
Object、数组Array、null以及其他所有内置对象类型。 - 能够跨
iframe和window工作,不存在instanceof的问题。 - 是编写健壮的库和框架时的不二之选。
2.5 总结对比
| 方法 | 优点 | 缺点 | 推荐指数 |
|---|---|---|---|
typeof | 简单快速,适合判断原始类型和 function | 无法区分 null、数组和对象 | ⭐⭐⭐ |
instanceof | 能判断对象是否在某条原型链上,适合自定义类 | 对原始类型无效,有跨 iframe 问题 | ⭐⭐⭐⭐ |
constructor | 语法直观 | 属性可被修改,不可靠 | ⭐⭐ |
Object.prototype.toString.call() | 最准确、最可靠,能区分所有类型,无兼容性问题 | 语法稍显繁琐,通常需要封装 | ⭐⭐⭐⭐⭐ |
3. 数据类型转换
JavaScript 是一种弱类型 (Weakly Typed) 语言,这意味着变量的数据类型是可以随时改变的,且在不同类型的数据进行运算时,JS 引擎会自动进行隐式转换,掌握类型转换是避免“奇怪 Bug”和通过前端面试的关键。
3.1 转换的两种形式
- 显式转换 (Explicit):代码中明确写出了转换逻辑。
- 例如:
Number("123"),String(123),Boolean(1).
- 例如:
- 隐式转换 (Implicit):由运算符或语句触发,JS 引擎自动完成。
- 例如:
"1" + 2(变为字符串),if (1)(变为布尔),1 == "1"(比较时转换).
- 例如:
3.2 三大底层抽象操作
ECMAScript 规范定义了几个内部操作,用于处理不同类型间的转换。理解它们是理解一切转换的基础。
1. ToPrimitive (转为原始值)
当对象需要被转为原始值时调用(例如对象相加、比较)。
- 逻辑:检查
Symbol.toPrimitive-> 调用valueOf()-> 调用toString()。
2. ToBoolean (转为布尔值)
JS 中只有极少数的值会被转换为 false,其他全是 true。
| 假值 (Falsy Values) | 说明 |
|---|---|
false | 布尔假 |
undefined | 未定义 |
null | 空值 |
0, -0 | 数字零 |
NaN | 非数字 |
"" / '' / | 空字符串 |
document.all | (浏览器历史遗留,特殊对象) |
注意:
[](空数组) and{}(空对象) 都是 true。
3. ToNumber (转为数字)
| 输入类型 | 结果 |
|---|---|
undefined | NaN (重点) |
null | 0 (重点) |
true | 1 |
false | 0 |
| String | 如果纯数字则解析为数字;空串为 0;含非数字字符则为 NaN |
| Symbol | 抛出 TypeError |
| Object | 先 ToPrimitive 转为原始值,再按上述规则转数字 |
4. ToString (转为字符串)
- 基本类型直接转字符串(
null->"null",undefined->"undefined")。 - 对象类型:先
ToPrimitive,通常调用toString()。{}->"[object Object]"[1,2]->"1,2"
3.3 显式转换 (Explicit Coercion)
通常为了代码清晰,建议使用显式转换。
1. 转数字
Number(val):严格转换。只要有一个字符无法解析(除了空格),就返回NaN。parseInt(val)/parseFloat(val):解析转换。从左向右解析,遇到非数字停止。
核心差异速查表
| 特性 | parseInt(string, radix) | parseFloat(string) | Number(value) |
|---|---|---|---|
| 核心目的 | 从字符串中解析出第一个整数 | 从字符串中解析出第一个浮点数 | 将整个值严格转换为数字 |
| 处理方式 | 从头读取,直到遇到非数字字符就停止 | 从头读取,直到遇到第二个小数点或非数字字符就停止 | “要么全部,要么没有”。任何无效字符都会导致失败 |
| 处理非数字 | 忽略字符串末尾的非数字部分 | 忽略字符串末尾的非数字部分 | 失败,返回 NaN |
| 处理小数点 | 忽略小数点及其之后的所有内容 | 识别并解析第一个小数点 | 识别并解析 |
处理空字符串 "" | NaN | NaN | 0 |
处理 null | NaN | NaN | 0 |
| 处理十六进制 | 需要指定基数 16 | 不支持 (0) | 支持 (0x...) |
| 推荐用法 | 从字符串中提取像素值、ID等整数 | 从字符串中提取货币、尺寸等小数值 | 验证用户输入、进行精确的数据类型转换 |
2. 转字符串
String(val):适用于所有类型(包括 null/undefined)。val.toString():适用于除 null/undefined 以外的类型。
3. 转布尔
Boolean(val)!!val(双重取反,最常用)
3.4 隐式转换 (Implicit Coercion)
这是最容易出错的地方,主要发生在运算符操作中。
1. 二元 + 运算符
规则:
- 如果有一侧是 字符串,则按照 字符串拼接 处理(另一侧调用
ToString)。 - 如果两侧都不是字符串,则按照 数字加法 处理(两侧调用
ToNumber)。
1 + "1" // "11" (拼接)
true + true // 2 (1 + 1)
4 + [1,2,3] // "41,2,3" (数组转字符串是 "1,2,3")
10 + {} // "10[object Object]"2. 数学运算符 (-, *, /, %)
规则:除了 + 以外的数学运算符,一律将操作数转换为 数字 (ToNumber)。
"100" - 10 // 90
100 * "2" // 200
10 - "abc" // NaN
1 - null // 1 (1 - 0)
1 - undefined // NaN (1 - NaN)3. 逻辑非 (!) 和 逻辑运算
规则:转换为 布尔值 (ToBoolean)。
![] // false (数组是真值,取反为假)
!!"0" // true (非空字符串是真值)4. 宽松相等运算符 (== 和 !=)
这是 JavaScript 中一个复杂且容易出错的部分。它在比较之前会尝试转换值的类型。
4.1 工作原理
a == b 的比较会经过一个复杂的抽象相等比较算法,主要规则如下:
- 类型相同? -> 行为与
===相同。 null == undefined? ->true(这是唯一的特例)。Number == String? -> 将String转换为Number再比较。Boolean == 任何类型? -> 将Boolean转换为Number(true->1,false->0) 再比较。Object == (String | Number | Symbol)? -> 将Object转换为原始类型(会调用内部的 ToPrimitive 操作。这个操作会根据上下文决定优先调用valueOf()还是toString()方法`)再比较。

4.2 示例
| 表达式 | 结果 | 转换过程 |
|---|---|---|
77 == "77" | true | "77" -> 77 |
true == 1 | true | true -> 1 |
false == 0 | true | false -> 0 |
"" == 0 | true | "" -> 0 |
"\n " == 0 | true | "\n " -> "" -> 0 |
[1] == 1 | true | [1] -> "1" -> 1 |
[] == 0 | true | [] -> "" -> 0 |
[] == false | true | [] -> "" -> 0; false -> 0 |
null == undefined | true | 规范中的特例。 |
[]==![] | true | [] -> "" -> 0;![]->false -> 0 |
[]==[] | false | 两个数组的内存地址不同 |
{}=={} | false | 两个对象的内存地址不同 |
{}==!{} | false | {}->'[object object]'->NaN;!{}->false |
❌ 为什么应避免使用
==: 它的类型转换规则复杂、不直观,是许多常见 bug 的根源。你很难记住所有的转换规则,这使得代码的行为难以预测。
3.5 同值相等 (Object.is())
Object.is() 是 ES6 引入的,用于判断两个值是否为“相同的值”。它被认为是比 === 更精确的比较方式。
1. 工作原理
Object.is() 的行为与 === 基本完全相同,除了两个特例:
Object.is(NaN, NaN)->true(与===不同)Object.is(+0, -0)->false(与===不同)
object.is=function(a,b){
// 情况 1: 处理 +0 和 -0
// 如果 x 和 y 都是 0,它们通过 `===` 比较会是 true。
// 但我们需要区分 +0 和 -0。
// 技巧:1 / +0 === Infinity,而 1 / -0 === -Infinity。
// 所以,如果它们的倒数不相等,说明一个是 +0,另一个是 -0。
if (x === 0 && y === 0) {
return 1 / x === 1 / y;
}
// 情况 2: 处理 NaN
// NaN 是唯一一个不等于自身的值 (NaN !== NaN)。
// 所以,如果 x 不等于 x,那么 x 就是 NaN。
// 如果 x 和 y 都是 NaN,那么它们应该被认为是相等的。
if (x !== x) {
return y !== y;
}
// 情况 3: 其他所有情况
// 对于所有其他值 (包括 null, undefined, string, boolean, object引用等),
// Object.is() 的行为与 === 完全相同。
return x === y;
}2. 示例
| 表达式 | 结果 |
|---|---|
Object.is(77, "77") | false |
Object.is({}, {}) | false |
Object.is(NaN, NaN) | true |
Object.is(+0, -0) | false |
用途:
Object.is()在某些需要精确区分NaN或+0和-0的数学计算或算法场景中非常有用。在日常的业务逻辑中,===依然是首选。
3.6 零值相等 (SameValueZero)
定义:它与“同值相等 (SameValue)”几乎一模一样,唯一的区别在于它认为 +0 和 -0 是相等的。
如何触发:你不能直接通过某个操作符或函数调用它。它是 JS 引擎在实现 Set、Map 以及数组的 includes() 方法 时内部使用的算法。
核心特征:
- 对待
NaN:认为NaN等于NaN。(和 SameValue 一样) - 对待
+0和-0:认为+0等于-0。(这和 SameValue 不同,更符合普通人的直觉)。
四种相等性判断的终极对比表
为了彻底搞懂它们,我们用一张表格来对比这四种相等性判断在边缘情况下的表现:
| 比较的值 | == (抽象相等) | === (严格相等) | SameValueZero (Set/Map/includes 使用) | SameValue (Object.is() 使用) |
|---|---|---|---|---|
NaN 与 NaN | false | false | true | true |
+0 与 -0 | true | true | true | false |
5 与 "5" | true (发生类型转换) | false | false | false |
null 与 undefined | true | false | false | false |
为什么 Set 和 Map 要使用 SameValueZero?
这是 ECMAScript 委员会经过深思熟虑后的决定:
- 为什么
NaN要等于NaN? 如果Set使用===,那么你可以向Set中无限添加NaN,因为NaN !== NaN。这违背了集合“元素唯一”的初衷。所以引擎内部规定在这里NaN是重复的。 - 为什么
+0要等于-0? 虽然Object.is能区分它们,但在绝大多数日常业务开发中,开发者并不关心正零和负零的区别。如果你把+0作为Map的键存入数据,然后用-0去取却取不到,这会让人非常困惑。为了符合开发者的直觉,选择了SameValueZero,将它们视为相同的键/值。
代码验证:
const map = new Map();
map.set(+0, "正零");
// 因为 Map 使用 SameValueZero,所以认为 -0 和 +0 是同一个键
console.log(map.get(-0)); // 输出: "正零"
const set = new Set();
set.add(NaN);
set.add(NaN);
console.log(set.size); // 输出: 1 (去重成功)4. 数据类型运算符(Operator Precedence)
当一个表达式中包含多个不同的运算符时,JavaScript 会根据一个固定的优先级顺序来决定先计算哪个部分。优先级越高的运算符越先被执行。
4.1 JavaScript 运算符详解
JavaScript 提供了丰富的运算符,用于执行各种操作,从数学计算到逻辑判断。
1. 赋值运算符 (Assignment Operators)
用于将右侧操作数的值赋给左侧操作数。
| 运算符 | 名称 | 示例 | 等同于 |
|---|---|---|---|
= | 赋值 | x = y | x = y |
+= | 加法赋值 | x += y | x = x + y |
-= | 减法赋值 | x -= y | x = x - y |
*= | 乘法赋值 | x *= y | x = x * y |
/= | 除法赋值 | x /= y | x = x / y |
%= | 取模赋值 | x %= y | x = x % y |
**= | 幂赋值 | x **= y | x = x ** y |
2. 比较运算符 (Comparison Operators)
用于比较两个操作数,并返回一个表示比较结果的布尔值 (true 或 false)。
| 运算符 | 名称 | 描述 |
|---|---|---|
== | 等于 | 比较两个操作数的值是否相等,会进行类型转换。 |
!= | 不等于 | 比较两个操作数的值是否不相等,会进行类型转换。 |
=== | 全等 | 比较两个操作数的值和类型是否都相等,不进行类型转换。 |
!== | 不全等 | 比较两个操作数的值或类型是否不相等,不进行类型转换。 |
> | 大于 | |
< | 小于 | |
>= | 大于或等于 | |
<= | 小于或等于 |
3. 算术运算符 (Arithmetic Operators)
用于执行数学计算。
| 运算符 | 名称 | 描述 |
|---|---|---|
+ | 加法 | |
- | 减法 | |
* | 乘法 | |
/ | 除法 | |
% | 取模 (求余数) | |
** | 幂 | |
++ | 自增 | |
-- | 自减 |
4. 逻辑运算符 (Logical Operators)
通常用于布尔值之间,返回一个布尔值。
| 运算符 | 名称 | 描述 |
|---|---|---|
&& | 逻辑与 (AND) | |
| ` | ` | |
! | 逻辑非 (NOT) |
5. 位运算符 (Bitwise Operators)
将操作数视为 32 位二进制序列(0 和 1)进行运算。
| 运算符 | 名称 |
|---|---|
& | 按位与 |
| ` | ` |
^ | 按位异或 |
~ | 按位非 |
<< | 按位左移 |
>> | 有符号右移 |
>>> | 无符号右移 |
6. 其他运算符
| 运算符 | 名称 | 描述 |
|---|---|---|
typeof | 类型 | 返回一个表示操作数类型的字符串。 |
instanceof | 实例 | 判断一个对象是否是某个构造函数的实例。 |
? : | 条件 (三元) | condition ? val1 : val2,如果条件为真,返回 val1,否则返回 val2。 |
delete | 删除 | 删除对象的属性。 |
in | 属性检查 | 如果指定的属性在指定的对象或其原型链中,则返回 true。 |
, | 逗号 | |
?? | 空值合并 | a ?? b,如果 a 是 null 或 undefined,则返回 b,否则返回 a。 |
4.2 运算符优先级 (Operator Precedence)
运算符的优先级决定了在表达式中运算执行的先后顺序。 优先级高的运算符会先被求值。
function Foo() {
getName = function() { //1
console.log(1);
};
return this;
}
Foo.getName = function() { //2
console.log(2);
};
Foo.prototype.getName = function() { //3
console.log(3);
};
var getName = function() { //4
console.log(4);
};
function getName() { //5
console.log(5);
}
//let foo=new Foo() 默认foo为Foo实例
Foo.getName(); //直接调用Foo构造函数的静态方法getName()=>执行函数2=>返回2
getName(); //函数5直接函数提升,函数4是函数表达式无法提升,覆盖函数5=>执行函数4=>返回4
Foo().getName(); //直接调用Foo函数内部的getName函数=>执行函数1=>getName变量是全局的,等效于window.getName,会覆盖全局的getName方法=>返回1
getName(); //全局getName函数被函数1覆盖=>执行函数1=>返回1
new Foo.getName(); //new Foo.getName()=>new (Foo.getName())=>执行Foo的静态方法getName=>执行函数2=>new 2 => 返回2
new Foo().getName(); // new Foo().getName()=>(new Foo()).getName()=>foo.getName()(实例对象执行的是FOO构造函数的getName原型方法)=>返回3
new new Foo().getName(); //new new Foo().getName()=>new ((new Foo()).getName())=>new (foo.getName())=>new 3=>3
//2 4 1 1 2 3 3| 优先级 | 运算符 | 描述 | 结合性 |
|---|---|---|---|
| 21 | ( ... ) | 分组 | 不适用 |
| 20 | .、[]、new (带参数)、()、?. | 成员访问、函数调用、可选链 | 从左到右 |
| 19 | new (无参数) | 实例化 | 从右到左 |
| 18 | ++ -- | 后置自增/自减 | 不适用 |
| 17 | ! ~ + - ++ -- typeof void delete await | 逻辑非、按位非、一元正负、前置自增/自减、类型、删除 | 从右到左 |
| 16 | ** | 幂 | 从右到左 |
| 15 | * / % | 乘、除、取模 | 从左到右 |
| 14 | + - | 加、减 | 从左到右 |
| 13 | << >> >>> | 按位移位 | 从左到右 |
| 12 | < <= > >= in instanceof | 关系、实例 | 从左到右 |
| 11 | == != === !== | 相等性 | 从左到右 |
| 10 | & | 按位与 | 从左到右 |
| 9 | ^ | 按位异或 | 从左到右 |
| 8 | ` | ` | 按位或 |
| 7 | && | 逻辑与 | 从左到右 |
| 6 | ` | ` | |
| 5 | ?? | 空值合并 | 从左到右 |
| 4 | ? : | 条件 (三元) | 从右到左 |
| 3 | = += -= **= *= /= %= <<= >>= >>>= &= ^= ` | =` | 赋值 |
| 2 | yield | 从右到左 | |
| 1 | , | 逗号 | 从左到右 |
关键记忆点:
- 括号最高:
()用于强制改变运算顺序。 - 一元运算符 优先级很高。
- 算术 优于 关系,关系 优于 逻辑,逻辑 优于 赋值。
- 赋值 运算符的结合性是从右到左,这使得链式赋值成为可能:
a = b = 5;。
5. 经典面试题解析 (Wat Moments)
5.1 [] == ![]
结果:true
解析步骤:
- 右边:
![]。[]是真值,![]变为false。- 比较变为:
[] == false
- 比较变为:
- 遇到 Boolean:
false转为数字0。- 比较变为:
[] == 0
- 比较变为:
- 遇到 Object vs Number:
[]调用valueOf(是数组本身) ->toString()(是空串"")。- 比较变为:
"" == 0
- 比较变为:
- 遇到 String vs Number:
""转为数字0。- 比较变为:
0 == 0-> true
- 比较变为:
5.2 {} + [] vs [] + {}
这取决于在浏览器控制台还是脚本中运行。
[] + {}[]->""{}->"[object Object]"- 结果:
"[object Object]"
{} + []- 在旧版本控制台或特定解析中,
{}被当做空代码块(Block)直接忽略。 - 实际执行的是
+ [](一元加号,强制转数字)。 []->""->0。- 结果:
0(但在现代浏览器和 Node 环境中,通常也被解析为字符串拼接"[object Object]").
- 在旧版本控制台或特定解析中,
5.3 true、false 与数字的比较
现象: if (myVar == true) 这样的代码可能不会按预期工作。
代码示例:
const value = "1";
if (value == true) { // "1" == true -> "1" == 1 -> 1 == 1 -> true
console.log("Value is true"); // ✅ 执行
}
const text = "hello";
if (text == true) { // "hello" == true -> NaN == 1 -> false
console.log("Text is true"); // ❌ 不执行
}✅ 解决方案: 永远不要用 == true 或 == false 来检查真值。直接利用值的“真值性” (truthiness)。
// 正确的方式
if (value) {
console.log("Value is truthy");
}5.4 空数组与 false 的比较
现象: [] 本身是一个“真值” (truthy),但 [] == false 的结果却是 true。
代码示例:
if ([]) {
console.log("Empty array is truthy"); // ✅ 执行
}
console.log([] == false); // true原因 (逐步转换):
[] == false[]是对象,false是布尔值。false转换为数字0->[] == 0[]尝试转换为原始类型,调用toString()->"" == 0""字符串转换为空数字0->0 == 0- 结果为
true。
✅ 解决方案: 使用 ===,或者通过 array.length 来判断数组是否为空。
5.5 null 与 0 的比较
现象: null == 0 是 false,但 null > 0 和 null >= 0 却表现不一。
代码示例:
console.log(null == 0); // false (宽松相等有 null 和 undefined 的特例)
console.log(null > 0); // false (关系比较中,null -> 0)
console.log(null >= 0); // true (关系比较中,null -> 0)✅ 解决方案: 同样,使用 === 进行显式检查,避免依赖 null 的隐式转换。
const value = null;
if (value === 0) {
// ...
}
if (value === null) {
// ...
}6. 总结
- 永远优先使用
===,除非你真的想判断null或undefined(if (obj == null)是唯一推荐的==用法)。 - 显式转换优于隐式转换。
Number(str)比str * 1可读性更好。 - 记住 Falsy 值,其他全为真。
| 运算符 | 描述 | 类型转换 | 适合场景 |
|---|---|---|---|
=== | 严格相等 | 否 | 99% 的情况。 你的默认选择。 |
== | 宽松相等 | 是 | 几乎从不。唯一的例外可能是 val == null (等价于 `val === null |
Object.is() | 同值相等 | 否 | 需要精确区分 NaN 或 +0 / -0 的特殊情况。 |