爲何 0.._ 等於 undefined

爲何 0.._ 等於 undefinedgit

前言

今天看文章 爲何用「void 0」代替「undefined」 的時候,github

做者提到,用 void 0 替代 undefined 的緣由其中有一點是前者更短,更省空間。bash

固然最主要的緣由仍是 undefined 在局部做用域中能夠被重寫markdown

下面有人回覆 0.._ 長度更短,結果也是 undefined。 後面解釋說是至關於 0['_'],不過沒有更深刻的討論了。less

當時心中產生了幾個問題:ide

  1. 0.._ 是如何隱式轉換成 undefined
  2. 爲什麼(幾乎)沒有人採用 0.._ 的寫法代替 void 0

0.._ 的隱式轉換

詞法分析

對於10進制數字來講,後面接 . 操做符,js 引擎並不知道該 . 是小數點仍是訪問屬性的 .,所以有以下規定:工具

前面的數字爲10進制,已帶小數點的,則該 . 是訪問屬性,不然即爲小數點; 若不是10進制,則 . 是訪問屬性oop

0.0._ // 輸出 undefined 至關於 (0.0)._ 
0.._ // 至關於 (0.)._
00._ // 前面爲 8進制
true._ // 輸出 undefined
0._ // 語法錯誤 .後面應該接數字
 'use strict';
00._ // Uncaught SyntaxError: Octal literals are not allowed in strict mode. 嚴格模式下不會解析成八進制
複製代碼

:以上是測試得出的結論,規範中沒找到。性能

不過按編譯原理的知識,引擎會先根據 詞法解析-數值字面量 找到 0. 這個數值字面量詞法,接着才進行語法分析測試

同時 附加語法-數值字面量 中提到非 strict 模式下 NumericLiteral 才容許 OctalIntegerLiteral 八進制的詞法

語法分析

接下來就是 爲什麼數值字面量可以進行屬性訪問 的問題了。這是一個左值表達式。

左值表達式 語法,這裏列舉部分

LeftHandSideExpression :
NewExpression
CallExpression

CallExpression :
MemberExpression Arguments
CallExpression Arguments
CallExpression [ Expression ]
CallExpression . IdentifierName

MemberExpression :
PrimaryExpression
FunctionExpression
MemberExpression [ Expression ]
MemberExpression . IdentifierName
new MemberExpression Arguments

複製代碼

左值表達式-屬性訪問 有二者方式

  • MemberExpression . IdentifierName
  • MemberExpression [ Expression ]

前者等同於 MemberExpression [ <identifier-name-string> ]

<identifier-name-string> 是一個字符串字面量,它與 Unicode 編碼後的 IdentifierName 包含相同的字符序列。

對於 MemberExpression [ Expression ] 表達式,其執行順序以下:

  1. 令 baseReference 爲解釋執行 MemberExpression 的結果 .
  2. 令 baseValue 爲 GetValue(baseReference).
  3. 令 propertyNameReference 爲解釋執行 Expression 的結果 .
  4. 令 propertyNameValue 爲 GetValue(propertyNameReference).
  5. 調用 CheckObjectCoercible(baseValue).
  6. 令 propertyNameString 爲 ToString(propertyNameValue).
  7. 若是正在執行中的語法產生式包含在嚴格模式代碼當中,令 strict 爲 true, 不然令 strict 爲 false.
  8. 返回一個 引用類型 的值。該引用類型,其基 (base) 值爲 baseValue, 其引用名稱(referenced name)爲 propertyNameString, 嚴格模式標記爲 strict.

0.._ 爲例,其等同於 0['_'],即 MemberExpression = 0,Expression = '_',按如下步驟進行

  1. baseReference = 0
  2. baseValue = GetValue(baseReference) = 0
  3. propertyNameReference = '_'
  4. propertyNameValue = GetValue(propertyNameReference) = '_'
  5. baseValue = ToObject(0) = new Number(0) // 生成一個臨時包裝對象

Number { __proto__: Number, [[PrimitiveValue]]: 0}

  1. propertyNameString = ToString(propertyNameValue) = '_'
  2. strict 設置
  3. 生成引用,其基值爲 Number { __proto__: Number, [[PrimitiveValue]]: 0},引用名稱爲 _。在該基值(及原型鏈)中進行_屬性的尋找。最後沒有找到,返回 undefined

其實關鍵的就是執行 CheckObjectCoercible(0) 的時候調用 ToObject 返回了一個臨時包裝對象

這點規範說的有點模糊,只說了 CheckObjectCoercible 在其參數沒法用 ToObject 轉換成對象的狀況下拋出一個異常,可是沒有說 baseValue 會進行 ToObject 轉換。 在 JS的基本數據類型的臨時包裝類型對象的觸發條件和生命週期是多久? - 貘吃饃香的回答 - 知乎 中有人進行了回答。

爲什麼不用 0.._ 代替 void 0

咱們從 可讀性、性能、正確性 三個方面分析

可讀性

void 0 相比,0.._ 僅減小了一個字符,可是該寫法大大減低了可讀性

對於壓縮工具來講,不在意可讀性,那麼咱們從性能角度分析。

性能

var COUNT = 100000000
var tmp
console.time("test1")
for(let i=0;i<COUNT;i++){
  if(tmp === void 0){
  }
}
console.timeEnd("test1")
// test1: 61.760986328125ms
console.time("test2")
for(let i=0;i<COUNT;i++){
  if(tmp === 0.._){
  }
}
console.timeEnd("test2")
// test2: 74.657958984375ms
複製代碼

void 0 更快一點,但這個影響不大,單次指令之間的執行差別在微秒以內。

最後就看二者的值是否是正確的,即結果永遠爲 undefined

正確性

對於 void 0 ,void 是關鍵字,不會被外部改變,所以返回值永遠返回 undefined ,見 void 運算符

對於 0.._,咱們上面分析到,在基值中進行引用名稱的查找時,會往原型鏈中查找,所以改變 Number、Object 等的原型屬性,0.._ 值就不同了

console.log(0.._) // undefined
Object.prototype._ = 0
console.log(0.._) // 0
Number.prototype._ = 1
console.log(0.._) // 1
複製代碼

能夠看到, 0.._ 結果不是固定的,所以不能用於替換 void 0

參考

  1. es5 規範_中文版
  2. es5 規範

ps: 中文版翻譯有些地方不夠準確,能夠先看中文版瞭解大概,再到原版中詳細查看

相關文章
相關標籤/搜索