摘要:通過使用其構造函數,可以將一個值的類型轉換為另一種類型。如果使用兩次,可用于將該值轉換為相應的布爾值。
編譯自:[1] + [2] – [3] === 9!? Looking into assembly code of coercion.全文從兩個題目來介紹類型轉換、寬松相等以及原始值的概念:
[1] + [2] – [3] === 9
如果讓 a == true && a == false 的值為 true
第二道題目是譯者加的,因為這其實是個很好的例子,體現出 JavaScript 的魔幻之處
變量值都具有類型,但仍然可以將一種類型的值賦值給另一種類型,如果是由開發者進行這些操作,就是類型轉換(顯式轉換)。如果是發生在后臺,比如在嘗試對不一致的類型執行操作時,就是隱式轉換(強制轉換)。
類型轉換(Type casting) 基本包裝類型(Primitive types wrappers)在 JavaScript 中除了 null 和 undefined 之外的所有基本類型都有一個對應的基本包裝類型。通過使用其構造函數,可以將一個值的類型轉換為另一種類型。
String(123); // "123" Boolean(123); // true Number("123"); // 123 Number(true); // 1
基本類型的包裝器不會保存很長時間,一旦完成相應工作,就會消失
需要注意的是,如果在構造函數前使用 new 關鍵字,結果就完全不同,比如下面的例子:
const bool = new Boolean(false); bool.propertyName = "propertyValue"; bool.valueOf(); // false if (bool) { console.log(bool.propertyName); // "propertyValue" }
由于 bool 在這里是一個新的對象,已經不再是基本類型值,它的計算結果為 true。
上述例子,因為在 if 語句中,括號間的表達式將會裝換成布爾值,比如
if (1) { console.log(true); }
其實,上面這段代碼跟下面一樣:
if ( Boolean(1) ) { console.log(true); }parseFloat
parseFloat 函數的功能跟 Number 構造函數類似,但對于傳參并沒有那么嚴格。當它遇到不能轉換成數字的字符,將返回一個到該點的值并忽略其余字符。
Number("123a45"); // NaN parseFloat("123a45"); // 123parseInt
parseInt 函數在解析時將會對數字進行向下取整,并且可以使用不同的進制。
parseInt("1111", 2); // 15 parseInt("0xF"); // 15 parseFloat("0xF"); // 0
parseInt 函數可以猜測進制,或著你可以顯式地通過第二個參數傳入進制,參考 MDN web docs。
而且不能正常處理大數,所以不應該成為 Math.floor 的替代品,是的,Math.floor 也會進行類型轉換:
parseInt("1.261e7"); // 1 Number("1.261e7"); // 12610000 Math.floor("1.261e7") // 12610000 Math.floor(true) // 1toString
可以使用 toString 函數將值轉換為字符串,但是在不同原型之間的實現有所不同。
String.prototype.toString
返回字符串的值
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
Number.prototype.toString
返回將數字的字符串表示形式,可以指定進制作為第一個參數傳入
(15).toString(); // "15" (15).toString(2); // "1111" (-15).toString(2); // "-1111"
Symbol .prototype.toString
返回 Symbol(${description})
Boolean.prototype.toString
返回 “true” 或 “false”
Object.prototype.toString
返回一個字符串 [ object $ { tag } ] ,其中 tag 可以是內置類型比如 “Array”,“String”,“Object”,“Date”,也可以是自定義 tag。
const dogName = "Fluffy"; dogName.toString(); // "Fluffy" (String.prototype.toString called here) Object.prototype.toString.call(dogName); // "[object String]"
隨著 ES6 的推出,還可以使用 Symbol 進行自定義 tag。
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 class 和 getter:
class Dog { constructor(name) { this.name = name; } get [Symbol.toStringTag]() { return "Dog"; } } const dog = new Dog("Fluffy"); dog.toString(); // "[object Dog]"
Array.prototype.toString
在每個元素上調用 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
上述例子中,Date 類型將轉換為數字,即 Unix 時間戳。
邏輯非如果原始值是 假,則使用邏輯非將輸出 真,如果 真,則輸出為 假。 如果使用兩次,可用于將該值轉換為相應的布爾值。
!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"
使用模板字符串的時候也會發生強制轉換,在下面例子中重寫 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] - [3] === 9希望這些知識都能幫助揭開這個題目的神秘面紗
[1] + [2] 將調用 Array.prototype.toString 轉換為字符串,然后進行字符串拼接。結果將是 “12”
[1,2] + [3,4] 的值講師 “1,23,4”
12 - [3],減號運算符會將值轉換為 Number 類型,所以等于 12-3,結果為 9
12 - [3,4] 的值是 NaN,因為"3,4" 不能被轉換為 Number
總結盡管很多人會建議盡量避免強制隱式轉換,但了解它的工作原理非常重要,在調試代碼和避免錯誤方面大有幫助。
【譯文完】
再談點,關于寬松相等和原始值這里看另一道題目,在 JavaScript 環境下,能否讓表達式 a == true && a == false 為 true。
就像下面這樣,在控制臺打印出 ’yeah":
// code here if (a == true && a == false) { console.log("yeah"); }
關于寬松相等(==),先看看 ECMA 5.1 的規范,包含 toPrimitive:
11.9.3 The Abstract Equality Comparison Algorithm
9.1 ToPrimitive
稍作總結規范很長很詳細,簡單總結就是,對于下述表達式:
x == y
類型相同,判斷的就是 x === y
類型不同
如果 x,y 其中一個是布爾值,將這個布爾值進行 ToNumber 操作
如果 x,y 其中一個是字符串,將這個字符串進行 ToNumber 操作
若果 x,y 一方為對象,將這個對象進行 ToPrimitive 操作
至于 ToPrimitive,即求原始值,可以簡單理解為進行 valueOf() 和 toString() 操作。
稍后我們再詳細剖析,接下來先看一個問題。
Question:是否存在這樣一個變量,滿足 x == !x就像這樣:
// code here if (x == !x) { console.log("yeah"); }
可能很多人會想到下面這個,畢竟我們也曾熱衷于各種奇技淫巧:
[] == ![] // true
但答案絕不僅僅局限于此,比如:
var x = new Boolean(false); if (x == !x) { console.log("yeah"); } // x.valueOf() -> false // x is a object, so: !x -> false var y = new Number(0); y == !y // true // y.valueOf() -> 0 // !y -> false // 0 === Number(false) // true // 0 == false // true
理解這個問題,那下面的這些例子都不是問題了:
[] == ![] [] == {} [] == !{} {} == ![] {} == !{}
在來看看什么是 ToPrimitive
ToPrimitive看規范:8.12.8 [[DefaultValue]] (hint)
如果是 Date 求原始值,則 hint 是 String,其他均為 Number,即先調用 valueOf() 再調用 toString()。
如果 hint 為 Number,具體過程如下:
調用對象的 valueOf() 方法,如果值是原值則返回
否則,調用對象的 toString() 方法,如果值是原值則返回
否則,拋出 TypeError 錯誤
// valueOf 和 toString 的調用順序 var a = { valueOf() { console.log("valueof") return [] }, toString() { console.log("toString") return {} } } a == 0 // valueof // toString // Uncaught TypeError: Cannot convert object to primitive value // Date 類型先 toString,后 valueOf var t = new Date("2018/04/01"); t.valueOf = function() { console.log("valueof") return [] } t.toString = function() { console.log("toString") return {} } t == 0 // toString // valueof // Uncaught TypeError: Cannot convert object to primitive value
到目前為止,上面的都是 ES5 的規范,那么在 ES6 中,有什么變化呢
ES6 中 ToPrimitive7.1.1ToPrimitive ( input [, PreferredType] )
在 ES6 中嗎,是可以自定義 @@toPrimitive 方法的,這是 Well-Known Symbols(§6.1.5.1)中的一個。JavaScript 內建了一些在 ECMAScript 5 之前沒有暴露給開發者的 symbol,它們代表了內部語言行為。
來自 MDN 的例子:
// 沒有 Symbol.toPrimitive 屬性的對象 var obj1 = {}; console.log(+obj1); // NaN console.log(`${obj1}`); // "[object Object]" console.log(obj1 + ""); // "[object Object]" // 擁有 Symbol.toPrimitive 屬性的對象 var obj2 = { [Symbol.toPrimitive](hint) { if (hint == "number") { return 10; } if (hint == "string") { return "hello"; } return true; } }; console.log(+obj2); // 10 -- hint is "number" console.log(`${obj2}`); // "hello" -- hint is "string" console.log(obj2 + ""); // "true" -- hint is "default"
有了上述鋪墊,答案就呼之欲出了
a == true && a == false 為 true 的答案var a = { flag: false, toString() { return this.flag = !this.flag; } }
或者使用 valueOf():
var a = { flag: false, valueOf() { return this.flag = !this.flag; } }
或者是直接改變 ToPrimitive 行為:
// 其實只需設置 default 即可 var a = { flag: false, [Symbol.toPrimitive](hint) { if (hint === "number") { return 10 } if (hint === "string") { return "hello" } return this.flag = !this.flag } }如果是嚴格相等呢
這個問題在嚴格相等的情況下,也是能夠成立的,這又是另外的知識點了,使用 defineProperty 就能實現:
let flag = false Object.defineProperty(window, "a", { get() { return (flag = !flag) } }) if (a === true && a === false) { console.log("yeah"); }閱讀更多
Can (a== 1 && a ==2 && a==3) ever evaluate to true?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94166.html
摘要:稍后我們再詳細剖析,接下來先看一個問題。還內建了一些在之前沒有暴露給開發者的,它們代表了內部語言行為。使用,可能有不少朋友一開始就想到這種方式,簡單貼一下閱讀更多 在 JavaScript 環境下,可以讓表達式 a == true && a == false 為 true 嗎? 就像下面這樣,可以在控制臺打印出 ’yeah: // code here if (a == true && ...
摘要:那么,它到底是如何工作的呢讓我們從一種更簡單的實現開始實際上這種實現代碼更短,并且更易讀是函數原型中的一個函數,它調用函數,使用第一個參數作為參數,并傳遞剩余參數作為被調用函數的參數。 原文:The Most Clever Line of JavaScript 作者:Seva Zaikov 原文 最近 一個朋友 發給我一段非常有趣的 JavaScript 代碼,是他在某個 開源庫中...
摘要:同一類的復合類型值兩個復合類型對象數組函數的數據比較時,不是比較它們的值是否相等,而是比較它們是否指向同一個對象。寬松相等雙等號將執行類型轉換原始類型的值原始類型的數據會轉換成數值類型再進行比較。 事件這塊知識點雖然是老生長談的,但對于我來說多多整理,多多感悟,溫故知新,每次看看這塊都有不同的收獲.(在這里我不會長篇大論,只會挑重點;具體的小伙伴們自行查找)參考:https://dev...
摘要:同一類的復合類型值兩個復合類型對象數組函數的數據比較時,不是比較它們的值是否相等,而是比較它們是否指向同一個對象。寬松相等雙等號將執行類型轉換原始類型的值原始類型的數據會轉換成數值類型再進行比較。 事件這塊知識點雖然是老生長談的,但對于我來說多多整理,多多感悟,溫故知新,每次看看這塊都有不同的收獲.(在這里我不會長篇大論,只會挑重點;具體的小伙伴們自行查找)參考:https://dev...
閱讀 3228·2021-11-15 11:37
閱讀 2449·2021-09-29 09:48
閱讀 3812·2021-09-22 15:55
閱讀 3013·2021-09-22 10:02
閱讀 2635·2021-08-25 09:40
閱讀 3224·2021-08-03 14:03
閱讀 1691·2019-08-29 13:11
閱讀 1569·2019-08-29 12:49