本文偏基础,主要讲讲 JS 的几种类型判断方式
总览
首先介绍一下 Javascript 的基础类型(7种)
Null, Undefined, Boolean, String, Number, Symbol(es6新增), BigInt(es10新增)
另外还有引用类型
Object、Array、Function、Date、RegExp、Set、Map、Error、Math.
上面基本涵盖了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
说到引用类型,这里有四个规则 引用类型的四个规则:
- 引用类型,都具有对象特性,即可自由扩展属性。
- 引用类型,都有一个隐式原型 proto 属性,属性值是一个普通的对象。
- 引用类型,隐式原型 proto 的属性值指向它的构造函数的显式原型
prototype属性值。 - 当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型
prototype)中寻找。
上面内容来自 面不面试的,你都得懂原型和原型链⇲, 当然这需要理解JS的原型链,推荐 重学JS---原型/原型链/继承⇲
还要注意的是 new 和 Object.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
由此可见,这种方法 判断也能应付大部分场景,但是 null 和 undefined 是·个特例
(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 就需要去更改
总结
- 能判断除
null和undefined外的原始类型和引用类型 - 因为
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',
}
参考
感谢以下文章/视频作者,站在别人的肩膀上才能看的更远。
- 前端食堂 - blibli | 【前端面试题满分答案】教女朋友学前端之JS数据类型检测
- 尼可陈 - 掘金 | 面不面试的,你都得懂原型和原型链
- 不写bug的米公子 - 掘金 | 面试官:说说原型链和继承吧