現在去做前端面試題都是心虛的, 本來可以做對的題,想想好像有坑,然後答錯了。舉個例子:
Number([0]); // 0
[0] == true; // false
if ([0]) alert('ok'); // "ok" // 恩? 不都是 false 嗎
複製程式碼
所以本文將盡可能多的去試圖挖一下 javascript 資料型別方面的蹊蹺。
資料相等
這張圖大夥應該很熟悉了,但其實這裡面有些很詭異的問題,很迷很迷。
0 == '0'; // true
0 == []; // true
'0' == []; // false
複製程式碼
雙等時也許是進行了型別轉換的, 比如都轉為數字或字串後再進行的比較。
個人猜測轉換的順序 可能 如下:
undefined < null < Boolean < Number < String < Array
複製程式碼
它是一層層向下進行轉換後進行比較的。
'0' == true // false
// 實則是 0 == true 的比較
複製程式碼
再比如
Boolean([0]); // true
[0] == true; // false
// 實際是 '0' == true 最後 0 == true 的比較
複製程式碼
<=
這類數值判斷,也是類似的,但很快就發現,
以上猜測並不完善,還有更多一步前置的 Number
轉換。
2 > true; // true
'1' > '2'; // false
複製程式碼
undefined == undefined; // true
undefined <= undefined; // false
// 因為 Number(undefined) 的結果是 NaN
複製程式碼
注意 [2] == [2]
當然是為 false
啦,
這個可 別混淆 了,以為也要去轉化。
此處稍微提一下 -0
的存在,會造成 Infinity
與 -Infinity
的不同。
但我們多半會做分母不為零的判斷的,恩大概會的吧。
0 === -0; // true
(1/-0) === (1/0); // false
複製程式碼
資料型別判斷
if 判斷
一般使用 if 大致會有以下五種情況,三目判斷 和 並或非 也包含其中。
if (a <= b)
if (a)
if (a())
if (a = 1)
if (!a)
複製程式碼
如圖所示,if 中結果即是 Boolean()
轉化後的結果。
請再回味一番,切實記住 if 判斷與等於判斷的不同喲。
還以為 !a
的判斷會有坑,試驗下來舒了口氣,並沒有什麼特別之處。
typeof 判斷
這章好像要記住的和留意的東西也並不多,
typeof [] === 'object';
typeof NaN === 'number'
typeof null === 'object'
複製程式碼
卻也是判斷中稍有點難判的,所以才出現了 Array.isArray
和 isNaN
這樣的方法存在。
為啥我試不出 typeof 為 array 的情況呀,很奇怪耶,是我記錯了咩
還有像 Date
RegExp
arguments
等自然就是物件了,typeof
的坑相對要少很多。
instanceof 判斷
用 [] instanceof Array
判陣列真的很方便,但這塊也還是有坑的。
'a' instanceof String // false
(new String('a')) instanceof String // true
複製程式碼
除此之外,還有原型鏈上的一點問題:
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo); //true
Foo.prototype = new Aoo();
var foo2 = new Foo();
console.log(foo2 instanceof Foo) // true
console.log(foo2 instanceof Aoo) // true
複製程式碼
說實話,除了幾個特例,用這個來判原型其實並不是很好的方法。 參考:www.ibm.com/developerwo…
constructor 判斷
constructor
相比 instanceof
有一點優勢,就是它不隨 __proto__
的改變
function A(){};
var a = new A();
var b = new A();
a.__proto__ = {};
a instanceof A // false
b.constructor === A // true
複製程式碼
以往 es5 做繼承時還要自己給 A.prototype.constructor
設新值,
有了 es6 的 class
後已經很簡單了,用 constructor
來判原型也穩妥了起來。
至於基礎資料型別嘛,也不太推薦用此方法。
is 方法判斷
isFinite();
isNaN();
Number.isNaN();
Array.isArray();
複製程式碼
其他判斷
Object.is() 判斷
其實 Object.is() 是類似 === 的,但又有點不一樣,它是真真正正的絕對相等。
+0 === -0 // true
Object.is(+0, -0) // false
NaN === NaN // false
Object.is(NaN, NaN) // true
複製程式碼
key in object 判斷
還需稍微分清一下原型與例項即可,即 for-in
和 for-of
的區別。
'0' in [1, 2]; // true
'now' in Date; // true
'getFullYear' in Date; // false
複製程式碼
至於專案是使用以下哪種判斷就見仁見智了。
if (Array.prototype.includes) {}
'includes' in [];
複製程式碼
prototype 判斷
obj.hasOwnProperty(key)
和 obj.isPrototypeOf(obj2)
等相關方法,整理中
強制資料型別轉換
運算式自動轉換
+' 014' // 14
+'0x12' // 18
1 + '14' // '114'
1 + '0x12' // '10x12'
1 + +'14' // 15
'14' + 1 // '141'
1 + [1, 1]; // '11,1'
1 + {}; // '1[object Object]'
1 + null; // 1
1 +undefined; // NaN
複製程式碼
很鮮明,當有單獨的運算子存在時(單加括號是不行滴),
會幫忙 Number
轉換,否則 String
轉換。
還請注意上例中後 4 種特殊的情況。
進行 ++
運算時並不會幫忙轉換為數字,還容易報錯。
所以使用時這裡得留個心眼喲。
++'14' // ReferenceError
複製程式碼
還有兩個特立獨行的數字運算,即 Infinity
和 0
的正負號。
Infinity+Infinity; // Infinity
-Infinity+(-Infinity); // -Infinity
Infinity+(-Infinity); // NaN
+0+(+0); // 0
(-0)+(-0); // -0
(+0)+(-0); // 0
複製程式碼
再看一個絕對不會遇到的特例,
{} + []
理論上應該是 '[object Object]' + ''
才對,
就算不是也應該是 NaN + 0
吧,結果我又猜錯了。
遇事不決問百度,結果震驚了,這裡的 {}
被當成空程式碼塊了,+[]
自然就是 0
了。
[] + {}; // '[object Object]'
{} + []; // 0
複製程式碼
物件式轉換
Number() 轉換
Number
與 parseInt
的不同,將於下文 parseInt 系列方法 講述
String() 轉換
探討一下 String
和 toString
的不同吧。
一方面是部分資料型別沒有 toString
方法:
String(null); // 'null'
(null).toString(); // Uncaught TypeError
(undefined).toString(); // Uncaught TypeError
複製程式碼
另一方面是 toString
可以傳個進位制數的參(僅對數字型別有用)
(30).toString(16); // "1e"
('30').toString(16); // "30"
複製程式碼
至於 Date
Error
RegRxp
的字串化,基本不會出啥么蛾子。
用原型的 toString
來判資料型別也是種很巧妙常用的方法。
function typeOf(obj) {
var typeStr = Object.prototype.toString.call(obj).split(" ")[1];
return typeStr.substr(0, typeStr.length - 1).toLowerCase();
}
typeOf([]); // 'array'
複製程式碼
函式式轉換
原型方法
toString
在上文已有介紹,但還得再區分一下陣列的。
[1,[2,"abc","",0,null,undefined,false,NaN],3].toString();
// "1,2,abc,,0,,,false,NaN,3"
複製程式碼
也即是下例想表達的意思:
(null).toString(); // Uncaught TypeError
[null].toString(); // ''
複製程式碼
toString
與 valueOf
大致是相同的,但是否有不同,整理中...
再則 (1).toFixed
Date.parse
等,應該不會有啥常見錯誤。
只需注意那些是會 對入參進行隱形轉換 的,下文 引數的隱形轉換 將介紹
parseInt 系列方法
window.parseInt
和 Number.parseInt
是全等的,即完全相同。
主要來看 Number
與 parseInt
的不同,挺迷的,
它們並不是單純的資料型別轉化那麼簡單,舉個例子:
Number(''); // 0
parseInt(''); // NaN
複製程式碼
parseInt
就很花哨,還會再多進行一些暗箱操作來判斷和適配成數字。
可見,用 Number
轉非整數時會是更好的選擇。
parseInt(' 10 '); // 10 // 自動去空格,通用
parseInt('10.2'); // 10 // 數字後的全剔除,Number 和 parseFloat 沒問題
parseInt('1e2'); // 1 // 區分不出科學計數法,Number 和 parseFloat 沒問題
parseFloat('0x5'); // 0 // 區分不出進位制,Number 和 parseInt 沒問題
複製程式碼
當引數為陣列時,當然也是先轉 String
的咯,
而 parseInt
又能去除 ,
後的字元,所以就有下面的情況。
Number([1, 2]); // NaN
parseInt([1, 2]); // 1
複製程式碼
引數的隱形轉換
比較典型的 isNaN
是先用 Number
轉了一次,但 Number.isNaN
就沒有。
isNaN('1x'); // true
Number.isNaN('1x'); // false
複製程式碼
這方面沒做什麼整理,遇到了再補吧。
'12'.replace(1, ''); // "2"
複製程式碼
其他強行轉換
JSON.stringify()
JSON.parse(JSON.strigify())
深拷貝時可得注意了喲。
其實遞迴加物件解構來做深拷貝要更好一些喲。
JSON.stringify(Infinity); // 'null'
JSON.stringify(NaN); // 'null'
JSON.stringify(undefined); // undefined
JSON.stringify({a: undefined}); // '{}'
JSON.stringify({a: null}); // '{"a":null}'
JSON.stringify(() => {}); // 'undefined'
複製程式碼
encode 系列
encodeURI
方法不會對下列字元編碼 ASCII字母、數字、~!@#$&*()=:/,;?+'
encodeURIComponent
方法不會對下列字元編碼 ASCII字母、數字、~!*()'
所以 encodeURIComponent
比 encodeURI
編碼的範圍更大。
其他
Array.from('foo'); // ["f", "o", "o"]
Object.assign([1], [2,3]); // [2, 3]
複製程式碼
大致就是這些了,寫完要自閉一會,整個過程充滿了懷疑與揣測。 雖然做了較為系統的拆分,但還是得承認沒寫好,敬請期待後續吧。
我還有一個 BUG 庫,不妨也分享出來一起看看吧。