typeof 和 instanceof
typeof :
typeof 运算符,用于检测数据类型。返回值为对应数据类型的字符串。
语法:typeof operand 或 typeof(operand)

typeof 123 // number
typeof true // boolean
typeof '123' // string
typeof undefined // undefined
typeof null // object
typeof [ ] // object
typeof new Date() // objec
typeof { } // object
typeof function() { } // function
typeof Array // function
typeof Symbol() // Symbol

为什么 typeof null 返回 object ?
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息。而 typeof 关键字检测数据类型的原理就是通过检测这1-3位机器码实现检测不同数据类型。 比如 object类型的数据,对应的机器码就是:000
000:对象 1:整数 010:浮点数 100:字符串 110:布尔值
undefined: -2^30 null:全零
null 类型的数据存储时,对应的机器码是一串的 000000......000000
所以typeof在检测 null 类型的数据,会返回 object。这个JavaScript这门语言的一个 特点。
typeof 检测引用类型数据结构,都返回应该object,但是为什么还会返回 function ?
因为:typeof 在检测出其为引用数据类型后,还会再检测这个引用数据类型是否有实现[[call]]方法。不是 则返回object ,是 则返回function。
极易混淆的一个点:

var str1 = 'myStr';
var str2 = new String( 'myStr' );
typeof str1; // string
typeof str2; // object
这里记住一点就行:当我们 new 一个构造函数时, 结果肯定是实例化了一个对象。所以其肯定是引用数据类型。接着我们只需要判断这个引用数据类型是否实现[[call]]方法,不是 则返回object ,是 则返回function。

instanceof:
语法: 实例对象 instanceof 类 属于返回 true ,不属于返回 false。
原理是:检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。返回值为布尔值。
利用这一点,instanceof 也可以用于检测数据类型。
缺点1:检测的实例必须是对象数据类型,基本数据类型(Number String Boolean null undefined Symbol)无法通过 instanceof 检测的。
缺点2:只要构造函数在这个被检测的实例的原型链上存在,就都返回 true。 所以即使是检测对象数据类型, instanceof 也是不准确的。

let reg = /^$/;
let str1 = new String(123);
let str2 = 'abc';
let num = 100;
console.log(reg instanceof RegExp); // true 字面量创建的正则也还是对象
console.log(str1 instanceof String); // true
console.log(str2 instanceof String); // false 字面量创建出的基本数据类型无法检测
console.log(num instanceof Number); // false
console.log([] instanceof Array); // true
console.log( [] instanceof Object); // true

检测引用类型更好的方法是使用: Object.prototype.toString() 我们先讨论 toString() 这个方法,对象的 toString() 方法,如果在自定义对象中未被覆盖,toString() 返回 "[object type]"。这里的 type 指 对应的类型。
但是 Boolean、String、Number、Function、Array、Date、都定义了各自的 toString() 方法。它们会覆盖Object原型上的 的原型对象上也 toString()。

console.log({}.toString()); // '[object Object]'
// 会被覆盖的原因在于:原型链查找的就近原则。
// 1. 'abc' 是个原始值,是基本数据类型。
// 2. 我们要对他使用方法,就会发生 包装类 。
// 3. 而包装类时,是通过 new String 这个构造函数创建实例的。
// 4. 当我们调用 toString() 这个方法时,这个新生成的实例上并没有这个方法,所以会去它的原型上寻找。
// 5. 而我们前面说过,String.prototype 是有自己预定义的 toSting() 方法的。
console.log(('abc').toString()); // 'abc'
console.log((100).toString()); // '100'
console.log(true.toString()); // 'true'
console.log([1,2,3].toString()); // '1,2,3'
console.log(function(){}.toString()); // 'function(){}'
console.log(new Date().toString()); // 当前时间
console.log(/1231/g.toString()); // '/1231/g'

所以我们可以通过直接使用 Object.prototype.toString() 来判断类型,这个方法执行时会把内部 this 的所属类信息输出。
Object.prototype.toString() 方法执行的时候,方法中的 this 是谁,就会检测谁的类型。所以需要使用 Function.prototype.call() 来改变Object.prototype.toString() 的 this,使之变为我们要检测的数据。

console.log(Object.prototype.toString.call(/123/)); // [object RegExp]
console.log(Object.prototype.toString.call(123)); // [object Number]
console.log(Object.prototype.toString.call('abc')); // [object String]
console.log(Object.prototype.toString.call([1,2,3])); // [object Array]
其实也可以像下面简写,反正只要指向的Object原型对象上的 toString() 就行。
console.log({}.toString.call( new Data() ) ); // [object Date]

扩展:Symbol.toStringTag
在调用 Object.prototype.toString()
方法时,会去读取这个标签并把它包含在自己的返回值里。
内置的 JavaScript 对象类型能识别并返回特定的类型标签,是因为引擎为它们设置好了对应的 Symbol.toStringTag
标签。
