- 原文地址:[1] + [2] - [3] === 9!? Looking into assembly code of coercion
- 原文做者:wanago.io
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:sunhaokk
- 校對者:Starrier、Xekin-FE
變量值擁有多種格式。並且您能夠將一種類型的值轉換爲另外一種類型的值。這叫類型轉換(也叫顯式轉換)。若是是在後臺中嘗試對不匹配的類型執行操做時發生, 叫 強制轉換(有時也叫隱式轉換)。在這篇文章中,我會引導你瞭解這兩個過程,以便更好地理解過程。讓咱們一塊兒深刻研究!javascript
正如我以前的一篇文章所描述的那樣,幾乎 JavaScript 中的全部原始類型(除了 null 和 undefined 外)都有圍繞它們原始值的對象包裝。事實上,你能夠直接調用原始類型的構造函數做爲包裝器將一個值的類型轉換爲另外一個值。前端
String(123); // '123'
Boolean(123); // true
Number('123'); // 123
Number(true); // 1
複製代碼
一些原始類型的包裝器,String、Bollean、Number 不會保留很長時間,一旦工做完成,它就消失。(譯者注:JS 中將數據分紅兩種類型,原始類型(基本數據類型)和對象類型(引用數據類型)。在對象類型中又有三種特殊類型的引用類型分別是,String、Boolean、Number。這三個就是基本包裝類型。實際上,每當讀取一個基本類型值的時候,後臺就會建立一個對應的基本包裝類型的對象,從而能夠調用這些類型的方法來操做數據。)java
您須要注意,若是您這裏使用了 new 關鍵字,就再也不是當前實例。node
const bool = new Boolean(false);
bool.propertyName = 'propertyValue';
bool.valueOf(); // false
if (bool) {
console.log(bool.propertyName); // 'propertyValue'
}
複製代碼
因爲 bool 在這裏是一個新的對象(不是原始值),它的計算結果爲 true。android
進一步分析ios
if (1) {
console.log(true);
}
複製代碼
效果同樣git
if ( Boolean(1) ) {
console.log(true);
}
複製代碼
不要畏懼,敢於嘗試。 下面用 Bash 測試。(譯者注:由於沒有找到源文件,因此我猜想這裏的意思是使用的 if1.js 和 if2.js 是上文的 if 語句文件,這裏經過 print-code 輸出彙編代碼。而後經過 awk 打印彙編文件每句第 4 列字符串到文件裏。最後對比兩個文件是否一致。藉以推論出上面兩句 if 在程序中的執行是一致的。)es6
$ node --print-code ./if1.js >> ./if1.asm
複製代碼
$ node --print-code ./if2.js >> ./if2.asm
複製代碼
#!/bin/bash
file1=$(awk '{ print $4 }' ./if1.asm)
file2=$(awk '{ print $4 }' ./if2.asm)
[ "$file1" == "$file2" ] && echo "文件匹配"
複製代碼
"文件匹配"
複製代碼
這個函數的做用相似於 Number 的構造函數,但對於傳遞的參數來講不那麼嚴格。若是它遇到一個不能成爲數字一部分的字符,它將返回一個到該點的值並忽略其他字符。github
Number('123a45'); // NaN
parseFloat('123a45'); // 123
複製代碼
它在解析數字時將數字向下舍入。它可使用不一樣的基數。web
parseInt('1111', 2); // 15
parseInt('0xF'); // 15
parseFloat('0xF'); // 0
複製代碼
函數 parseInt 能夠猜想基數或讓它做爲第二個參數傳遞。有關其中須要考慮的規則列表,請查看 MDN web docs。
若是傳入的數值過大會出問題,因此它不該該被認爲是 Math.floor (它也會進行類型轉換)的替代品:
parseInt('1.261e7'); // 1
Number('1.261e7'); // 12610000
Math.floor('1.261e7') // 12610000
Math.floor(true) // 1
複製代碼
您可使用 toString 函數將值轉換爲字符串。這個功能的實如今原型之間有所不一樣。
若是您以爲您但願更好地理解原型的概念,請隨時查看個人其餘文章: Prototype. The big bro behind ES6 class。
返回一個字符串的值
const dogName = 'Fluffy';
dogName.toString() // 'Fluffy'
String.prototype.toString.call('Fluffy') // 'Fluffy'
String.prototype.toString.call({}) // Uncaught TypeError: String.prototype.toString requires that 'this' be a String
複製代碼
返回轉換爲 String 的數字(您能夠將 appendix 做爲第一個參數傳遞)
(15).toString(); // "15"
(15).toString(2); // "1111"
(-15).toString(2); // "-1111"
複製代碼
返回 Symbol(${description})
若是你對此感到疑問: 我這裏使用的是 template literals的方式,它能夠向你解釋是怎麼輸出這種字符串的。
返回 「true」 或 「false」
Object 調用內部 [[Class]] 。它是表明對象類型的標籤。
Object.prototype.toString 返回一個 [object ${tag}]
字符串。 要麼它是內置標籤之一 (例如 「Array」, 「String」, 「Object」, 「Date」 ), 或者它被明確設置。
const dogName = 'Fluffy';
dogName.toString(); // 'Fluffy' (在這調用 String.prototype.toString )
Object.prototype.toString.call(dogName); // '[object String]'
複製代碼
隨着 ES6 的推出,設置標籤可使用 Symbols來完成。
const dog = { name: 'Fluffy' }
console.log( dog.toString() ) // '[object Object]'
dog[Symbol.toStringTag] = 'Dog';
console.log( dog.toString() ) // '[object Dog]'
複製代碼
const Dog = function(name) {
this.name = name;
}
Dog.prototype[Symbol.toStringTag] = 'Dog';
const dog = new Dog('Fluffy');
dog.toString(); // '[object Dog]'
複製代碼
你也能夠在這裏使用 ES6 類和 getter:
class Dog {
constructor(name) {
this.name = name;
}
get [Symbol.toStringTag]() {
return 'Dog';
}
}
const dog = new Dog('Fluffy');
dog.toString(); // '[object Dog]'
複製代碼
在每一個元素上調用 toString 並返回一個字符串,全部的輸出用逗號分隔。
const arr = [
{},
2,
3
]
arr.toString() // "[object Object],2,3"
複製代碼
若是您瞭解類型轉換的工做原理,那麼理解隱式轉換會容易得多。
當在字符串與操做數之間使用 + 時結果將返回一個字符串。
'2' + 2 // 22
15 + '' // '15'
複製代碼
你能夠用加符號將一個操做數轉換爲數字:
+'12' // 12
複製代碼
其餘數學運算符,例如 -
或 /
操做,將自動轉成數字。
new Date('04-02-2018') - '1' // 1522619999999
'12' / '6' // 2
-'1' // -1
複製代碼
日期, 轉成數字 Unix 時間戳。
若是原始值是 false 的,則使用它將輸出 true,若是 true,則輸出爲 false。所以,若是使用兩次,它能夠用於將該值轉換爲相應的布爾值。
!1 // false
!!({}) // true
複製代碼
值得一提的是,即便 ToInt32 其實是一個抽象操做(僅限內部,不可調用),它也會把一個值轉換爲帶符號 32 位整型。
0 | true // 1
0 | '123' // 123
0 | '2147483647' // 2147483647
0 | '2147483648' // -2147483648 (too big)
0 | '-2147483648' // -2147483648
0 | '-2147483649' // 2147483647 (too small)
0 | Infinity // 0
複製代碼
當其中一個操做數爲 0 時執行按位或操做將致使不改變另外一個操做數的值。
在編碼時,您可能會遇到更多隱式轉換的狀況。考慮這個例子
const foo = {};
const bar = {};
const x = {};
x[foo] = 'foo';
x[bar] = 'bar';
console.log(x[foo]); // "bar"
複製代碼
發生這種狀況是由於 foo 和 bar 在轉換爲字符串時都會轉成 「[object Object]」 。真正發生的是這樣的:
x[bar.toString()] = 'bar';
x["[object Object]"]; // "bar"
複製代碼
隱式轉換在 template literals也會發生。嘗試在這裏重載 toString 函數:
const Dog = function(name) {
this.name = name;
}
Dog.prototype.toString = function() {
return this.name;
}
const dog = new Dog('Fluffy');
console.log(`${dog} is a good dog!`); // "Fluffy is a good dog!"
複製代碼
隱式轉換也是爲何比較運算符(==)可能被認爲是很差的作法,由於若是它們的類型不匹配,它會嘗試經過強制轉換進行匹配。
查看這個例子以得到一個關於比較的有趣事實:
const foo = new String('foo');
const foo2 = new String('foo');
foo === foo2 // false
foo >= foo2 // true
複製代碼
由於咱們在這裏使用了 new 關鍵字,因此 foo 和 foo2 都保留了它們的原始值(這是 'foo' )的包裝。因爲他們如今正在引用兩個不一樣的對象, foo === foo2
結果爲 false。關係操做 ( >=
) 在兩邊調用 valueOf 函數。所以,在這裏比較原始值內存地址, 'foo' >= 'foo'
返回 true。
我但願全部這些知識能幫助你揭開本文標題中問題的神祕面紗。讓咱們揭開它吧!
[1] + [2]
這些轉換應用 Array.prototype.toString 規則而後鏈接字符串。結果將是 "12"
。[1,2] + [3,4]
結果是 "1,23,4"
。12 - [3]
將致使 12
減 "3"
得 9
12 - [3,4]
由於 "3,4"
不能轉成數字因此得 NaN儘管不少人可能會建議你避免隱式轉換,但我認爲了解它的工做原理很是重要。依靠它可能不是一個好主意,但它對您在調試代碼和避免首先出現的錯誤方面大有幫助。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。