
?創(chuàng)作者:全棧弄潮兒
?? 個人主頁: 全棧弄潮兒的個人主頁
??? 個人社區(qū),歡迎你的加入:全棧弄潮兒的個人社區(qū)
?? 專欄地址,歡迎訂閱:前端架構(gòu)師之路
JavaScript 類型及其判斷
JavaScript 具有七種內(nèi)置數(shù)據(jù)類型,它們分別是:
- null
- undefined
- boolean
- number
- string
- object
- symbol
其中,前面五種為基本類型。第六種 object 類型又具體包含了 function、array、date 等。
對于這些類型的判斷,我們常用的方法有:
- typeof
- instanceof
- Object.prototype.toString
- constructor
使用 typeof 判斷類型
基本類型
可以使用 typeof 來判斷:
typeof 5 // "number"
typeof 'lucas' // "string"
typeof undefined // "undefined"
typeof true // "boolean"
但是也存在著一些特例,比如用 typeof 判斷 null 時:
typeof null // "object"
我們再看使用 typeof 判斷復(fù)雜類型時的表現(xiàn):
const foo = () => 1
typeof foo // "function"
const foo = {}
typeof foo // "object"
const foo = []
typeof foo // "object"
const foo = new Date()
typeof foo // "object"
const foo = Symbol("foo")
typeof foo // "symbol"
因此,我們可以總結(jié)出:使用 typeof 可以準(zhǔn)確判斷出除 null 以外的基本類型,以及 function 類型、symbol 類型
;null 會被 typeof 判斷為 object。
使用 instanceof 判斷類型
再來看看 instanceof:
使用 a instanceof B 判斷的是:a 是否為 B 的實(shí)例,即 a 的原型鏈上是否存在 B 構(gòu)造函數(shù) 。因此如果我們使用:
function Person(name) {
this.name = name
}
const p = new Person('lucas')
p instanceof Person
// true
這里 p 是 Person 構(gòu)造出來的實(shí)例。同時,順著 p 的原型鏈,也能找到 Object 構(gòu)造函數(shù):
p.__proto__.__proto__ === Object.prototype
因此:
p instanceof Object
// true
原型原型鏈的知識我們會在后續(xù)章節(jié)中介紹,這里只需要理解 instanceof 的判斷原理即可。另外,一個細(xì)節(jié)需要注意:
5 instanceof Number // false
返回 false,是因?yàn)?5 是基本類型,它并不是 Number 構(gòu)造函數(shù)構(gòu)造出來的實(shí)例對象,如果:
new Number(5) instanceof Number
// true
結(jié)果返回 true。
我們使用以下代碼來模擬 instanceof 原理:
// L 表示左表達(dá)式,R 表示右表達(dá)式
const instanceofMock = (L, R) => {
if (typeof L !== 'object') {
return false
}
while (true) {
if (L === null) {
// 已經(jīng)遍歷到了最頂端
return false
}
if (R.prototype === L.__proto__) {
return true
}
L = L.__proto__
}
}
L 表示左表達(dá)式,R 表示右表達(dá)式,我們可以如此使用:
instanceofMock('', String)
// false
function Person(name) {
this.name = name
}
const p = new Person('lucas')
instanceofMock(p, Person)
// true
使用 constructor 和 Object.prototype.toString 判斷類型
使用 constructor 可以查看目標(biāo)的構(gòu)造函數(shù),這也可以進(jìn)行類型判斷,但也存在著問題,具體請看:
var foo = 5
foo.constructor
// ? Number() { [native code] }
var foo = ‘Lucas’
foo.constructor
// ? String() { [native code] }
var foo = true
foo.constructor
// ? Boolean() { [native code] }
var foo = []
foo.constructor
// ? Array() { [native code] }
var foo = {}
foo.constructor
// ? Object() { [native code] }
var foo = () => 1
foo.constructor
// ? Function() { [native code] }
var foo = new Date()
foo.constructor
// ? Date() { [native code] }
var foo = Symbol(“foo”)
foo.constructor
// ? Symbol() { [native code] }
var foo = undefined
foo.constructor
// VM257:1 Uncaught TypeError: Cannot read property ‘constructor’ of undefined
at :1:5
var foo = null
foo.constructor
// VM334:1 Uncaught TypeError: Cannot read property ‘constructor’ of null
at :1:5
我們發(fā)現(xiàn)對于 undefined 和 null,如果嘗試讀取其 constructor 屬性,將會進(jìn)行報(bào)錯。并且 constructor 返回的是構(gòu)造函數(shù)本身,一般使用它來判斷類型的情況并不多見。
使用 Object.prototype.toString 判斷類型,我們稱之為“萬能方法”,“終極方法”:
console.log(Object.prototype.toString.call(1))
// [object Number]
console.log(Object.prototype.toString.call(‘lucas’))
// [object String]
console.log(Object.prototype.toString.call(undefined))
// [object Undefined]
console.log(Object.prototype.toString.call(true))
// [object Boolean]
console.log(Object.prototype.toString.call({}))
// [object Object]
console.log(Object.prototype.toString.call([]))
// [object Array]
console.log(Object.prototype.toString.call(function(){}))
// [object Function]
console.log(Object.prototype.toString.call(null))
// [object Null]
console.log(Object.prototype.toString.call(Symbol(‘lucas’)))
// [object Symbol]
JavaScript 類型及其轉(zhuǎn)換
JavaScript 的一個顯著特點(diǎn)就是“靈活”?!办`活”的反面就是猝不及防的“坑”多,其中一個典型的例子就是被詬病的類型“隱式轉(zhuǎn)換”。
MDN 這樣介紹過 JavaScript 的特點(diǎn):JavaScript 是一種弱類型或者說動態(tài)語言。這意味著你不用提前聲明變量的類型,在程序運(yùn)行過程中,類型會被自動確定。
我們再來看一些基本例子,在使用加號進(jìn)行運(yùn)算時:
console.log(1 + '1')
// 11
console.log(1 + true)
// 2
console.log(1 + false)
// 1
console.log(1 + undefined)
// NaN
console.log('lucas' + true)
// lucastrue
我們發(fā)現(xiàn):
當(dāng)使用 + 運(yùn)算符計(jì)算 string 和其他類型相加時,都會轉(zhuǎn)換為 string 類型;其他情況,都會轉(zhuǎn)換為 number 類型,但是 undefined 會轉(zhuǎn)換為 NaN,相加結(jié)果也是 NaN
比如布爾值轉(zhuǎn)換為 number 類型:true 為 1,false 為 0,因此:
console.log(1 + true)
// 2
console.log(1 + false)
// 1
console.log(false + false)
// 0
console.log(true + true)
// 2
再看代碼:
console.log({} + true)
// [object Object]true
在 + 號兩側(cè),如果存在復(fù)雜類型,比如對象,那么這到底是怎樣的一套轉(zhuǎn)換規(guī)則呢?
當(dāng)使用 + 運(yùn)算符計(jì)算時,如果存在復(fù)雜類型,那么復(fù)雜類型將會轉(zhuǎn)換為基本類型,再進(jìn)行運(yùn)算。
這就涉及到“對象類型轉(zhuǎn)基本類型
”這個過程。具體規(guī)則:對象在轉(zhuǎn)換基本類型時,會調(diào)用該對象上 valueOf 或 toString 這兩個方法,該方法的返回值是轉(zhuǎn)換為基本類型的結(jié)果
。
那具體調(diào)用 valueOf 還是 toString 呢?這是 ES 規(guī)范所決定的,實(shí)際上這取決于內(nèi)置的 toPrimitive 調(diào)用結(jié)果。主觀上說,這個對象傾向于轉(zhuǎn)換成什么,就會優(yōu)先調(diào)用哪個方法。如果傾向于轉(zhuǎn)換為 Number 類型,就優(yōu)先調(diào)用 valueOf;如果傾向于轉(zhuǎn)換為 String 類型,就只調(diào)用 toString。
valueOf 以及 toString 是可以被開發(fā)者重寫的。比如:
const foo = {
toString () {
return 'lucas'
},
valueOf () {
return 1
}
}
我們對 foo 對象的 valueOf 以及 toString 進(jìn)行了重寫,這時候調(diào)用:
alert(foo)
輸出:lucas。這里就涉及到“隱式轉(zhuǎn)換”,在調(diào)用 alert 打印輸出時,“傾向于使用 foo 對象的 toString 方法,將 foo 轉(zhuǎn)為基本類型”,得以打印出結(jié)果。
然而:
console.log(1 + foo)
輸出:2,這時候的隱式轉(zhuǎn)換“傾向于使用 foo 對象的 valueOf 方法,將 foo 轉(zhuǎn)為基本類型”,得以進(jìn)行相加。
我們再全面總結(jié)一下,對于加法操作
,如果加號兩邊都是 Number 類型
,其規(guī)則為:
- 如果 + 號兩邊存在 NaN,則結(jié)果為 NaN(typeof NaN 是 ‘number’)
- 如果是 Infinity + Infinity,結(jié)果是 Infinity
- 如果是 -Infinity + (-Infinity),結(jié)果是 -Infinity
- 如果是 Infinity + (-Infinity),結(jié)果是 NaN
如果加號兩邊有至少一個是字符串
,其規(guī)則為:
- 如果 + 號兩邊都是字符串,則執(zhí)行字符串拼接
- 如果 + 號兩邊只有一個值是字符串,則將另外的值轉(zhuǎn)換為字符串,再執(zhí)行字符串拼接
- 如果 + 號兩邊有一個是對象,則調(diào)用 valueof() 或者 toStrinig() 方法取得值,轉(zhuǎn)換為基本類型再進(jìn)行字符串拼接。
當(dāng)然也可以進(jìn)行顯式轉(zhuǎn)換,我們往往使用類似 Number、Boolean、String、parseInt 等方法,進(jìn)行顯式類型轉(zhuǎn)換。
JavaScript 函數(shù)參數(shù)傳遞
我們知道 JavaScript 當(dāng)中有“引用賦值”和“基本類型賦值”以及相關(guān)概念:“深拷貝”、“淺拷貝”區(qū)分。那么函數(shù)的參數(shù)傳遞有什么講究呢?請看例題:
let foo = 1
const bar = value => {
value = 2
console.log(value)
}
bar(foo)
console.log(foo)
兩處輸出分別為 2、1;也就是說在 bar 函數(shù)中,參數(shù)為基本類型時,函數(shù)體內(nèi)復(fù)制了一份參數(shù)值,而不會影響參數(shù)實(shí)際值。
let foo = {bar: 1}
const func = obj => {
obj.bar = 2
console.log(obj.bar)
}
func(foo)
console.log(foo)
兩處輸出分別為 2、{bar: 2};也就是說如果函數(shù)參數(shù)是一個引用類型,當(dāng)在函數(shù)體內(nèi)修改這個引用類型參數(shù)的某個屬性值時,將會對參數(shù)進(jìn)行修改。因?yàn)檫@時候函數(shù)體內(nèi)的引用地址指向了原來的參數(shù)。
但是如果在函數(shù)體內(nèi),直接修改了對參數(shù)的引用,則情況又不一樣:
let foo = {bar: 1}
const func = obj => {
obj = 2
console.log(obj)
}
func(foo)
console.log(foo)
兩處輸出分別為 2、{bar: 1};這樣的情況理解起來較為晦澀,其實(shí)總結(jié)下來就是:
- 參數(shù)為基本類型時,函數(shù)體內(nèi)復(fù)制了一份參數(shù)值,對于任何操作不會影響參數(shù)實(shí)際值
- 函數(shù)參數(shù)是一個引用類型時,當(dāng)在函數(shù)體內(nèi)修改這個值的某個屬性值時,將會對參數(shù)進(jìn)行修改
- 函數(shù)參數(shù)是一個引用類型時,如果我們直接修改了這個值的引用地址,則相當(dāng)于函數(shù)體內(nèi)新創(chuàng)建了一份引用,對于任何操作不會影響原參數(shù)實(shí)際值
cannot read property of undefined 問題解決方案
這里我們分析一個常見的 JavaScript 細(xì)節(jié):cannot read property of undefined 是一個常見的錯誤,如果意外的得到了一個空對象或者空值,這樣惱人的問題在所難免。
考慮這樣的一個數(shù)據(jù)結(jié)構(gòu):
const obj = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
{ title: 'Bar', comments: [ 'Ok' ] },
{ title: 'Baz', comments: []}
],
comments: []
}
}
為了在對象中相關(guān)取值的過程,需要驗(yàn)證對象每一個 key 的存在性。常見的處理方案有:
- && 短路運(yùn)算符進(jìn)行可訪問性嗅探
obj.user &&
obj.user.posts &&
obj.user.posts[0] &&
obj.user.posts[0].comments
- || 單元設(shè)置默認(rèn)保底值
(((obj.user || {}).posts||{})[0]||{}).comments
- try…catch
var result
try {
result = obj.user.posts[0].comments
}
catch {
result = null
}
最后,TC39 提案中有一個新的提案,支持:
console.log(obj?.user?.posts[0]?.comments)
由此可見,JavaScript 語言也在不斷演進(jìn)。通過這個案例,想告訴大家:熟練掌握基礎(chǔ)環(huán)節(jié),將對于進(jìn)階起到關(guān)鍵作用。
分析一道網(wǎng)紅題目
綜合以上知識點(diǎn),我們來看一道“網(wǎng)紅”題目:
Can (a == 1 && a == 2 && a == 3) ever evaluate to true?
即:
a == 1 && a == 2 && a == 3 可能為 true 嗎?
直觀上分析,如果變量 a 是一個基本 Number 類型,這是不可能為 true 的,因此解題思路也需要從變量 a 的類型及(對象)轉(zhuǎn)換(基本類型)上來考慮。
方案一:
const a = {
value: 1,
toString: function () {
return a.value++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
這個方案中,我們將 a 定義為一個對象,并重寫了其 toString 方法。因此在每次進(jìn)行判斷時,按照規(guī)則,== 號兩邊出現(xiàn)了對象類型,另一邊是 Number 類型,需要調(diào)用 a 對象 toString 方法,toString 方法的返回值會作為對象轉(zhuǎn)為基本類型的值,我們每次將 value 屬性加 1。同樣,如果按照相同的方式重寫 valueOf 方法,也是可以達(dá)到同樣目的的。
方案二:文章來源:http://www.zghlxwxcb.cn/news/detail-803762.html
let value = 0
Object.defineProperty(window, 'a', {
get: function() {
return ++value
}
})
console.log(a == 1 && a == 2 && a == 3) // true
這里我們將 a 作為屬性,掛載在 window 對象當(dāng)中,重寫其 getter 方法。文章來源地址http://www.zghlxwxcb.cn/news/detail-803762.html
JavaScript 類型判斷總結(jié)
- 通過 x === null 來判斷 null 類型
- 對于 typeof x 不為 object 的情況,直接返回 typeof x 結(jié)果,這時候可以判斷出 number,string,boolean,undefined,symbol 類型
- Object.prototype.toString 方法,該方法確實(shí)可以稱得上“終極方案”。對返回結(jié)果使用 .slice(8, -1),更加方便拿到結(jié)果:
Object.prototype.toString.call(true).slice(8, -1)
// "Boolean"
到了這里,關(guān)于JavaScript 類型判斷及類型轉(zhuǎn)換規(guī)則的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!