重学JS | 数据类型判断

285 阅读5分钟

本文偏基础,主要讲讲 JS 的几种类型判断方式

总览

首先介绍一下 Javascript 的基础类型(7种) Null, Undefined, Boolean, String, Number, Symbol(es6新增), BigInt(es10新增)

另外还有引用类型 ObjectArrayFunctionDateRegExpSetMapErrorMath.

上面基本涵盖了JS 所有数据类型了, 主要数据类型判断方式如下

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString

下面依次介绍。


typeof

typeof 能判断基础的数据类型

typeof a // 'undefined'
typeof '' // 'string'
typeof 1 // 'number'
typeof false //'boolean'
typeof null // 'object'
typeof undefined // 'undefined'
typeof Symbol('a') // 'symbol'
typeof 10n // 'bigint'

// 注意NAN
typeof NaN // 'number'

对于一些 复杂的数据类型就无能为力了

typeof {} // 'object'
typeof [] // 'object'
typeof /s/g // 'object'
typeof function(){} // 'function'
typeof new Date() // 'object' 
typeof new Map() // 'object'
typeof new Set() // 'object'
typeof new Error() // 'object'

注意, 上面例子可以看到, null 判断是一个bug

typeof null // 'object'

为了兼容以前的代码,所以这个bug被保留了。

总结

  • 能判断除 null 外的基础类型
  • 能判断 引用类型中的函数(function)类型

instanceof

instanceof 是用来检测 构造函数的 prototype 属性是否出现在 某个实例对象的原型链上

什么意思呢,?是否出现 的意思就是检测是否相等

const Person = function(){}
const Tom = new Person()
Tom instanceof Person //true
Tom.__proto__ === Person.prototype // true

基础类型是无法通过此方法检验的。

1 instanceof Number // false
'' instanceof String // false

引用类型可以

注意小括号运算符将数值转化为对象

new String(1) instanceof String //true
({}) instanceof Object // true
([]) instanceof Array // true

说到引用类型,这里有四个规则 引用类型的四个规则:

  1. 引用类型,都具有对象特性,即可自由扩展属性。
  2. 引用类型,都有一个隐式原型 proto 属性,属性值是一个普通的对象。
  3. 引用类型,隐式原型 proto 的属性值指向它的构造函数的显式原型 prototype 属性值。
  4. 当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型 prototype)中寻找。

上面内容来自 面不面试的,你都得懂原型和原型链⇲, 当然这需要理解JS的原型链,推荐 重学JS---原型/原型链/继承⇲

还要注意的是 newObject.create 都干了什么事呢。

const FN = function(){}
fn.prototype = Object.create(Array.prototype)
fn.prototype.__proto__ === Array.prototype // true

const fn1 = new FN()
fn1 instanceof Array // true
// 对应着上面第三个原则
fn1.__proto__ === Array.prototype // false
fn1.__proto__.__proto__ === Array.prototype // true

所以

  • new 会让实例属性 __ proto__ 指向 构造函数的 prototype 属性
  • Object.create 会让 生成的对象属性 __ proto__ 指向传入的对象

当然可以用 Symbol.hasInstance 自定义 instanceof 操作符在某个类上的行为。它的主要作用是用于判断某对象是否为某构造器的实例。

class MyArray {
  static [Symbol.hasInstance](instance) {
    console.log('自定义 instance')
    return Array.isArray(instance);
  }
}
[] instanceof MyArray // 自定义 instance true

instanceof 说到这里就有点偏了,补充一个手写 instanceof 和手写 new

  • instanceof
function instanceOf(left, right) {
    let proto = left.__proto__
    while (true) {
        if (proto === null) return false
        if (proto === right.prototype) {
            return true
        }
        proto = proto.__proto__
    }
}
  • new
function newOperator(ctor) {
   if (typeof ctor !== 'function') {
       throw 'newOperator function the first param must be a function';
   }
   newOperator.target = ctor; //目标函数, 更改 new 调用的目标
   var newObj = Object.create(ctor.prototype); // newObj.__proto__ = ctor.prototype
   var argsArr = [].slice.call(arguments, 1); // 取非第一位的参数
   var ctorReturnResult = ctor.apply(newObj, argsArr); // 运行;改变this指向;传参
   var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
   var isFunction = typeof ctorReturnResult === 'function';
   if (isObject || isFunction) {
       return ctorReturnResult;
   }
   return newObj;
}

总结

  • 可以检测引用类型
  • 但是因为基于原型,而原型可能被更改,所以这种方式不安全。

constructor

a.contructor 其实就是获取 a 的构造函数。 具体如何判断呢

''.constructor // ƒ String() { [native code] }
''.constructor === String // true

下面列举一些情况

''.constructor === String //true
(1).constructor === Number // true
(false).constructor === Number // false
(false).constructor === Boolean // true
Symbol('').constructor === Symbol // true
(10n).constructor === BigInt // true
new Date('2020').constructor === Date // true
(new Date('2020')).constructor === Date // true
(/s/g).constructor === RegExp // true
new Set().constructor === Set // true
new Map().constructor === Map // true

function fn(){}
fn.constructor === Function // true

由此可见,这种方法 判断也能应付大部分场景,但是 nullundefined 是·个特例

(null).constructor
// Uncaught TypeError: Cannot read properties of null (reading 'constructor')
(undefined).constructor
// Uncaught TypeError: Cannot read properties of undefined 

consturctor 可以改写的

str.__proto__.constructor = 'changed' // 'changed'
str.constructor === String // false

当某个对象需要定义一个创建自身的方法 create 时,重写 contructor 也是不错的方法。来自 MDN-Constructor⇲

function Parent() {};
function CreatedConstructor() {}

CreatedConstructor.prototype = Object.create(Parent.prototype);
// 修改 constructor
CreatedConstructor.prototype.constructor = CreatedConstructor; 

CreatedConstructor.prototype.create = function create() {
  return new this.constructor();
}

new CreatedConstructor().create().create();

大家如果记得原型继承,contructor 就需要去更改

总结

  • 能判断除 nullundefined 外的原始类型和引用类型
  • 因为 contructor 能被重写,所以也不安全

Object.prototype.toString

这个方法比较通用,基本能应付一切 对于基础类型

Object.prototype.toString.call('') // '[object String]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(NaN) // '[object Number]'
Object.prototype.toString.call(false) // '[object Boolean]'
Object.prototype.toString.call(10n) // '[object BigInt]'
Object.prototype.toString.call(Symbol('')) // '[object Symbol]'

引用类型

Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(/s/g) // '[object RegExp]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(function(){}) // '[object Function]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new Set()) // '[object Set]'

封装一个通用方法

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

总结

  • 通用方法,重写的概率较小。

最后

重写原型方法会造成原型污染,规避的方法也有很多

  • 拦截 属性重写的关键词 如 __ proto___, prototype
  • 利用Object.create(null) 生成对象。
  • Object.freeze()冻结对象使其不可扩展。

贴一其它库的类型判断实现 anime.js github.com/juliangarni…

const is = {
  arr: a => Array.isArray(a),
  obj: a => stringContains(Object.prototype.toString.call(a), 'Object'),
  pth: a => is.obj(a) && a.hasOwnProperty('totalLength'),
  svg: a => a instanceof SVGElement,
  inp: a => a instanceof HTMLInputElement,
  dom: a => a.nodeType || is.svg(a),
  str: a => typeof a === 'string',
  fnc: a => typeof a === 'function',
  und: a => typeof a === 'undefined',
  nil: a => is.und(a) || a === null,
  hex: a => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a),
  rgb: a => /^rgb/.test(a),
  hsl: a => /^hsl/.test(a),
  col: a => (is.hex(a) || is.rgb(a) || is.hsl(a)),
  key: a => !defaultInstanceSettings.hasOwnProperty(a) && !defaultTweenSettings.hasOwnProperty(a) && a !== 'targets' && a !== 'keyframes',
}

参考

感谢以下文章/视频作者,站在别人的肩膀上才能看的更远。