JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

19 | a + b:动态类型是灾难之源还是最好的特性?(下)

数字值
字符串
从引用x到值:调用x.valueOf()方法;或,调用四种值类型的包装类函数,例如Number(x),或者String(x)等等
从值x到引用:调用Object(x)函数
"x === x"在哪些情况下不为true
JavaScript中的动态类型
Symbol.toPrimitive的处理
字符串在“+”号中的优先权
显式的 vs. 隐式的转换
不同预期下的对象转换结果差异
JavaScript无法确定用户代码的预期的情况
"+"号运算符的特殊处理
大括号作为块语句的情况
四种对象与数组相加的结果
对象和数组的toString()方法调用结果
对象和数组的valueOf()方法调用结果
步骤四:进入“传统的类型转换逻辑”
步骤三:五种包装类的对象实例的valueOf()方法总是会忽略掉“number”这样的预设,并返回它们内部确定的原始值
步骤二:对象有对应的PrimitiveValue内部槽则直接返回
步骤一:原始值直接返回
两种主要类型
两条简单规则
"x === x"在哪些情况下不为true
JavaScript中的动态类型
结语与思考
其他
解题3:预期 vs. 非预期
解题2:“加号(+)”运算的戏分很多
解题1:从对象到原始值
类型转换系统
类型转换
动态类型
动态类型与类型转换

该思维导图由 AI 生成,仅供参考

你好,我是周爱民。
上一讲,我们说到如何将复杂的类型转换缩减到两条简单的规则,以及两种主要类型。这两条简单规则是:
从值 x 到引用:调用 Object(x) 函数。
从引用 x 到值:调用 x.valueOf() 方法;或,调用四种值类型的包装类函数,例如 Number(x),或者 String(x) 等等。
两种主要类型则是字符串数字值
当类型转换系统被缩减成这样之后,有些问题就变得好解释了,但也确实有些问题变得更加难解。例如 @graybernhardt 在讲演中提出的灵魂发问,就是:
如果将数组跟对象相加,会发生什么?
如果你忘了,那么我们就一起来回顾一下这四个直击你灵魂深处的示例,简单地说,这些示例就是“数组与对象”相加的四种情况,结果都完全不同。
> [] + {}
'[object Object]'
> {} + []
0
> {} + {}
NaN
> [] + []
''
而这个问题,也就是这两讲的标题中“a + b”这个表达式的由来。也就是说,如何准确地解释“两个操作数相加”,与如何全面理解 JavaScript 的类型系统的转换规则,关系匪浅!

集中精力办大事

一般来说,运算符很容易知道操作数的类型,例如“a - b”中的减号,我们一看就知道意图,是两个数值求差,所以 a 和 b 都应该是数值;又例如“obj.x”中的点号,我们一看也知道,是取对象 obj 的属性名字符串 x
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript类型转换机制是一个复杂而深奥的话题,本文通过深入讨论类型转换的规则和特殊情况,帮助读者更好地理解JavaScript中的类型转换机制。文章首先介绍了类型转换的规则和主要类型,然后深入讨论了在JavaScript中“加号(+)”运算符的特殊性,以及对象到原始值的转换过程。通过详细的步骤分析,解释了对象和数组相加在特定情况下会出现不同结果。此外,还介绍了JavaScript中的预期和非预期情况下的类型转换结果,以及显式和隐式转换的区别。文章还讨论了字符串在“+”号运算中的优先权和Date类中的特殊处理方式,最后提到了Symbol.toPrimitive的处理对隐式转换逻辑的影响。总之,本文通过深入分析JavaScript中的类型转换机制,为读者提供了深入理解JavaScript类型转换机制的视角,具有技术深度和实用价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(16)

  • 最新
  • 精选
  • weineel
    强行找到一种方法, 但和本节所讲没啥关系: Object.defineProperty(global, 'x', { get: function() { return Math.random(); } }) x === x // false

    作者回复: 赞! 的确,这是除NaN之外我认为最可行的一个答案。事实上,这也是我在课程中提升“动态语言特性”这个方向的原因:一部分动态特性是基于OOP来实现的,这正是JavaScript的混合语言特性的应用。 不过这个例子其实可以变成更简单。例如: ``` Object.defineProperty(global, 'x', { get: Symbol }) // 或 Object.defineProperty(global, 'x', { get: Math.random }) ``` AND, @晓小东 给出的Symbol()方案对这个getter方法是一个很好的补充,很好地利用了“symbol总是唯一”的特性。

    2019-12-27
    2
    11
  • 陈强
    这里是一个拓展阅读:https://2ality.com/2012/01/object-plus-object.html 为什么{} + {} 在浏览器中打印的是 "[object Object][object Object]" 因为“the Node.js REPL parses its input differently from either Firefox or Chrome (which even uses the same V8 JavaScript engine as Node.js). The following input is parsed as expressions and the results are less surprising:” 实际上chrome自己加了console.log(),相当于({} + {}),firefox的控制台会输出NaN。 // test in chrome {} + {} // "[object Object][object Object]" {} + {} === "[object Object][object Object]" // false ({} + {}) === "[object Object][object Object]" // true

    作者回复: 谢谢 👍+3

    2020-04-08
    8
  • Geek_185c7d
    请问老师,为什么{} + {} 在浏览器中打印的是 "[object Object][object Object]"

    作者回复: 浏览器把引擎包了一层,你执行的不是真正的引擎环境。 在node中用如下命令行试试: ``` > node -p -e '{} + {}' NaN > node -p -e '{} + []' 0 ```

    2020-01-19
    3
    4
  • 晓小东
    难道是这个吗, 如果作为标识符var x 确实没想出。 >> Symbol() === Symbol() // false

    作者回复: 参见 @sprinty 的答案。呵呵,我自己也不知道有没有更多的可能了。

    2019-12-28
    2
  • 清波
    老师您好,我在浏览器控制台,node环境控制台或者代码中:{} + {}的执行结果都是"[object Object][object Object]",但是教程中的结果是NaN,是不是还有其他理解方式?

    作者回复: 评论中有解说过这个问题,是node/控制台包过一次。可以用用如下命令行试试: ``` > node -p -e '{} + {}' NaN > node -p -e '{} + []' 0 ```

    2022-01-07
    1
  • undefined
    > node -v v12.20.1 > node -p '{} + {}' NaN > node -p '({} + {})' [object Object][object Object] > node -p "eval('{} + {}')" NaN > node -p "eval('({} + {})')" [object Object][object Object]

    作者回复: 这个?问题是? 1、3是按语句执行,所以是NaN;2、4是按表达式执行,所以是[object Object]*2。 正文中(解题2)说的都是第一种情况。而第二种情况的分析路径在正文中也是交待了的哦。

    2021-04-30
    1
  • wheatup
    和sprinty的方法类似,只不过避免了污染全局作用域: with ({ get x() { return Symbol() } }) { console.log(x === x); // false } 这个能成功的原因是这里的 x 其实是有 [withObj].x 取值的一个操作,然后利用getter返回不同的值造成的。 --- 真要说利用本节课学到的内容的话,可以把题目的 x === x 换成 x >= x,单纯的x === x是不会触发隐式类型转换的,而js在比较大小时一定会将对象转换成数值再进行比较,所以这里就会调用[Symbol.toPrimitive],这时候就可以重写这个方法做些手脚: let x = { [Symbol.toPrimitive]() { return Math.random(); } }; console.log(x >= x); // true和false都有可能

    作者回复: +1

    2020-06-10
    1
  • 油菜
    老师,Number([]) == Number([]) 是true,但[] == [] 是false,动态类型转换如何解释这个案例呢? ------------ var x = []; x === x // true; x === [] // false; [] === x // false; [] === [] //false;

    作者回复: 对于`Number([])`来说,过程如下: ==== Number([]) => Number(ToNumber(ToPrimitive([]))) => Number(ToNumber([].valueOf())) => Number(ToNumber('')) => Number(0) => 0 ==== 注意这里的结果值是一个值类型,而不是对象类型,所以`typeof Number([])`的值将是"number"。所以,第一个式子等义于`0 == 0`,当然是true。 对于`[] == []`来说,两个对象实例的比较总是false。

    2020-11-19
  • HoSalt
    「Date() 类中仍然是会调用 toString 或 valueOf 的,这是因为在它的Symbol.toPrimitive实现中仅是调整了两个方法的调用顺序,而之后仍然是调用原始的、内置的ToPrimitive()方法的」 调整顺序是值hint值为default的情况下?

    作者回复: 是。如果hint不是default,那么就相当于指定顺序了。 https://tc39.es/ecma262/#sec-toprimitive

    2020-05-25
  • K4SHIFZ
    自动分号插入并不会将{}+{} 变成{};+{} 吧?因为没有行结束符。

    作者回复: 会的。:)

    2020-03-28
    2
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部