快速导航
类型介绍
任何一种计算机编程语言,数据类型都是基础,在js中共有两大类类型:基本类型 和 引用类型(ps有的也叫对象类型);
基本类型共六种:undefined、null、Symbol、布尔值(Boolean)、字符串(String)、数值(Number);
备注:
-
string、boolean、number、null、undefined称为原始类型,表示js最基础最原始的规定类型。
-
symbol为ES6中新增的数据类型,表示独一无二的值。使用symbol类型不能使用new 进行初始化,直接Symbol()即可。
-
null和undefined 是js里的特殊值,null表示空。undefined表示未定义。
引用类型:一般来说除了基础类型的其他类型都属于引用类型,如Function(定义类)、Array、Date、Object等。
引用类型和基本类型的区别:
-
存储位置:基本类型的值直接存储在栈内存中,引用类型的真实存储在对内存的地址保存在栈内存中,其值保存在堆内存中。
-
克隆情况:基本类型直接通过赋值的方式给另一变量即可克隆一个变量,但是引用类型直接赋值克隆,只是将栈内存中的值引用地址赋值给另一变量,结果两个变量指向的是同一堆内存中的地址。所以操作其中一个变量,另一个变量也会被修改。(ps:将一个变量的赋值给另一变量,只会将该变量栈内存中的复制值保存给另一变量,不会操作堆内存)
类型判断
为什么要进行类型判断?
个人觉得因为js是弱类型语言,其声明的变量可以被替换成任何类型,并且不会进行类型校验,这虽然使得使用非常灵活,但是这会导致一些操作报错。 例如: 声明一个函数将字符串转为大写,但是调用该函数的时候传入的传入是数值.由此会导致程序报错退出运行。
let test= function(name){
name.toUpperCase()
}
test(11)
故此,我们要给他添加类型判断使得程序健壮性更好
let test= function(name){
if(typeof name ==='string'){
name.toUpperCase()
}
}
test(11)
一般来说,js判断变量类型的方法有:typeof、instanceof、constructor、Object.prototype.toString.call()
typeof
typeof 只能分辨一个变量是string、boolean、number、undefined、symbol;
console.log(typeof 'a'); //->string
console.log(typeof 1); //->number
console.log(typeof undefined); //->undefined
console.log(typeof Symbol()); //->symbol
console.log(typeof false); //->boolean
console.log(typeof function(){}); //->function
console.log(typeof null); //->object
console.log(typeof new function(){}); //->object
console.log(typeof {}); //->object
console.log(typeof []); //->object
由上可以看出typeof无法分辨出null及对象的准确类型。为了解决这一问题js提供了另一个检测类型的操作符 instanceof
instanceof
与typeof相对应,instanceof只能检测对象的类型;
console.log( 'a' instanceof String) //false
console.log( new String('a') instanceof String) //true
console.log( 1 instanceof Number) //false
console.log( function(){} instanceof Function) //true
console.log( new function(){} instanceof Function) //false
console.log( new function(){} instanceof Object) //true
console.log( [] instanceof Array) //true
console.log( {} instanceof Object) //true
console.log( {} instanceof Map) //false
console.log( new Map() instanceof Map) //true
console.log( new Map() instanceof Object) //true 注意这里为true的原因是所有对象都会继承全局Object对象
console.log( new Set() instanceof Set) //true
console.log( null instanceof null) //报错
console.log( undefined instanceof undefined) //报错
由上可以看出instanceof的使用方式为 变量 instanceof 类型。instanceof检测的是原型,内部机制是通过判断原型链中是否有类型的原型。也就是说只要B是A的子类,(B instanceof A )(A instanceof A) 都将返回true。这导致无准确的判断该变量是A的实例还是B的。同时我们看到 instanceof 的右侧必须是object,否则就报错,故其检测有限。
constructor
和instanceof相差不大。利用constructor进行类型判断只能判断变量类型是否为引用类型。该原理是利用当函数F被定义时,JS引擎会为prototype添加原型,然后在prototype上添加constructor属性,该属性会指向F,当用F创建对象的时候,prototype上的constrctor会复制到新对象上。即可用构造函数的类型确定当前变量的类型是否与他相同。
'a'.constructor== String; //true
[].constructor== Array ; //true
{}.constructor== Object; //true
funtion(){}.constructor== Function; //true
new Number(1).constructor==Number; //true
new Map().constructor==Map; //true
new Set().constructor==Set; //true
new Number(1).constructor==Number; //true
Symbol().constructor==Symbol; //true
1..constructor ==Number; //true(ps:这里调用多加一点的原因是只有一个点会被当成小数点而报错)
由上可知被检测的变量,必须具有constructor属性,当遇到没有该属性的变量值。如:undefined,null。会导致报错。除此之外当B通过原型继承A类时,new B().constructor ==B的值为false;new B().constructor ==A 的值为true。若想要检测正确,则需要手动修改constructor的指向。
Object.prototype.toString.call()
对比与typeof、instanceof、constructor等检测方式,Object.prototype.tostring.call()能够检测所有非自定义的数据类型。
其内部检测实现分为以下几步:
-
先判断this值,如果this值为undefined 则返回 "[object Undefined]",如果this值为null,则返回"[object Null]"
-
调用toObject(this)将this转化为一个对象O。注意:下面用于判断的属性或方法都是在这里,根据传入对象的类型生成的。
-
判断对象O是否是数组,若是则声明一个builtinTag并赋值"Array";
若不是则判断O是否有
[[ParameterMap]]属性,若有则声明一个builtinTag并赋值"Arguments";若没有则判断O是否有
[[Call]]方法,若有则声明一个builtinTag并赋值"Function";若没有则判断O是否有
[[ErrorData]]属性,若有则声明一个builtinTag并赋值"Error";若没有则判断O是否有
[[BooleanData]]属性,若有则声明一个builtinTag并赋值"Boolean";若没有则判断O是否有
[[NumberData]]属性,若有则声明一个builtinTag并赋值"Number";若没有则判断O是否有
[[StringData]]属性,若有则声明一个builtinTag并赋值"String";若没有则判断O是否有
[[DateValue]]属性,若有则声明一个builtinTag并赋值"Date";若没有则判断O是否有
[[RegExpMatcher]]属性,若有则声明一个builtinTag并赋值"RegExp";如果还是没有则给builtinTag赋值"Object"
-
调用Get(O,@@toStringTag)方法,并将结果赋值给变量tag
-
调用Type(tag),判断其返回结果是否为String,如果不是则将builtinTag赋值给tag
-
生成"[object"+ tag +"]"这个字符串并返回
实验验证如下:
Object.prototype.toString.call(1); //"[object Number]"
Object.prototype.toString.call('a'); //"[object String]"
Object.prototype.toString.call(function(){}); //"[object Function]"
Object.prototype.toString.call(new function(){}); //"[object Object]"
Object.prototype.toString.call({}); //"[object Object]"
Object.prototype.toString.call([]); //"[object Array]"
Object.prototype.toString.call(new Map()); //"[object Map]"
Object.prototype.toString.call(new WeakMap()); //"[object WeakMap]"
Object.prototype.toString.call(new Set()); //"[object Set]"
Object.prototype.toString.call( Symbol()); //"[object Symbol]"
由上可知Object.prototype.toString.call();方法并不能检测自定义对象的类型。不过对于原生的类型检测该方法的功能无疑是最好的。
总结
对于类型检测一般来说,原生类型使用Object.prototype.toString.call(),自定义使用instanceof或者constructor,不过constructor有点繁琐。因此建议自己选用时权衡一下难易以及是否埋坑。
最后内容难免出错,欢迎指正,交流。