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高級程序設計》

相關文章
相關標籤/搜索