动态类型
各类编程语言内置的数据结构都有所不同,而ECMAScript
的类型系统是松散的、动态或者是弱类型。在定义变量时,不用提前确定其具体类型,反而能在运行当中可以随意更改其数据类型:
let a = 123;
a = '123';
console.log(a); // a => '123'
在开发发过程当中,不可避免为了确定数据类型而头疼。
开发者需要一定的手段来随时确定数据具体类型。
typeOf
typeOf
在一般的情况下确实能满足开发者的要求,但是它却是经常返回一些技术上是正确,让人感到不解的答案。
比如null
,在ECMAScript
里面定义有一个自己的基础数据类型null
,包括其他的一些数组、正则、日期等,返回的都是原型链上最顶端的object
,从技术上说,没有错,但不是我们想要的结果。
数据类型 | 效果 |
---|---|
String | 有效 |
Number | 有效 |
Symbol | 有效 |
Boolean | 有效 |
Undefined | 有效 |
Null | 无效 |
Array | 无效 |
Function | 有效 |
Date | 无效 |
regExp | 无效 |
很明显,typeOf
只能检测部分数据类型,这对于开发者来说并不满意。
instanceof
根据 ECMAScrip
t 规范,这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例“。
注意这里的描述:instanceof
检测的是原型。
[] instanceof Array;// true
{} instanceof Object;// true
[] instanceof Object;// true
这里能出一点端倪,通过instanceof
判断出[]
不仅是Array
的实例,也是Object
的实例。
[]、Array、Object
显然是包含某种关系,[]
的原型链指向了Array.prototype
,而Array.prototype.__proto__
又指向了Object.prototype
,最终 Object.prototype.__proto
__ 指向了null
,标志着原型链的结束。因此,[]、Array、Object
就在内部形成了一条原型链:
从原型链可以看出,[]
的 __proto__
直接指向Array.prototype
,间接指向 Object.prototype
,所以按照 instanceof
的判断规则,[]
就是Object
的实例。
依次类推,类似的 new Date()
、new Person()
也会形成一条对应的原型链 。
因此,instanceof 只能用来判断两个对象是否属于实例关系**, 而不能判断一个对象实例具体属于哪种类型。**
instanceof
不能区别undefined
和null
,而且对于基本类型如果不是用new
声明的则也测试不出来,对于是使用new
声明的类型,它还可以检测出多层继承关系。
constructor
当一个函数被定义时,JavaScript
会为该函数添加 prototype
原型,并为其添加一个constructor
属性,并让其指向该函数 的引用。如下所示:
使用一个方法作为构造函数来创建对象时:
可以得知,函数在创建时,利用constructor
引用了自身,通过构造函数来创建对象时,原型上的constructor
就被遗传到了新对象上,这样做的话可让新对象具有可追溯的数据类型。
相同的,JavaScript
在构建内置对象时也是如此:
new Number(1).constructor === number; // true
new Date().constructor === Date; // true
// 其他的内置对象也是如此
需要注意到,
constructor
不能判断undefined
和null
。并且使用contructor的指向是可以改变的,所以尽量的避免使用。
当然,重写对象原型时可以重新给
constructor
赋值,以保证对象实例的类型不被更改。
toString
toString()
是 Object
的原型方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
console.log(Object.prototype.toString.call(bool));//[object Boolean]
console.log(Object.prototype.toString.call(num));//[object Number]
console.log(Object.prototype.toString.call(str));//[object String]
console.log(Object.prototype.toString.call(und));//[object Undefined]
console.log(Object.prototype.toString.call(nul));//[object Null]
console.log(Object.prototype.toString.call(arr));//[object Array]
console.log(Object.prototype.toString.call(obj));//[object Object]
console.log(Object.prototype.toString.call(fun));//[object Function]
function User(){}
function Begonia(){}
Begonia.prototype = new User()
let func1 = new Begonia()
console.log(Object.prototype.toString.call(func1));//[object Object]
当然,该方法也不是绝对的,这里大部分数据的类型都可以检测出来,但是使用非原生构造函数构建的对象,就无法检测出具体的类型。
需要注意到,对于函数而言,
toString
始终返回函数的代码。但是具体格式却是由各大浏览器厂商规定,所以必然包含某些差异,甚至包含注释。所以在重要功能上,尽量不要使用此方法。应只在调试中使用它。ES2019 以前,浏览器厂商可以自由决定 Function.prototype.toString()返回什么。ES2019 要求这个方法尽可能返回函数的源代码,否则返回{ [native code] }。 ------ 摘自《JavaScript高级程序设计(第4版)》
Q.E.D.