JS 中的数据类型
JS 的数据类型分为简单数据类型和复杂数据类型。其中,简单数据类型包括 string number null undefined boolean symbol,这类数据占用空间固定,为了提升访问速度,将其存在栈中,即按值访问;复杂数据类型包括object array date等等...,这类数据的占用空间不固定,放在栈中则会影响其访问速度,所以数据的值存在堆中,变量中储存的是一个指针,指向堆中的地址。
形形色色的类型检测
鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型。对于这个问题,JavaScript 也提供了多种方法,但遗憾的是,不同的方法得到的结果参差不齐。
下面给出四种方法以供参考
typeof 方法
typeof 是一个操作符,其后就是需要检测的操作数,它将操作数的类型以全小写字母的形式返回,包括number、boolean、symbol、string、object、undefined、function 。
看下面的调用示例:
typeof '' // string
typeof 1 // string
typeof false // boolean
typeof {} // object
typeof undefined // undefined
typeof null // object 检测不出是 null
typeof symbol('') //symbol
typeof function() {} // function
typeof [] // object 检测不出是数组
typeof 操作符存在的问题
- 除了
null以外的基本数据类型都能正确的检测出类型 Array,Date等对象返回object,不够准确null会返回object,但null的类型是Null
instanceof
instanceof 方法用于检测一个对象是否属于另一个对象的实例,简单来讲 A instanceof B ,如果 A 是 B 实例对象就返回 true,否则返回 false。
看下面的检测实例:
[] instanceof Array; // true
{} instanceof Object;// true
new Date() instanceof Date;// true
function Person(){};
newPerson() instanceof Person;
[] instanceof Object; // true
new Date() instanceof Object;// true
new Person instanceof Object;// true
从上面的代码中可以看出,instanceof 稚嫩那个用来检测两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
instanceof 的检测原理也很简单,就是进行原型检测,看下面演示代码:
function _instanceof(A,B) {
const a = A.__proto__;
return a = B.prototype;
}
这段代码不完全,实际会便利 A 原型链上的所有层级的 __proto__ 属性,只要有相等的,就会返回 true,否则返回 false。所以这种方法检测的类型不是很准确,当你的数据是继承方式获得话,它可能被检测为多种类型,使用时要注意。
constructor 方法
当一个函数被定义时,JS 引擎就会将自身的以用挂在函数的 constructor 属性上:
function a() {}
a.constructor // {constructor: function a() {}, ....}
利用这种机制,可以对数据类型做简单的检测,看下面的实例:
"".constructor = String;
Number(1).constructor = Number;
true.constructor = Boolean;
new Function().constructor = Function
从上面的代码可以看出,这种检测方式不可靠,因为 constructor 属性值时会被更改的,而这个更改是你感知不到的,如果你觉得这个描述不直观,上一段 ES5 时期的经典代码:
function A() {}
A.prototype.a = function () {}
function B() {
A.call(this)
}
B.prototype = A.prototype
上面代码中,B 实例化对象的 constructor 将指向 A,而不是 B,这就会导致类型检测错误,但是这个更改可能是你无意识的。所以这种方法在检测类型时不可取。
toString
toString() 方法是 Object 原型方法,它会检测数据的内部属性 [[class]],这个属性的值一般为 [Object Xxx],其中 Xxx 就指的数据类型。
看下面的示例:
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(newFunction()) ; // [object Function]
Object.prototype.toString.call(newDate()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(newRegExp()) ; // [object RegExp]
Object.prototype.toString.call(newError()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
这个方法能满足你大部分的类型检测需求,但是需要事先先保存好各个数据的 [[class]] 属性的值,方便做比较和检测。