美味值:

口味:芥末虾仁球

为了和易老师对线,咱们先来简略温习下。

JavaScript 的数据类型包含原始类型和对象类型:

  • 原始类型:Null、Undefined、Number、String、Boolean、Symbol、BigInt
  • 对象类型:Object

咱们习惯把对象称为援用类型,当然还有很多非凡的援用类型,比方 Function、Array、RegExp、Math、Date、Error、Set、Map、各种定型数组 TypedArray 等。

原始类型值保留在栈中,对象类型值保留在堆中,在栈中保留了对象的援用地址,当 JavaScript 拜访数据的时候,通过栈中的援用拜访。

在 JavaScript 中,原始类型的赋值会残缺复制变量值,而对象(援用)类型的赋值是复制援用地址。

再来两道常考面试题练练手

let a = {    name: '前端食堂',    age: 2}let b = aconsole.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 属性。

然而 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) // falseconsole.log(new Number(1) instanceof Number) // trueconst arr = []console.log(arr instanceof Array) // trueconsole.log(arr instanceof Object) // trueconst 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; // trueclass PrimitiveString {  static [Symbol.hasInstance] = x => typeof x === 'string';}'abc' instanceof PrimitiveString; // trueclass PrimitiveBoolean {  static [Symbol.hasInstance] = x => typeof x === 'boolean';}false instanceof PrimitiveBoolean; // trueclass PrimitiveSymbol {  static [Symbol.hasInstance] = x => typeof x === 'symbol';}Symbol.iterator instanceof PrimitiveSymbol; // trueclass PrimitiveNull {  static [Symbol.hasInstance] = x => x === null;}null instanceof PrimitiveNull; // trueclass PrimitiveUndefined {  static [Symbol.hasInstance] = x => x === undefined;}undefined instanceof PrimitiveUndefined; // true

代码起源上面链接。

  • 有没有一种办法能够将instanceof用于原始JavaScript值?
既然你对 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)) // trueconsole.log(myInstanceof(arr, Object)) // trueconsole.log(myInstanceof(arr, RegExp)) // false

要了解 instanceof 的工作原理,就必须了解原型链,对 JavaScript 原型链把握的不够粗浅的同学能够戳上面链接学习。

  • JavaScript深刻之从原型到原型链
  • 如何答复面试中的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) // trueconst b = 5console.log(b.constructor) // ƒ Number() { [native code] }console.log(b.constructor === Number) // trueconst c = trueconsole.log(c.constructor) // ƒ Boolean() { [native code] }console.log(c.constructor === Boolean) // trueconst d = []console.log(d.constructor) // ƒ Array() { [native code] }console.log(d.constructor === Array) // trueconst e = {}console.log(e.constructor) // ƒ Object() { [native code] }console.log(e.constructor === Object) // trueconst f = () => 1console.log(f.constructor) // ƒ Function() { [native code] }console.log(f.constructor === Function) // trueconst g = Symbol('1')console.log(g.constructor) // ƒ Symbol() { [native code] }console.log(g.constructor === Symbol) // trueconst h = new Date()console.log(h.constructor) // ƒ Date() { [native code] }console.log(h.constructor === Date) // trueconst i = 11nconsole.log(i.constructor) // ƒ BigInt() { [native code] }console.log(i.constructor === BigInt) // trueconst j = /a/console.log(j.constructor) // ƒ RegExp() { [native code] }console.log(j.constructor === RegExp) // trueString.prototype.constructor = 'aaa'console.log(a.constructor === String) // falseconst k = nullconsole.log(k.constructor) // Cannot read property 'constructor' of nullconst l = undefinedconsole.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({}) // objectgetType('a') // stringgetType(1) // numbergetType(true) // booleangetType(null) // nullgetType(undefined) // undefinedgetType(Symbol('a')) // symbolgetType(11n) // bigintgetType(/a/) // regexpgetType(new Date()) // dategetType([0, 1, 2]) // arraygetType(function() {}) // functiongetType(new Error()) // errorgetType(new Map()) // mapgetType(new Set()) // set

当然,换个姿态,这样也能够实现。

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

聊到这,基本上就是满分答案了。

如果你感觉哪里有脱漏,欢送在评论区补充。

最初一个易老师的问题留给大家:

你,喜爱 JavaScript 吗?