「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端
JavaScript 是一種很好的語言。它有一個簡單的語法,龐大的生態系統,以及最重要,最偉大的社區。同時,咱們都知道,JavaScript 是一個很是有趣又充滿戲法的語言。他們中的有些能夠迅速將咱們的平常工做變成地獄,有些可讓咱們大聲笑起來。web
這些示例的主要目的是收集一些瘋狂的例子,並解釋它們如何工做,若是可能的話。只是由於學習之前不瞭解的東西頗有趣。若是您是初學者,您能夠閱讀此文章來深刻了解 JavaScript。我但願這個文章會激勵你花更多的時間閱讀規範。若是您是專業開發人員,您能夠將這些示例視爲您公司新手訪問問題和測驗的重要資源。同時,這些例子在準備面試時會很方便。不管如何,讀讀看。也許你會爲本身找到新的東西。面試
Number(true); // -> 1
Number(false); // -> 0
1 + 0; // -> 1
複製代碼
布爾值被轉換爲它們的數字表示後端
!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true
複製代碼
考慮一下這一步:數組
true == "true"; // -> true
false == "false"; // -> false
// 'false' 不是空字符串,因此它的值是 true
!!"false"; // -> true
!!"true"; // -> true
複製代碼
"b" + "a" + +"a" + "a"; // baNaNa
複製代碼
用 JavaScript 寫的老派笑話:瀏覽器
"foo" + +"bar"; // -> 'fooNaN'
複製代碼
這個表達式能夠轉化成 'foo' + (+'bar')
,但沒法將'bar'
強制轉化成數值。markdown
NaN
不是一個 NaN
NaN === NaN; // -> false
複製代碼
規範嚴格定義了這種行爲背後的邏輯:app
- 若是
Type(x)
不一樣於Type(y)
, return false.- 若是
Type(x)
數值, 而後
- 若是
x
是 NaN, return false.- 若是
y
是 NaN, return false.- … … …
遵循 IEEE 的「NaN」的定義:函數
有四種可能的相互排斥的關係:小於,等於,大於和無序。 當至少一個操做數是 NaN 時,即是最後一種狀況。每一個 NaN 都要比較無窮無盡的一切,包括本身。oop
你不會相信,但...
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
複製代碼
將大量的符號分解成片斷,咱們注意到,如下表達式常常出現:
![] + []; // -> 'false'
![]; // -> false
複製代碼
因此咱們嘗試將[]
和false
加起來。 可是經過一些內部函數調用(binary + Operator
- >ToPrimitive
- >[[DefaultValue]
]),咱們最終將右邊的操做數轉換爲一個字符串:
![] + [].toString(); // 'false'
複製代碼
將字符串做爲數組,咱們能夠經過[0]
來訪問它的第一個字符:
"false"[0]; // -> 'f'
複製代碼
如今,其他的是明顯的,能夠本身弄清楚!
[]
是 true
, 但它不等於 true
!![] // -> true
[] == true // -> false
複製代碼
數組是一個true
,可是它不等於true
。
null
是 false, 但又不等於 false
儘管 null
是 false
,但它不等於 false
。
!!null; // -> false
null == false; // -> false
複製代碼
同時,其餘的一些等於 false 的值,如 0
或 ''
等於 false
。
0 == false; // -> true
"" == false; // -> true
複製代碼
跟前面的例子相同。這是一個相應的連接:
document.all
是一個 object,但又同時是 undefined⚠️ 這是瀏覽器 API 的一部分,對於 Node.js 環境無效 ⚠️
儘管 document.all 是一個 array-like object 而且經過它能夠訪問頁面中的 DOM 節點,但在經過 typeof
的檢測結果是 undefined
。
document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'
複製代碼
同時,document.all
不等於 undefined
。
document.all === undefined; // -> false
document.all === null; // -> false
複製代碼
可是同時:
document.all == null; // -> true
複製代碼
document.all
曾經是訪問頁面 DOM 節點的一種方式,特別是在早期版本的 IE 瀏覽器中。它從未成爲標準,但被普遍使用在早期的 JS 代碼中。當標準演變出新的 API 時(例如document.getElementById
)這個 API 調用就被廢棄了,標準委員會必須決定如何處理它。由於它被普遍使用嗯他們決定保留這個 API 但引入一個有意的對 JavaScript 的標準的違反。 其與undefined
使用嚴格相等比較得出false
而使用抽象相等比較 得出true
是由於這個有意的對標準的違反明確地容許了這一點。
Number.MIN_VALUE
是最小的數字,大於零:
Number.MIN_VALUE > 0; // -> true
複製代碼
Number.MIN_VALUE
是5e-324
,便可以在浮點精度內表示的最小正數,便可以達到零。 它定義了浮點數的最高精度。
如今,總體最小的值是
Number.NEGATIVE_INFINITY
,儘管這在嚴格意義上並非真正的數字。
⚠️ V8 v5.5 或更低版本中出現的 Bug(Node.js <= 7) ⚠️
大家全部人都知道的關於討厭的 undefined 不是 function ,可是這個呢?
// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]
new Foo() instanceof null;
// > TypeError: function is not a function
// > at … … …
複製代碼
這不是規範的一部分。這只是一個錯誤,如今它已被修復,因此未來不會有這個問題。
若是您嘗試兩個數組相加呢?
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
複製代碼
會發生合併。一步一步地,它是這樣的:
[1, 2, 3] +
[4, 5, 6][
// joining
(1, 2, 3)
].join() +
[4, 5, 6].join();
// concatenation
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");
複製代碼
您已經建立了一個包含 4 個空元素的數組。儘管如此,你仍是會獲得一個有三個元素的,由於後面的逗號:
let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'
複製代碼
尾逗號 (有時也稱爲「最後逗號」) 在向 JavaScript 代碼中添加新元素、參數或屬性時有用。若是您想添加一個新屬性,您能夠簡單地添加一個新行,而不用修改之前的最後一行,若是該行已經使用了後面的逗號。這使得版本控制比較清潔和編輯代碼可能不太麻煩。
數組進行相等比較是一個怪物,看下面的例子:
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
複製代碼
你應該很是當心留意上面的例子! 7.2.13 Abstract Equality Comparison 規範描述了這些行爲。
undefined
和 Number
若是咱們不把任何參數傳遞到 Number
構造函數中,咱們將獲得 0
。undefined
是一個賦值形參,沒有實際的參數,因此您可能指望 NaN
將 undefined
做爲參數的值。然而,當咱們經過 undefined
,咱們將獲得 NaN
。
Number(); // -> 0
Number(undefined); // -> NaN
複製代碼
根據規範:
n
爲 +0
;n
調用 ToNumber(value)
undefined
,那麼 ToNumber(undefined)
應該返回 NaN
.parseInt
是一個壞蛋parseInt
它以的怪異而出名。
parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15
複製代碼
**💡 說明:
** 這是由於 parseInt
會持續經過解析直到它解析到一個不識別的字符,'f*ck'
中的 f
是 16 進制下的 15
。
解析 Infinity
到整數也頗有意思…
//
parseInt("Infinity", 10); // -> NaN
// ...
parseInt("Infinity", 18); // -> NaN...
parseInt("Infinity", 19); // -> 18
// ...
parseInt("Infinity", 23); // -> 18...
parseInt("Infinity", 24); // -> 151176378
// ...
parseInt("Infinity", 29); // -> 385849803
parseInt("Infinity", 30); // -> 13693557269
// ...
parseInt("Infinity", 34); // -> 28872273981
parseInt("Infinity", 35); // -> 1201203301724
parseInt("Infinity", 36); // -> 1461559270678...
parseInt("Infinity", 37); // -> NaN
複製代碼
也要當心解析 null
:
parseInt(null, 24); // -> 23
複製代碼
💡 說明:
它將
null
轉換成字符串'null'
,並嘗試轉換它。 對於基數 0 到 23,沒有能夠轉換的數字,所以返回 NaN。 在 24,「n」
,第 14 個字母被添加到數字系統。 在 31,「u」
,添加第 21 個字母,能夠解碼整個字符串。 在 37 處,再也不有能夠生成的有效數字集,並返回NaN
。
不要忘記八進制:
parseInt("06"); // 6
parseInt("08"); // 8 若是支持 ECMAScript 5
parseInt("08"); // 0 若是不支持 ECMAScript 5
複製代碼
💡 說明:
這是由於 parseInt
可以接受兩個參數,若是沒有提供第二個參數,而且第一個參數以 0
開始,它將把第一個參數當作八進制數解析。
parseInt
老是把輸入轉爲字符串:
parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1
複製代碼
解析浮點數的時候要注意
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
複製代碼
💡 說明: ParseInt
接受字符串參數並返回一個指定基數下的證書。ParseInt
也去除第一個字符串中非數字字符(字符集由基數決定)後的內容。0.000001
被轉換爲 "0.000001"
而 parseInt
返回 0
。當 0.0000001
被轉換爲字符串時它被處理爲 "1e-7"
所以 parseInt
返回 1
。1/1999999
被轉換爲 5.00000250000125e-7
而 parseInt
返回 5
。
true
和 false
數學運算咱們作一些數學計算:
true +
true(
// -> 2
true + true
) *
(true + true) -
true; // -> 3
複製代碼
嗯… 🤔
咱們能夠用 Number
構造函數強制轉化成數值。 很明顯,true
將被強制轉換爲 1
:
Number(true); // -> 1
複製代碼
一元加運算符嘗試將其值轉換成數字。 它能夠轉換整數和浮點的字符串表示,以及非字符串值 true
,false
和 null
。 若是它不能解析特定的值,它將轉化爲 NaN
。 這意味着咱們能夠更容易地強制將 true
換成 1
+true; // -> 1
複製代碼
當你執行加法或乘法時,ToNumber
方法調用。 根據規範,該方法返回:
若是
參數
is true , 返回 1 。 若是參數
是 false 返回 +0。
這就是爲何咱們能夠進行進行布爾值相加並獲得正確的結果
相應部分:
你會留下深入的印象,<!--
(這是 HTML 註釋)是一個有效的 JavaScript 註釋。
// 有效註釋
<!-- 也是有效的註釋
複製代碼
感動嗎? 相似 HTML 的註釋旨在容許不理解標籤的瀏覽器優雅地降級。這些瀏覽器,例如 Netscape 1.x 已經再也不流行。所以,在腳本標記中添加 HTML 註釋是沒有意義的。
因爲 Node.js 基於 V8 引擎,Node.js 運行時也支持相似 HTML 的註釋。
NaN
儘管 NaN
類型是 'number'
,可是 NaN
不是數字的實例:
typeof NaN; // -> 'number'
NaN instanceof Number; // -> false
複製代碼
typeof
和 instanceof
運算符的工做原理:
[]
和 null
是對象typeof []; // -> 'object'
typeof null; // -> 'object'
// 然而
null instanceof Object; // false
複製代碼
typeof
運算符的行爲在本節的規範中定義:
根據規範,typeof
操做符返回一個字符串 。對於沒有 [[Call]]
實現的 null
、普通對象、標準特異對象和非標準特異對象,它返回字符串 "object「
。
可是,您可使用 toString
方法檢查對象的類型。
Object.prototype.toString.call([]);
// -> '[object Array]'
Object.prototype.toString.call(new Date());
// -> '[object Date]'
Object.prototype.toString.call(null);
// -> '[object Null]'
複製代碼
999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000
複製代碼
這是由 IEEE 754-2008 二進制浮點運算標準引發的。
0.1 + 0.2
精度計算來自 JavaScript 的知名笑話。0.1
和 0.2
相加是存在精度錯誤的
0.1 +
0.2(
// -> 0.30000000000000004
0.1 + 0.2
) ===
0.3; // -> false
複製代碼
浮點計算壞了:
程序中的常量
0.2
和0.3
也將近似爲真實值。最接近0.2
的double
大於有理數0.2
,但最接近0.3
的double
小於有理數0.3
。0.1
和0.2
的總和大於有理數0.3
,所以不符合您的代碼中的常數判斷。
這個問題是衆所周知的,甚至有一個網站叫 0.30000000000000004.com。
您能夠添加本身的方法來包裝對象,如 Number
或 String
。
Number.prototype.isOne = function() {
return Number(this) === 1;
};
(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0)
.isOne()(
// -> false
7
)
.isOne(); // -> false
複製代碼
顯然,您能夠像 JavaScript 中的任何其餘對象同樣擴展 Number
對象。可是,不建議擴展不屬於規範的行爲定義。如下是 Number
屬性的列表:
1 < 2 < 3; // -> true
3 > 2 > 1; // -> false
複製代碼
爲何會這樣呢?其實問題在於表達式的第一部分。如下是它的工做原理:
1 < 2 < 3; // 1 < 2 -> true
true < 3; // true -> 1
1 < 3; // -> true
3 > 2 > 1; // 3 > 2 -> true
true > 1; // true -> 1
1 > 1; // -> false
複製代碼
咱們能夠用 大於或等於運算符(>=
):
3 > 2 >= 1; // true
複製代碼
一般 JavaScript 中的算術運算的結果多是很是難以預料的。 考慮這些例子:
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
複製代碼
前四個例子發生了什麼?這是一個小表,以瞭解 JavaScript 中的添加:
Number + Number -> addition
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation
String + Boolean -> concatenation
String + String -> concatenation
複製代碼
剩下的例子呢?在相加以前,[]
和 {}
隱式調用 ToPrimitive
和 ToString
方法。
String
的實例"str"; // -> 'str'
typeof "str"; // -> 'string'
"str" instanceof String; // -> false
複製代碼
String
構造函數返回一個字符串:
typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true
複製代碼
咱們來試試一個 new
:
new String("str") == "str"; // -> true
typeof new String("str"); // -> 'object'
複製代碼
對象?那是什麼?
new String("str"); // -> [String: 'str']
複製代碼
注: 部份內容參考自 jsisweird