1. 數(shù)據(jù)類(lèi)型概念
JavaScript 的數(shù)據(jù)類(lèi)型有下圖所示的 8 種:
其中吹害,前 7 種類(lèi)型為基礎(chǔ)類(lèi)型,最后 1 種(Object)為引用類(lèi)型,而引用數(shù)據(jù)類(lèi)型(Object)又分為圖上這幾種常見(jiàn)的類(lèi)型:Array - 數(shù)組對(duì)象
、RegExp - 正則對(duì)象
、Date - 日期對(duì)象
劲够、Math - 數(shù)學(xué)函數(shù)
震桶、Function - 函數(shù)對(duì)象
- 基礎(chǔ)類(lèi)型存儲(chǔ)在棧內(nèi)存,被引用或拷貝時(shí)征绎,會(huì)創(chuàng)建一個(gè)完全相等的變量蹲姐;
- 引用類(lèi)型存儲(chǔ)在堆內(nèi)存,存儲(chǔ)的是地址人柿,多個(gè)引用指向同一個(gè)地址
2. 數(shù)據(jù)類(lèi)型檢測(cè)常用的三種方法
2.1 typeof
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
typeof null 會(huì)輸出 object柴墩,這是 JS 存在的一個(gè)悠久 Bug,引用數(shù)據(jù)類(lèi)型 Object凫岖,用 typeof 來(lái)判斷的話江咳,除了 function 會(huì)判斷為 OK 以外,其余都是 'object'哥放,是無(wú)法判斷出來(lái)的
2.2 instanceof
new 一個(gè)對(duì)象歼指,那么這個(gè)新對(duì)象就是它原型鏈繼承上面的對(duì)象了,通過(guò) instanceof 我們能判斷這個(gè)對(duì)象是否是之前那個(gè)構(gòu)造函數(shù)生成的對(duì)象甥雕,這樣就基本可以判斷出這個(gè)新對(duì)象的數(shù)據(jù)類(lèi)型
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('Mercedes Benz')
car instanceof String // true
let str = 'Covid-19'
str instanceof String // false
2.2.1 具體實(shí)現(xiàn)
function myInstanceof(left, right) {
// 判斷是否為引用數(shù)據(jù)類(lèi)型
if(typeof left !== 'object' || left === null) return false;
// getProtypeOf是Object對(duì)象自帶的API踩身,能夠拿到參數(shù)的原型對(duì)象
let proto = Object.getPrototypeOf(left);
while(true) { // 循環(huán)往下尋找,直到找到相同的原型對(duì)象
if(proto === null) return false;
if(proto === right.prototype) return true; // 找到相同原型對(duì)象社露,返回true
proto = Object.getPrototypeof(proto);
}
}
console.log(myInstanceof(new Number(123), Number)); // true
console.log(myInstanceof(123, Number)); // false
instanceof
可以準(zhǔn)確地判斷復(fù)雜引用數(shù)據(jù)類(lèi)型挟阻,但是不能正確判斷基礎(chǔ)數(shù)據(jù)類(lèi)型- 而
typeof
也存在弊端,它雖然可以判斷基礎(chǔ)數(shù)據(jù)類(lèi)型(null 除外),但是引用數(shù)據(jù)類(lèi)型中附鸽,除了 function 類(lèi)型以外脱拼,其他的也無(wú)法判斷
2.3 Object.prototype.toString
toString()
是 Object 的原型方法,調(diào)用該方法拒炎,可以統(tǒng)一返回格式為 "[object Xxx]"
的字符串挪拟,其中 Xxx
就是對(duì)象的類(lèi)型。
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上結(jié)果击你,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
2.3.1 具體實(shí)現(xiàn)
function getType(obj){
let type = typeof obj;
// 先進(jìn)行typeof判斷玉组,如果是基礎(chǔ)數(shù)據(jù)類(lèi)型,直接返回
if (type !== "object") {
return type;
}
// 對(duì)于typeof返回結(jié)果是object的丁侄,再進(jìn)行如下的判斷惯雳,正則返回結(jié)果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
/* 代碼驗(yàn)證,需要注意大小寫(xiě)鸿摇,哪些是typeof判斷石景,哪些是toString判斷? */
getType([]) // "Array" typeof []是object拙吉,因此toString返回
getType('123') // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null) // "Null"首字母大寫(xiě)潮孽,typeof null是object,需toString來(lái)判斷
getType(undefined) // "undefined" typeof 直接返回
getType() // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判斷筷黔,因此首字母小寫(xiě)
getType(/123/g) //"RegExp" toString返回
3. 數(shù)據(jù)類(lèi)型轉(zhuǎn)換
'123' == 123 // true
'' == null // false
'' == 0 // true
[] == 0 // true
[] == '' // true
[] == ![] // true
null == undefined // true
Number(null) // 0
Number('') // 0
parseInt('') // NaN
{} + 10 // 10
10 + {} // "10[object Object]"
// 對(duì)象在作為操作數(shù)時(shí)往史,解釋器總是優(yōu)先調(diào)用valueOf(), 而其他情況佛舱,解釋器總是認(rèn)為我們想要的是字符串椎例,所以會(huì)優(yōu)先調(diào)用toString()。 因此對(duì)象在前面返回結(jié)果就是Number;其他情況對(duì)象默認(rèn)用toString
let obj = {
[Symbol.toPrimitive]() {
return 200;
},
valueOf() {
return 300;
},
toString() {
return 'Hello';
}
}
console.log(obj + 200); // 400
3.1 強(qiáng)制類(lèi)型轉(zhuǎn)換
強(qiáng)制類(lèi)型轉(zhuǎn)換方式包括 Number()请祖、parseInt()订歪、parseFloat()、toString()肆捕、String()刷晋、Boolean()
3.1.1 Number() 方法的強(qiáng)制轉(zhuǎn)換規(guī)則
轉(zhuǎn)換類(lèi)型 | 轉(zhuǎn)換結(jié)果 |
---|---|
布爾值 | true 和 false 分別被轉(zhuǎn)換為 1 和 0 |
數(shù)字 | 自身 |
null | 0 |
undefined | NaN |
字符串 | 字符串中只包含數(shù)字(或者是 0X / 0x 開(kāi)頭的十六進(jìn)制數(shù)字字符串,允許包含正負(fù)號(hào))慎陵,則將其轉(zhuǎn)換為十進(jìn)制掏秩;<br />字符串中包含有效的浮點(diǎn)格式,將其轉(zhuǎn)換為浮點(diǎn)數(shù)值荆姆;<br />空字符串蒙幻,將其轉(zhuǎn)換為 0;<br />不是以上格式的字符串胆筒,均返回 NaN |
Symbol | 拋出錯(cuò)誤 |
對(duì)象 | 部署了 [Symbol.toPrimitive] 邮破,那么調(diào)用此方法诈豌,<br />否則調(diào)用對(duì)象的 valueOf() 方法,然后依據(jù)前面的規(guī)則轉(zhuǎn)換返回的值抒和;<br />如果轉(zhuǎn)換的結(jié)果是 NaN 矫渔,則調(diào)用對(duì)象的 toString() 方法,再次依照前面的順序轉(zhuǎn)換返回對(duì)應(yīng)的值 |
Number('0111'); //111
Number(null); // 0
Number(''); // 0
Number('1a'); // NaN
Number(-0X11); //-17
3.1.2 Boolean() 方法的強(qiáng)制轉(zhuǎn)換規(guī)則
除了 undefined摧莽、 null庙洼、 false、 ' '镊辕、 0(包括 +0油够,-0)、 NaN 轉(zhuǎn)換出來(lái)是 false征懈,其他都是 true
3.2 隱式類(lèi)型轉(zhuǎn)換
凡是通過(guò)邏輯運(yùn)算符 (&&石咬、 ||、 !)卖哎、運(yùn)算符 (+鬼悠、-、*亏娜、/)焕窝、關(guān)系操作符 (>、 <维贺、 <= 它掂、>=)、相等運(yùn)算符 (==) 或者 if/while
條件的操作幸缕,如果遇到兩個(gè)數(shù)據(jù)類(lèi)型不一樣的情況,都會(huì)出現(xiàn)隱式類(lèi)型轉(zhuǎn)換
3.2.1 '==' 的隱式類(lèi)型轉(zhuǎn)換規(guī)則
轉(zhuǎn)換類(lèi)型 | 轉(zhuǎn)換結(jié)果 |
---|---|
類(lèi)型相同 | 無(wú)須進(jìn)行類(lèi)型轉(zhuǎn)換 |
其中一個(gè)操作值是 null 或者 undefined | 另一個(gè)操作符必須為 null 或者 undefined晰韵,返回 true<br />否則返回 false |
其中一個(gè)是 Symbol 類(lèi)型 | 返回 false |
兩個(gè)操作值為 string 和 number 類(lèi)型 | 將字符串轉(zhuǎn)換為 number |
一個(gè)操作值是 boolean | 轉(zhuǎn)換成 number |
一個(gè)操作值為 object | 另一方為 string发乔、number 或者 symbol,就會(huì)把 object 轉(zhuǎn)為原始類(lèi)型再進(jìn)行判斷(調(diào)用 object 的 valueOf/toString 方法進(jìn)行轉(zhuǎn)換) |
3.2.2 '+' 的隱式類(lèi)型轉(zhuǎn)換規(guī)則
轉(zhuǎn)換類(lèi)型 | 轉(zhuǎn)換結(jié)果 |
---|---|
其中有一個(gè)是字符串 | 另外一個(gè)是 undefined雪猪、null 或布爾型栏尚,則調(diào)用 toString() 方法進(jìn)行字符串拼接,沒(méi)有toString()方法只恨,則調(diào)用 Strin译仗;<br />如果是純對(duì)象、數(shù)組官觅、正則等纵菌,則默認(rèn)調(diào)用對(duì)象的轉(zhuǎn)換方法,然后再進(jìn)行拼接 |
其中有一個(gè)是數(shù)字 | 另外一個(gè)是 undefined休涤、null咱圆、布爾型或數(shù)字笛辟,則會(huì)將其轉(zhuǎn)換成數(shù)字進(jìn)行加法運(yùn)算,<br />對(duì)象的情況還是參考上一條規(guī)則 |
其中一個(gè)是字符串 | 另一個(gè)是數(shù)字序苏,則按照字符串規(guī)則進(jìn)行拼接 |
'1' + undefined // "1undefined"
'1' + null // "1null"
'1' + true // "1true"
'1' + 1n // '11' 比較特殊字符串和BigInt相加手幢,BigInt轉(zhuǎn)換為字符串
1 + undefined // NaN 規(guī)則2,undefined轉(zhuǎn)換數(shù)字相加NaN
1 + null // 1
1 + true // 2
1 + 1n // 錯(cuò)誤 不能把BigInt和Number類(lèi)型直接混合相加
3.3 Object 的轉(zhuǎn)換規(guī)則
轉(zhuǎn)換類(lèi)型 | 轉(zhuǎn)換結(jié)果 |
---|---|
如果部署了 Symbol.toPrimitive 方法 | 優(yōu)先調(diào)用再返回 |
調(diào)用 valueOf() | 如果轉(zhuǎn)換為基礎(chǔ)類(lèi)型忱详,則返回 |
調(diào)用 toString() | 如果轉(zhuǎn)換為基礎(chǔ)類(lèi)型围来,則返回 |
如果都沒(méi)有返回基礎(chǔ)類(lèi)型 | 會(huì)報(bào)錯(cuò) |
var obj = {
value: 1,
valueOf() {
return 2;
},
toString() {
return '3'
},
[Symbol.toPrimitive]() {
return 4
}
}
console.log(obj + 1); // 5
// 因?yàn)橛蠸ymbol.toPrimitive,就優(yōu)先執(zhí)行這個(gè)匈睁;如果Symbol.toPrimitive這段代碼刪掉监透,則執(zhí)行valueOf打印結(jié)果為3;如果valueOf也去掉软舌,則調(diào)用toString返回'31'
// 再看兩個(gè)特殊的case:
10 + {}
// "10[object Object]"才漆,注意:{}會(huì)默認(rèn)調(diào)用valueOf是{},不是基礎(chǔ)類(lèi)型繼續(xù)轉(zhuǎn)換佛点,調(diào)用toString醇滥,返回結(jié)果"[object Object]",于是和10進(jìn)行'+'運(yùn)算
[1,2,undefined,4,5] + 10
// "1,2,,4,510"超营,注意[1,2,undefined,4,5]會(huì)默認(rèn)先調(diào)用valueOf結(jié)果還是這個(gè)數(shù)組鸳玩,不是基礎(chǔ)數(shù)據(jù)類(lèi)型繼續(xù)轉(zhuǎn)換,也還是調(diào)用toString演闭,返回"1,2,,4,5"不跟,然后再和10進(jìn)行運(yùn)算