操作符和表达式✅
== 和 === 区别
答案
==内部调用 IsLooselyEqual 宽松比较 将不同类型的值转换为为同类型后再进行比较,===内部调用 IsStrictlyEqual 严格比较 如果类型不同直接返回 false,类型相同再进行值比较!
宽松比较的核心类型转换规则为
- 原始类型比对时中 String、Boolean 都会调用 ToNumber 转换为数值后比较
- 对象比对时会调用 ToPrimitive 转换为原始值后在回退到规则 1 进行比较
- 注意一个特殊的细节,是
document.all == false返回 ture,stackoverflow Why is document.all falsy? 了解细节
提示
这里我们以 [] == false 为例,基于规范详细说明为什么会返回 true
==比较触发内部 IsLooselyEqual 宽松比较[]是一个对象,调用 ToPrimitive 转换为原始值- ToPrimitive 首先尝试调用对象实例内部
valueOf方法,返回[],然后尝试调用toString方法,返回空字符串"" - 然后变为
"" == false比较 - false 进一步调用 ToNumber 转换为数值 0
"" == 0,""调用 ToNumber 转换为数值 0- 数值 0 == 0 返回 true
注意宽松比较不会抛出错误,所以规范中会递归调用直到返回一个结果,但是引擎实现时,会根据具体情况进行优化,可能会提前返回结果。
typeof 和 instanceof 的区别
答案
| 特性 | typeof | instanceof |
|---|---|---|
| 用途 | 判断数据类型 | 判断实例归属关系 |
| 返回值 | 字符串 | 布尔值 |
| 适用范围 | 基本类型/引用类型 | 引用类型(对象) |
| 局限性/注意点 | null 返回 "object",无法区分数组/对象 | 不能判断基本类型,依赖原型链 |
总结:
typeof适合判断基本类型,部分引用类型(如函数),但无法区分数组和对象,null返回"object"。instanceof用于判断对象是否为某构造函数的实例,适合引用类型,无法判断基本类型。
操作符优先级
答案
操作符优先级(Operator Precedence)决定了在表达式中多个操作符同时出现时,谁先参与运算。优先级高的操作符会比优先级低的操作符先执行。
如果没有优先级,表达式如 2 + 3 * 4 就无法确定是先加还是先乘。优先级规则保证了表达式的计算顺序符合数学和语言设计的直觉。
核心规则
- 括号
()拥有最高优先级,可强制改变默认顺序。 - 一元操作符(如
!,typeof,++,--)优先级高于二元操作符(如+,-,*,/)。 - 乘除(
*,/,%)高于加减(+,-)。 - 赋值操作符(
=,+=,-=等)优先级较低,通常最后执行。 - 逻辑与(
&&)高于逻辑或(||)。
常见错误
- 忽略括号导致运算顺序错误,如
a + b * c实际为a + (b * c)。 - 连续赋值时,
a = b = c实际为a = (b = c),右侧先执行。 - 混用逻辑与或时,
a || b && c实际为a || (b && c)。
优先级表,数字越大优先级越高:
| 优先级(高→低) | 操作符 | 说明 |
|---|---|---|
| 20 | () | 圆括号 |
| 17 | ++, --, !, typeof | 一元操作符 |
| 15 | *, /, % | 乘、除、取余 |
| 14 | +, - | 加、减 |
| 13 | <<, >>, >>> | 位移 |
| 12 | <, >, <=, >= | 比较 |
| 11 | ==, !=, ===, !== | 相等/不等 |
| 6 | && | 逻辑与 |
| 5 | ` | |
| 3 | =, +=, -= | 赋值 |
建议:表达式复杂时,优先使用括号明确运算顺序,避免歧义和潜在 bug。
延伸阅读
说明下面的代码运行结果
(function () {
const a = b = 3
})()
console.log('a defined? ' + (typeof a !== 'undefined'))
console.log('b defined? ' + (typeof b !== 'undefined'))
答案
运行结果是:
a defined? false
b defined? true
详细解释:
-
const a = b = 3这行代码其实等价于:b = 3:由于没有用var/let/const声明,b变成了全局变量(在浏览器环境下会挂载到window上)。const a = b:a只是当前函数作用域内的常量。
-
由于整个赋值语句在一个自执行函数
(function () { ... })()内部,a只在函数作用域内有效,外部访问不到。 -
b虽然在函数内部赋值,但由于没有用var/let/const声明,实际上是给全局对象添加了一个属性,所以函数外部也能访问到。 -
console.log('a defined? ' + (typeof a !== 'undefined'))a在全局作用域未定义,typeof a返回'undefined',所以结果是false。
-
console.log('b defined? ' + (typeof b !== 'undefined'))b已经变成全局变量,typeof b返回'number',所以结果是true。
易错点提示:
- 赋值表达式
const a = b = 3,b没有声明关键字,容易误以为b只在函数内有效,实际是全局变量。 - 推荐始终使用
let或const显式声明变量,避免污染全局作用域。
Object.is() 与比较操作符 “===”、“==” 有什么区别
答案
| 特性/比较方式 | == | === | Object.is() |
|---|---|---|---|
| 类型转换 | 有 | 无 | 无 |
NaN 与 NaN | 不相等 | 不相等 | 相等 |
+0 与 -0 | 相等 | 相等 | 不相等 |
| 对象比较 | 引用地址 | 引用地址 | 引用地址 |
| 备注 | 宽松相等,自动类型转换 | 严格相等,类型和值都需相同 | 区分 +0/-0 和 NaN |
总结:
Object.is()与===类似,但能区分+0和-0,且NaN与自身相等。==存在类型转换,通常不推荐用于判断相等性。