携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
javascript中的数据类型判断及原理探究
目录
- 为什么要做数据类型检测(安全防范机制) e.g.
- 四种方式检测数据类型
- 便捷api
- 从vue封装的函数,去窥见数据类型判断的封装
- 总结
安全防范机制
- 10 + val = ?
- val.map(v=>v) ?
- try catch, 默认值 ?.
typeof
定义
typeof操作符返回一个字符串,表示未经计算的操作数的类型(此处有彩蛋)。
长图来袭
typeof 原理探究
-
从上面可以总结出,对于基本数据类型,typeof可以完美判断,但是对于null取判断出了
object。对于复杂数据类型,几乎都是object, 但是function(){}, 和Symbol ,却判断出是function。完美来探究下typeof原理 -
js在底层存储变量时,会在变量的机器码的低位1-3位存储其类型信息
000:对象
010:浮点数
100:字符串
110:布尔
1:整数
null:所有机器码均为0
undefined:用 −2^30 整数来表示
`typeof通过判断前三位的值来判断变量的数据类型`
- 所以这就很好的解释了
typeof null为什么是object。因为null的所有机器码都是0 - 为什么
function(){}判断出来的是function。我们从ECMA262标准去寻找答案
规范里说,这个对象,如果实现了call方法,那么他就是function类型
我们来看看函数是否实现了call方法
确实是实现了call,所以typeof function(){} 是 function
- 如果我们输出一个未定义的变量,会发生什么?
如果我们使用typeof去判断这个未定义的变量,会发生什么?
规范里这样解释到
总结
typeof 是根据变量在机器码的低位1-3位存储其类型信息来做类型判断的,所以他是未经计算的操作数的类型判断!!!。除了null, typeof判断基本数据类型没什么问题,但是对于引用数据类型,就显得力不从心了,下面介绍一个几乎万能判断法Object.prototype.toString.call()
Object.prototype.toString.call()
定义
toString() 方法返回一个表示该对象的字符串()。
字符串形如=》[object, [type],其中[type]会返回es定义的对象类型,包含"Undefined","Null","Arguments", “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, 和 “String”
原理探究
Object.prototype.toString()没什么好说的,就是返回表示改对象的字符串,为什么要使用call呢?
由于Object.prototype.toString()本身允许被修改,像Array、Boolean、Number的toString就被重写过,所以需要调用Object.prototype.toString.call(arg)来判断arg的类型,call将arg的上下文指向Object,所以arg执行了Object的toString方法。
至于call,就是改变对象的this指向,当一个对象想调用另一个对象的方法,可以通过call或者apply改变其this指向,将其this指向拥有此方法的对象,就可以调用该方法了
代码实践
function type() {}
console.log(type.toString()); // function type() {}
由于Function重写了toString() ,本来使用toString会返回表示该对象类型,但是被重写后,返回了函数本身。
如果我们把Function重写的toString() 删掉,看会发生什么
function type() {}
delete Function.prototype.toString;
console.log(type.toString()); // [object Function]
删掉Function重写的toString(),会返回我们希望的结果。但是我们往往不会用delete去删除,所以就采用call方法将arg的上下文指向Object,所以arg执行了Object的toString方法。
function type() {}
console.log(Object.prototype.toString.call(type)); // [object Function]
对自定义类型的判断
- 假设我们声明了一个类,并且去做函数的构造调用
class A {}
let a = new A();
console.log(Object.prototype.toString.call(A)); // [object Function]
console.log(Object.prototype.toString.call(a)); // [object Object]
显然,对于对自定义类型的判断,Object.prototype.toString.call 出现了问题,下面会给出解决方案
总结
万精油Object.prototype.toString.call()的实现关键,其实抛开一切,就是一个关键的方法
toString()。他本身的功能就是返回对象的字符串,形如[object, [[class]]],也就是数据的类型。但是很多类型,都改写了toString方法,我们本来可以delete 改写的方法。但是更为明智的是使用Object去做类型判断
instanceof
定义
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
代码实践
class A {}
let B = new A();
console.log(B instanceof A); // true
根据定义可知,instanceof 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。看过这篇文章的都知道,new的实现,就有一个原型的关联这一段代码
instanceof原理实现
function _instanceof(explame, classFunction) {
let classFuncPrototype = classFunction.prototype;
proto = Object.getPrototypeOf(explame);
while (true) {
if (classFuncPrototype == proto) {
return true;
}
if (proto == null) {
return false;
}
proto = Object.getPrototypeOf(proto);
}
}
let arr = [];
console.log(_instanceof(arr, Array));
原型链接巩固
console.log([].__proto__ === Array.prototype); // true
console.log([].__proto__ === Object.prototype); // false
console.log([].__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
你我本无缘,全靠__proto__ 。
对象尽头,终为null。
instanceof的弊端
- 类型判断不准确
let arr = []
arr instanceof Object // true
因为instanceof玩的是原型链,所以数组类型 也是对象类型 2. 可以随意更改原型链,导致判断不准
function Fn() {
this.x = 100
}
Fn.prototype = Object.create(Array.prototype)
let f = new Fn()
console.log(f instanceof Array)
instanceof扩展
根据instanceof的定义,我们只能检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。这在h很大程度上限制了我们的判断。我们可以使用Symbol.hasInstance来扩展instanceof
Symbol.hasInstance
- Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
class A {
constructor() {}
}
let a = new A();
console.log(a instanceof A); // true
console.log(A[Symbol.hasInstance](a)); // true
以上两种写法是等价,如果我们要使用instanceof来做字符串的检测呢?
let c = '';
console.log(c instanceof String); // false
console.log(String[Symbol.hasInstance](c)); // false
下面让我们自定义 instanceof 操作符在某个类上的行为。
// 判断字符串
class isStr {
static [Symbol.hasInstance](x) {
return typeof x === 'string';
}
}
console.log('' instanceof isStr); // true
// 判断数组
class isArray {
static [Symbol.hasInstance](x) {
return Array.isArray(x);
}
}
console.log([] instanceof isArray); // true
constructor
定义
-
constructor属性返回Object的构造函数(用于创建实例对象)这个属性(constructor)指向该对象的基本对象构造函数类型。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。 -
简单来说:有了constructor属性,可以得知某个实例对象,到底是哪一个构造函数产生的。如果不能确定constructor属性是什么函数,有一个办法,通过
name属性,从实例得到构造函数的名称。
class B {
constructor() {}
}
let b = new B();
console.log('B', B); // [class B]
console.log('B', b.constructor); // [class B]
console.log('B', b.constructor === B); // true
console.log('B', b.constructor.name); // B
判断数据类型,长图来袭击
null 和 undefined
这俩兄弟 null 和 undefined 是光杆司令,没有相应的构造函数。
弊端
- 可以改
便捷api
isNaN
与 JavaScript 中其他的值不同,NaN不能通过相等操作符(== 和 ===)来判断 ,因为 NaN == NaN 和 NaN === NaN 都会返回 false。 因此,isNaN 就很有必要了。
当算术运算返回一个未定义的或无法表示的值时,NaN就产生了。但是,NaN并不一定用于表示某些值超出表示范围的情况。将某些不能强制转换为数值的非数值转换为数值的时候,也会得到NaN。
例如,0 除以 0 会返回NaN —— 但是其他数除以 0 则不会返回NaN。
tips
- isNaN函数会首先尝试将参数进行类型转换(所以用他只判断NaN即可,其他的不太保险)
- polyfill (利用
NaN自身永不相等于自身这一特征)
var isNaN = function(value) {
var n = Number(value);
return n !== n;
};
Object.is()
Object.is()方法判断两个值是否为同一个值。- demo
- 让null 和undefined 终于找回了自己
Array.isArray()
Array.isArray()用于确定传递的值是否是一个Array。
从vue封装的函数,去窥见数据类型的方式(内部查看)
实现一个万能数据类型检测的方法
let class2type = {};
let toString = class2type.toString;
[
'Boolean',
'Number',
'String',
'Function',
'Array',
'Date',
'RegExp',
'Object',
'Error',
'Symbol',
].forEach((val) => {
class2type[`object ${val}`] = val.toLowerCase();
});
console.log('class2type', class2type);
function toType(obj) {
if (obj == null) {
return obj + '';
}
return typeof obj === 'object' || typeof obj === 'function'
? class2type[toString.call(obj)]
: typeof obj;
}
window.toType = toType;
总结
typeof
- typeof 判断基本数据类型是完全可以的,但是对于null会返回
object。这是由于js在底层存储变量时,会在变量的机器码的低位1-3位存储其类型信息。恰巧,null的所有机器码都为0。 - typeof在判断复杂数据类型时,基本都返回了object,但是再判断
function(){}时,却返回了function.根据规范可知,由于Function 实现了call方法,所以返回function。
instanceof
- instanceof 用于判断对象是否是某个构造函数的实例,走的是
原型链。由于null 和 undefined是光杆司令,所以使用instanceof,会报错。他有个缺陷就是不能判断基本数据类型。但是我们可以使用Symbol.hasInstance自定义 instanceof 操作符在某个类上的行为,这样就变得灵活起来
toString()
- toString 与身俱来就是做类型检测的,但是无奈被大多数
类型改写了toString。所以我们把希望给予 Object上,使用Object.prototype.toString.call()去做类型校验。但是也有一个问题,就是对于自定义类型,无法做出正确判断。
是结束,也是开始
- 判断数据类型,大多是将上面的方式组合起来,那个方便用那个。学了数据类型判断,后续就要输出
类型转化的相关文章了