深刻了解JS类型判断

关注『前端开发博客』公众号,回复 加群,加入高薪前端群

JS中判断数据类型的方式有不少javascript

  • typeof
  • Object.prototype.toString
  • instanceof
  • Array.isArray

1、回顾

JS数据类型分为基本类型和引用类型。
基本类型html

  • undefined
  • null
  • Number
  • String
  • Boolean
  • Symbol

引用类型前端

  • Object
  • Function

函数是一种特殊的对象,便可调用的对象。java

2、typeof

2.1 语法

typeof操做符能够区分基本类型,函数和对象。git

console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof 1) // number
console.log(typeof 1.2) // number
console.log(typeof "hello") // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // symbol
console.log(typeof (() => {})) // function
console.log(typeof {}) // object
console.log(typeof []) // object
console.log(typeof /abc/) // object
console.log(typeof new Date()) // object
  1. typeof有个明显的bug就是typeof nullobject;
  2. typeof没法区分各类内置的对象,如Array, Date等。

2.2 原理

JS是动态类型的变量,每一个变量在存储时除了存储变量值外,还须要存储变量的类型。JS里使用32位(bit)存储变量信息。低位的1~3个bit存储变量类型信息,叫作类型标签(type tag)github

.... XXXX X000 // object
.... XXXX XXX1 // int
.... XXXX X010 // double
.... XXXX X100 // string
.... XXXX X110 // boolean
  1. 只有int类型的type tag使用1个bit,而且取值为1,其余都是3个bit, 而且低位为0。这样能够经过type tag低位取值判断是否为int数据;
  2. 为了区分int,还剩下2个bit,至关于使用2个bit区分这四个类型:object, double, string, boolean
  3. 可是nullundefinedFunction并无分配type tag

如何识别Function

函数并无单独的type tag,由于函数也是对象。typeof内部判断若是一个对象实现了[[call]]内部方法则认为是函数。segmentfault

如何识别undefined

undefined变量存储的是个特殊值JSVAL_VOID(0-2^30)typeof内部判断若是一个变量存储的是这个特殊值,则认为是undefinedapi

#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))

如何识别null

null变量存储的也是个特殊值JSVAL_NULL,而且恰巧取值是空指针机器码(0),正好低位bit的值跟对象的type tag是同样的,这也致使著名的bug:数组

typeof null // object

很不幸,这个bug也不修复了,由于初版JS就存在这个bug了。祖传代码,不敢修改啊。浏览器

有不少方法能够判断一个变量是一个非null的对象,以前遇到一个比较经典的写法:

// 利用Object函数的装箱功能
function isObject(obj) {
    return Object(obj) === obj;
}

isObject({}) // true
isObject(null) // false

3、Object.prototype.toString

通常使用Object.prototype.toString区分各类内置对象。

3.2 语法

console.log(Object.prototype.toString.call(1)); // [object Number],隐式类型转换
console.log(Object.prototype.toString.call('')); // [object String],隐式类型转换
console.log(Object.prototype.toString.call(null)); // [object Null],特殊处理
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]
  1. 若是实参是个基本类型,会自动转成对应的引用类型;

Object.prototype.toString不能区分基本类型的,只是用于区分各类对象;

  1. nullundefined不存在对应的引用类型,内部特殊处理了;

3.3 原理

内部属性[[Class]]

每一个对象都有个内部属性[[Class]],内置对象的[[Class]]的值都是不一样的("Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"),而且目前[[Class]]属性值只能经过Object.prototype.toString访问。

Symbol.toStringTag属性

其实Object.prototype.toString内部先访问对象的Symbol.toStringTag属性值拼接返回值的。

var a = "hello"
console.log(Object.prototype.toString.call(a)); // "[object String]"

// 修改Symbol.toStringTag值
Object.defineProperty(String.prototype, Symbol.toStringTag, {
    get() {
        return 'MyString'
    }
})

console.log(Object.prototype.toString.call(a)); // "[object MyString]"

若是哪一个货偷偷修改了内置对象的Symbol.toStringTag属性值,那Object.prototype.toString也就不能正常工做了。

3.4 Object.prototype.toString内部逻辑

综上能够总结Object.prototype.toString的内部逻辑:

  1. 若是实参是undefined, 则返回"\[object Undefined\]";
  2. 若是实参是null, 则返回"\[object Null\]";
  3. 把实参转成对象
  4. 获取对象的Symbol.toStringTag属性值subType

    • 若是subType是个字符串,则返回[object subType]
    • 不然获取对象的[[Class]]属性值type,并返回[object type]

4、instanceof

4.1 语法

object instanceof constructorFunc

instanceof 操做符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。

Object.create({}) instanceof Object // true
Object.create(null) instanceof Object // false

Function instanceof Object // true
Function instanceof Function // true
Object instanceof Object // true
  1. 做为类型判断的一种方式,instanceof 操做符不会对变量object进行隐式类型转换
"" instanceof String; // false,基本类型不会转成对象
new String('') instanceof String; // true
  1. 对于没有原型的对象或则基本类型直接返回false
1 instanceof Object // false
Object.create(null) instanceof Object // false
  1. constructorFunc必须是个对象。而且大部分状况要求是个构造函数(即要具备prototype属性)
// TypeError: Right-hand side of 'instanceof' is not an object
1 instanceof 1

// TypeError: Right-hand side of 'instanceof' is not callable
1 instanceof ({})

// TypeError: Function has non-object prototype 'undefined' in instanceof check
({}) instanceof (() => {})

4.2 intanceof的缺陷

不一样的全局执行上下文的对象和函数都是不相等的,因此对于跨全局执行上下文intanceof就不能正常工做了。

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <iframe src=""></iframe>
        <script type="text/javascript">
            var iframe = window.frames[0];
            var iframeArr = new iframe.Array();

            console.log([] instanceof iframe.Array) // false
            console.log(iframeArr instanceof Array)  // false
            console.log(iframeArr instanceof iframe.Array)  // true       
        </script>
    </body>
</html>

4.3 原理

Symbol.hasInstance函数

instanceof操做符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。可是能够利用Symbol.hasInstance自定义instanceof操做逻辑。

var obj = {}

// 自定义Symbol.hasInstance方法
Object.defineProperty(obj, Symbol.hasInstance, {
    value: function() {
        return true;
    }
});

1 instanceof obj // true

固然了这个举例没有任何实际意义。只是说明下Symbol.hasInstance的功能。Symbol.hasInstance本意是自定义构造函数判断实例对象的方式,不要改变instanceof 的含义。

原型链

4.4 instanceof内部逻辑

综上能够梳理instanceof内部逻辑

object instanceof constructorFunc
  1. 若是constructorFunc不是个对象,或则是null,直接抛TypeError异常;
  2. 若是constructorFunc[Symbole.hasInstance]方法,则返回!!constructorFunc[Symbole.hasInstance](object )
  3. 若是constructorFunc不是函数,直接抛TypeError异常;
  4. 遍历object的原型链,逐个跟constructorFunc.prototype属性比较:

    • 若是object没有原型,则直接返回false;
    • 若是constructorFunc.prototype不是对象,则直接抛TypeError异常。

5、内置的类型判断方法

5.1 Array.isArray

ES5引入了方法Array.isArray专门用于数组类型判断。Object.prototype.toStringinstanceof都不够严格

var arr = []
Object.defineProperty(Array.prototype, Symbol.toStringTag, {
    get() {
        return 'myArray'
    }
})

console.log(Object.prototype.toString.call(arr)); // [object myArray]
console.log(Array.isArray(arr)); // true

console.log(Array.prototype instanceof Array); // false
console.log(Array.isArray(Array.prototype)); // true

不过现实状况下基本都是利用Object.prototype.toString做为Array.isArray的polyfill:

if (!Array.isArray) {
    Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
    };
}

6、内置对象的prototype属性类型判断

内置的对象Number, String, Boolean, Object, Function, Date, RegExp, Array都是各自类型对象的构造函数,而且他们的prototype属性都是各自实例对象的原型。可是这些内置对象的prototype属性又是什么类型呢?

6.1 Number.prototype

Number.prototype也是个数字,相似Number(0),可是Number.prototype并非Number的实例。

var prototype = Number.prototype

console.log(prototype == 0); // true

console.log(prototype instanceof Number); // false
console.log(Object.prototype.toString.call(protoype)); // [object Number]

6.2 String.prototype

String.prototype也是字符串,相似"",可是String.prototype并非String的实例。

var prototype = String.prototype

console.log(prototype == ''); // true
console.log(prototype instanceof String); // false
console.log(Object.prototype.toString.call(prototype)); // [object String]

6.3 Boolean.prototype

Boolean.prototype也是Boolean,相似false,可是Boolean.prototype并非Boolean的实例。

var prototype = Boolean.prototype

console.log(prototype == false); // true
console.log(prototype instanceof Boolean); // false
console.log(Object.prototype.toString.call(prototype)); // [object Boolean]

6.4 Object.prototype

Object.prototype也是Object,相似Object.create(null)的值(原型为null的空对象),可是Object.prototype并非Object的实例。

var prototype = Object.prototype

Object.getPrototypeOf(prototype); // null
console.log(prototype instanceof Object); // false
console.log(Object.prototype.toString.call(prototype)); // [object Object]

6.5 Function.prototype

Function.prototype也是Function,是个空函数,可是Function.prototype并非Function的实例。

var prototype = Function.prototype

console.log(prototype()) // undefined
console.log(prototype instanceof Function); // false
console.log(Object.prototype.toString.call(prototype)); // [object Function]

6.6 Array.prototype

Array.prototype也是Array,是个空数组,可是Array.prototype并非Array的实例。

var prototype = Array.prototype

console.log(prototype instanceof Array); // false
console.log(Array.isArray(prototype)) // true
console.log(Object.prototype.toString.call(prototype)); // [object Array]

6.6 RegExp.prototype

RegExp.prototype并非RegExp的实例。可是关于RegExp.prototypeRegExp仍是对象存在兼容性问题,有些浏览器下RegExp.prototype也是RegExp,而且是个总返回true的正则。

var prototype = RegExp.prototype
console.log(prototype.test('hello')) // true
console.log(prototype instanceof RegExp); // false
// Chrome v84返回"[object Object]", IE返回"[object RegExp]"
console.log(Object.prototype.toString.call(prototype)); //

整理自gitHub笔记:

  1. JS-类型&类型判断
  2. JS-操做符-instanceof
关注公众号「前端开发博客」,回复1024,领取前端资料包

原文:https://segmentfault.com/a/11...

相关文章
相关标签/搜索