假如易立競問你如何判斷 JavaScript 中的數據類型?

美味值:🌟🌟🌟🌟🌟javascript

口味:芥末蝦仁球前端

爲了和易老師對線,咱們先來簡單複習下。java

JavaScript 的數據類型包括原始類型和對象類型:git

  • 原始類型:Null、Undefined、Number、String、Boolean、Symbol、BigInt
  • 對象類型:Object

咱們習慣把對象稱爲引用類型,固然還有不少特殊的引用類型,好比 Function、Array、RegExp、Math、Date、Error、Set、Map、各類定型數組 TypedArray 等。github

原始類型值保存在棧中,對象類型值保存在堆中,在棧中保留了對象的引用地址,當 JavaScript 訪問數據的時候,經過棧中的引用訪問。web

在 JavaScript 中,原始類型的賦值會完整複製變量值,而對象(引用)類型的賦值是複製引用地址。面試

再來兩道常考面試題練練手

let a = {
    name: '前端食堂',
    age: 2
}
let b = a
console.log(a.name)
b.name = '童歐巴'
console.log(a.name)
console.log(b.name)

// 前端食堂
// 童歐巴
// 童歐巴
複製代碼

第一題 So Easy,閉着眼睛也能答對。數組

let a = {
    name: '前端食堂',
    age: 2
}
const expand = function(b) {
    b.age = 18
    b = {
        name: '童歐巴',
        age: 25
    }
    return b
}
let c = expand(a)
console.log(c.age)
console.log(a.age)
console.log(a)

// 25
// 18
// {name: "前端食堂", age: 18}
複製代碼

這道題可能有些同窗會答錯,咱們來一塊兒分析一下:安全

expand 函數傳進來的參數 b,其實傳遞的是對象在堆中的內存地址值,經過調用 b.age = 18 能夠改變 a 對象的 age 屬性。markdown

可是 return 又把 b 變成了另外一個內存地址,將 {name: "童歐巴", age: 25} 存入,致使最後返回 a 的值就變成了 {name: "童歐巴", age: 25}

接下來讓咱們以熱烈的掌聲,歡迎易老師閃亮登場!

我會問你一些問題,你隨時能夠喝水。

JavaScript 中檢測數據類型的方法有哪些你知道嗎?

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString.call()

那 typeof 用起來怎麼樣呢?

1.typeof

typeof 'a' // 'string'
typeof 1   // 'number' 
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol('a') // 'symbol'
typeof 1n // 'bigint'

typeof null // 'object'

typeof function() {} // 'function'
typeof [] // 'object'
typeof {} // 'object'
typeof /a/ // 'object'
typeof new Date() // 'object'
typeof new Error() // 'object'
typeof new Map() // 'object'
typeof new Set() // 'object'
複製代碼

兩條結論:

  1. typeof 能夠判斷除了 null 之外的原始類型。
  2. typeof 只能判斷對象類型中的 Function,其餘判斷不出來,都爲 object。

爲何 typeof null 的值是 object?

typeof 檢測 null 時返回 object,是最初 JavaScript 語言的一個 Bug,爲了兼容老代碼一直保留至今。

若是想了解更多,請戳下面連接。

Tips

這裏不得不提一下 NaN,畢竟咱們都知道它戲比較多。

typeof NaN // number
複製代碼

F**k NaN!

instanceof 能判斷出哪些類型你知道嗎?

2.instanceof

檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。

也就是使用 a instanceof B 判斷的是:a 是否爲 B 的實例,即 a 的原型鏈上是否存在 B 的構造函數。

console.log(1 instanceof Number) // false
console.log(new Number(1) instanceof Number) // true

const arr = []
console.log(arr instanceof Array) // true
console.log(arr instanceof Object) // true

const Fn = function() {
    this.name = '構造函數'
}
Fn.prototype = Object.create(Array.prototype)
let a = new Fn()
console.log(a instanceof Array) // true
複製代碼

兩條結論:

  1. instanceof 能夠準確判斷對象(引用)類型,可是不能準確檢測原始類型。
  2. 因爲咱們能夠隨意修改原型的指向致使檢測結果不許確,因此這種方法是不安全的。

若是我就想用 instanceof 檢測原始類型,你能知足個人需求嗎?

好,知足。

雖然 instanceof 不能檢測原始類型,可是有一種方法可讓其用於檢測原始類型。

Symbol.hasInstance 容許咱們自定義 instanceof 的行爲。

class PrimitiveNumber {
  static [Symbol.hasInstance] = x => typeof x === 'number';
}
123 instanceof PrimitiveNumber; // true

class PrimitiveString {
  static [Symbol.hasInstance] = x => typeof x === 'string';
}
'abc' instanceof PrimitiveString; // true

class PrimitiveBoolean {
  static [Symbol.hasInstance] = x => typeof x === 'boolean';
}
false instanceof PrimitiveBoolean; // true

class PrimitiveSymbol {
  static [Symbol.hasInstance] = x => typeof x === 'symbol';
}
Symbol.iterator instanceof PrimitiveSymbol; // true

class PrimitiveNull {
  static [Symbol.hasInstance] = x => x === null;
}
null instanceof PrimitiveNull; // true

class PrimitiveUndefined {
  static [Symbol.hasInstance] = x => x === undefined;
}
undefined instanceof PrimitiveUndefined; // true
複製代碼

代碼來源下面連接。

既然你對 instanceof 這麼瞭解了,能給我現場手寫一個嗎?

手寫 instanceof

const myInstanceof = function(left, right) {
    if (typeof left !== 'object' || left === null) return false
    let proto = Reflect.getPrototypeOf(left)
    while (true) {
        if (proto === null) return false
        if (proto === right.prototype) return true
        proto = Reflect.getPrototypeOf(proto)
    }
}

const arr = []
console.log(myInstanceof(arr, Array)) // true
console.log(myInstanceof(arr, Object)) // true
console.log(myInstanceof(arr, RegExp)) // false
複製代碼

要理解 instanceof 的工做原理,就必須理解原型鏈,對 JavaScript 原型鏈掌握的不夠深入的同窗能夠戳下面連接學習。

constructor 怎麼樣,好用嗎?

3.constructor

對於數值直接量,直接使用 constructor 是會報錯的,這個錯誤來自於浮點數的字面量解析過程,而不是 "." 做爲存取運算符的處理過程。

在 JS 中,浮點數的小數位是能夠爲空的,所以 1. 和 1.0 會解析成相同的浮點數。

// 因此須要加上一個小括號,小括號運算符可以把數值轉換爲對象
(1).constructor // ƒ Number() { [native code] }
// 或者
1..constructor // ƒ Number() { [native code] }

const a = '前端食堂'
console.log(a.constructor) // ƒ String() { [native code] }
console.log(a.constructor === String) // true

const b = 5
console.log(b.constructor) // ƒ Number() { [native code] }
console.log(b.constructor === Number) // true

const c = true
console.log(c.constructor) // ƒ Boolean() { [native code] }
console.log(c.constructor === Boolean) // true

const d = []
console.log(d.constructor) // ƒ Array() { [native code] }
console.log(d.constructor === Array) // true

const e = {}
console.log(e.constructor) // ƒ Object() { [native code] }
console.log(e.constructor === Object) // true

const f = () => 1
console.log(f.constructor) // ƒ Function() { [native code] }
console.log(f.constructor === Function) // true

const g = Symbol('1')
console.log(g.constructor) // ƒ Symbol() { [native code] }
console.log(g.constructor === Symbol) // true

const h = new Date()
console.log(h.constructor) // ƒ Date() { [native code] }
console.log(h.constructor === Date) // true

const i = 11n
console.log(i.constructor) // ƒ BigInt() { [native code] }
console.log(i.constructor === BigInt) // true

const j = /a/
console.log(j.constructor) // ƒ RegExp() { [native code] }
console.log(j.constructor === RegExp) // true


String.prototype.constructor = 'aaa'
console.log(a.constructor === String) // false

const k = null
console.log(k.constructor) // Cannot read property 'constructor' of null

const l = undefined
console.log(l.constructor) // Cannot read property 'constructor' of undefined
複製代碼

兩條結論:

  1. 除了 null 和 undefined,constructor 能夠正確檢測出原始類型和對象(引用)類型。
  2. 因爲咱們能夠隨意修改 constructor 致使檢測結果不許確,因此這種方法是不安全的。

還剩下 Object.prototype.toString 了,它就無懈可擊了嗎?

4.Object.prototype.toString

toString() 方法返回一個表示該對象的字符串,咱們能夠改變它的 this 指向,將 this 指向要檢測的值,便可返回當前檢測值的信息。

Object.prototype.toString({}) // '[object Object]'

Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call('a') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(Symbol('a')) // '[object Symbol]'
Object.prototype.toString.call(11n) // '[object BigInt]'
Object.prototype.toString.call(/a/) // '[object RegExp]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call([0, 1, 2]) // '[object Array]'
Object.prototype.toString.call(function() {}) // '[object Function]'
Object.prototype.toString.call(new Error()) // '[object Error]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new Map()) // '[object Map]'
複製代碼

你能封裝一個檢測數據類型的通用方法嗎?

封裝檢測數據類型的通用方法

封裝方法的時候注意大小寫。

方案有不少種,這裏簡單提供兩個思路。

const getType = function(obj) {
    let type = typeof obj
    if (type !== 'object') {
        return type
    }
    return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1').toLowerCase()
}

getType({}) // object
getType('a') // string
getType(1) // number
getType(true) // boolean
getType(null) // null
getType(undefined) // undefined
getType(Symbol('a')) // symbol
getType(11n) // bigint
getType(/a/) // regexp
getType(new Date()) // date
getType([0, 1, 2]) // array
getType(function() {}) // function
getType(new Error()) // error
getType(new Map()) // map
getType(new Set()) // set
複製代碼

固然,換個姿式,這樣也能夠實現。

Object.prototype.toString.call('1').slice(8, -1).toLowerCase()
// 'string'
複製代碼

聊到這,基本上就是滿分答案了。

若是你以爲哪裏有遺漏,歡迎在評論區補充。

最後一個易老師的問題留給你們:

你,喜歡 JavaScript 嗎?

❤️愛心三連擊

1.若是你以爲食堂酒菜還合胃口,就點個贊支持下吧,你的是我最大的動力。

2.關注公衆號前端食堂,吃好每一頓飯!

3.點贊、評論、轉發 === 催更!

相關文章
相關標籤/搜索