JavaScript隐式类型转换小结

本文首发于个人我的网站blog.skyline.ink,欢迎各位大大访问. 做者水平有限,文章仅供参考,不对的地方但愿各位及时指正,共同进步,不胜感激javascript

强弱类型

JavaScript强or弱

对于前端程序员来讲,JavaScript可谓灰常强,但此强非彼强。根据维基百科的阐释,在计算机编程中,通俗地将语言分为强类型和弱类型,虽然没有精确的定义,可是强类型有很是严格的规则,包括变量定义时必须指定类型,使用时必须是指望的类型,不然报错或拒绝编译。维基百科上的类型强弱html

JavaScript并不须要在定义时指定变量类型,同时使用时,不是指望的值会自动转换,弱类型相对于强类型来讲类型检查更不严格,故而引出了今天的问题,自动转换的一些机制前端

转换规则

隐式转换为布尔值

如表
数据类型 转化成true 转化成false
String 非空字符 ""(空字符)
Number 非零 0与NaN
Object 非Null对象 null
undefined undefined

(注:调用Boolean()方法获得结果相同)vue


对象隐式转换规则

ToPrimitive

对于多数状况来讲,对象隐式转换成字符串或数字,其实调用了一个叫作ToPrimitive(obj,preferredType)的内部方法来干这件事情,看了网上不少资料,都是balabala一大堆,规范里面文字也很多,其实调用这个方法转换的时候,除了date对象走转换数字流程(即preferredType值是number),其余走的都是转字符流程(即preferredType值是string),大概流程以下:java

0E0B74FDA0F7561D3AF96126F84DD4B3.png-content

不一样对象调用toString()获得的结果
对象 调用toString()
普通对象 "[object Object]"
数组arr arr.join()
函数类 定义函数的代码
日期类 可读日期
正则对象 正则对象字面量的字符
  • 若是数组的某一项的值是null或者undefined,join()方法返回的结果以空字符串链接
  • 基本包装类型的引用类型用其字面量形式的值调用toString()
不一样对象调用valueOf()获得的结果
  • 大多数对象,包括普通对象、数组、函数、正则简单返回对象自己
  • 日期对象返回19700101以来的毫秒数值
  • 基本包装类型的引用类型返回其字面量形式的值

其余隐式转换规则

转化成字符串 转化成数字 转化成布尔值
undefined "undefined" NaN false
null "null" 0 false
NaN "NaN" NaN false
[](空数组) "" 0 true
""(空字符串) "" 0 false
  • 布尔值转数字为0/1,转字符串为"true"/"false"
  • 数字转字符串加引号便可🙄
  • 字符串转数字,看去掉引号是不是数字便可🤣,不然为NaN (注:伪装这样表述是很严谨的🤣,数字与字符串的相互转换不用表述你们都知道的)

开始转换

常规加号

表达式中有字符串
  • 其余类型隐式转换为字符串
  • 多个加号时,按照从左到右的顺序,两两进行计算
  • 只要表达式中若是有字符串,最终结果必定是字符串
  • 若是有复杂类型,先将复杂类型按照对象隐式转换规则转换成字符串
2 + "3"; // "23"
1 + 2 + "3"; // "33"
true + 2 + "3"; // "33"
1 + "2" + 3; // "123"

"2" + true; //"2true"

"2" + undefined; //"2undefined"
"2" + NaN //"2NaN"
'23' + {'a': 1} //"23[object Object]"

'23' + [1,3,{}, null, undefined, '', '2'] // "231,3,[object Object],,,,2"
[1,3,{}, null, undefined, '', '2'].toString() //"1,3,[object Object],,,,2"
23 + "1,3,[object Object],,,,2" //"231,3,[object Object],,,,2"
复制代码
表达式中没有字符串
  • 若是没有复杂类型,其余类型隐式转换为数字
  • 若是有复杂类型,先将复杂类型按照对象隐式转换规则转换成原始值再按照如上规则计算
1 + [] //"1"
1 + [1] //"11"
1 + {a:'a'} //"1[object Object]"
null + null //0
true + {a:'a'} //"true[object Object]"
复制代码
  • 注意undefined 转化成数字是NaN
typeof NaN //"number"
null + undefined //NaN
1 + undefined //NaN
复制代码

乘,除,取余,常规减

  • 其余类型会隐式转换为数字
1 - '5' //-4
1 - [2, 2] //NaN
1 - {a:1} //NaN
1- undefined //NaN
1 - [] //1
1 - [2, 2] //NaN
1 - null //1
复制代码

一元加、一元减

  • 一元+运算符将其操做数转换为Number类型。一元减号同理可是反转正负
+ '3'      // 数字3
- '-3'      // 数字3
复制代码

比较运算符部分 > < >= <= ==

  • 数字vs其余,其余转化为数字
  • 布尔值vs其余,布尔值转数字,数字vs其余
  • 字符串vs字符串,按unicode依次比较(大写字母老是在小写字母以后)
  • 对象vs数字,对象vs字符串,将对象转化为转换成原始值,再进行比较。
  • 若是其中一个操做数是NaN,那么老是返回false(NaN和NaN是不相等的)
  • null 只和undefined是好基友(互相相等)
var x = NaN;
x === NaN; // false

undefined == "undefined" // false
null == "null" // false
null == 0 // false
null == false // false
undefined == 0 // false
undefined == false // false
复制代码

几个有意思的输出

如下内容纯属拓展,不感兴趣的童鞋可忽略node

chrome/safari

[] + {} //"[object Object]"
{} + [] // 0
[] + {} === {} + [] // true
{} + [] === [] + {} // true
{a: 1} // {a: 1}
{a: 1}; // 1
{'a': 1} // {a: 1}
{'a': 1}; // SyntaxError
{} + 0 + {}; // "0[object Object]"
{} + 0 + {} // "[object Object]0[object Object]"
复制代码

firefox

[] + {} //"[object Object]"
{} + [] // 0
[] + {} === {} + [] // true
{} + [] === [] + {} // false
{a: 1} // 1
{a: 1}; // 1
{'a': 1} // SyntaxError
{'a': 1}; // SyntaxError
{} + 0 + {}; // "0[object Object]"
{} + 0 + {} // "0[object Object]"
复制代码

须要的其余知识

label语句

MDN:label {a: 1}相关的几个输出里,先忽略分号,{}分别被当作block和object literal,当被当作代码块时,入下所示:webpack

//{'a': 1}
{
    "a": 1; // 语法错误
}

// {a: 1}
{
    a: 
        1;
}     // ^-- Automatic Semicolon Insertion
复制代码
自动分号插入(ASI)

分号是否书写在前端领域来讲,这个问题如同vi、emacs编辑器之争,最好计算机语言之争通常。在知乎上看了JavaScript 语句后应该加分号么?后,大体总结出来就是:git

  • 写不写分号看项目风格与我的喜爱
  • 书写分号也没法避免ASI带来的问题
  • 采用行首特例加分号的策略,通常只有行首是 [ ( + - / 五个符号之一在其前面加分号,这些地方的分号必须书写

说回ASI,程序员

官方规范中的ASIweb

官文太抽象,网上大神的翻译大多数更加抽象,根据网上各类资料来看,总结用口水话说就是代码块最后一条语句自动插入,换行时候大多数在语句末尾自动插入,除了:

  • 该语句不是有效结束一个语句的方式。(好比以 . 或 , 或:结尾)
  • 该行是 -- 或 ++(将减量/增量的下一个标记)
  • for、while、do、if 或 else,且以后没有 {
  • 下一行以 [、(、+、*、/、-、,、.或一些其它在单个表达式中两个标记之间的二元操做符。主要遇到[ ( + - /
{}是代码块仍是对象?
Abstract Syntax Tree 抽象语法树(AST)

wikipedia: Abstract_syntax_tree

In computer science, an abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language. Each node of the tree denotes a construct occurring in the source code. The syntax is "abstract" in not representing every detail appearing in the real syntax. For instance, grouping parentheses are implicit in the tree structure, and a syntactic construct like an if-condition-then expression may be denoted by means of a single node with three branches.

其实就是将源代码分析成所对应的树状结构,便于以后的语法分析,代码检查等。如今的不少热门工具如webpack、vue、UglifyJS、Lint等都会用到这个技术,各个浏览器引擎也会使用自家定义的一套语法书生成树规范,生成相应的语法树。

分析

其实因为浏览器厂商众多,每一个与解析状况不一致,日常代码中基本不会遇到{}+这种问题,咱们也没有精力研究各厂商预解析源码,从Chrome和Firefox来看,总结出来有下面几点:

  • {}的前面有运算符号的时候,{}都会被解析成对象字面量。
  • {}前面没有运算符时候但有;结尾的时候,{}都会被解析成代码块。
  • {}前面什么运算符都没有,{}后面也没有分号(;)结尾
    • Firefox会始终如一的解析为代码块
    • chrome在这种状况下须要被扒一下历史

大概在chrome版本49以前,Chrome控制台上面的输出结果基本和Firefox一致,以后在chrome上有人提出bug,Issue 499864,大概意思就是说我在控制台输入{a: 4, b: 5}你给我报个错干吗,我就是想要一个对象而已。Chrome确实该近几年大火,没过多久就修复了,修复的方式也特别666,就是凡是语句以{开头,以}结尾,我解析的时候就包裹一层括号在外面。git记录,里面的关键代码以下:

+    if (/^\s*\{/.test(text) && /\}\s*$/.test(text))
+        text = '(' + text + ')';
复制代码

也就是说{} + 0 + {}实际上是({} + 0 + {}), {a: 1}实际上是({a: 1}),也就是说在Chrome中,凡是语句以{开头,

  • 以}结尾,语句里第一个{}是对象
  • 不以}结尾,语句第一个{}是代码块
最后用图来看一下AST

以{} + 0 + {}为例来看

  • Chrome

此时,Chrome将第一个{}解析成对象

https://user-gold-cdn.xitu.io/2018/3/22/1624bc2968b4f4c4?w=627&h=171&f=png&s=11600

  • firefox

此时,firefox将第一个{}解析成代码块

https://user-gold-cdn.xitu.io/2018/3/22/1624bc2968b4f4c4?w=627&h=171&f=png&s=11600

看AST的网站

分析以后不可贵出如上的结果


参考资料

《JavaScript高级程序设计》

相关文章
相关标签/搜索