相信很多人看过这个仓库.
Node.JS Interview
此文就是用来解答上述所问的第一篇类型判断.
为什么需要去考察 JS 的类型判断?
前置知识:
JS 数据类型分为两大类:
- 基本数据类型: Number,String,Boolean,Undefined,Null,Symbol
- 复杂数据类型: OBject
JS 是一门动态类型语音, 它不像 java 等存在类型约束, 所以写 JS 时候一不小心就会发生类型转换.
- console.log({}+[]) // 0
- console.log("aa" == true) // false
- const a = {
- i: 1,
- toString: function () {
- return a.i++;
- }
- }
- if (a == 1 && a == 2 && a == 3) {
- console.log('hello world!');
- }
正因为 JS 没有类型约束, 在开发大型项目时候不利于代码维护, 代码阅读型不强, 所以社区提出了一些解决方法如 flow 和 typescript.
JS 的类型转化分类
JS 的类型转化是一把双刃剑, 方便你书写代码同时又时刻可能导致 bug 的风险.
JS 类型转化分为两种:
隐式类型转换
强制类型转换
常见的隐式类型转换
我们平时经常涉及最多的隐式类型转换运算符大概就是 + 与 ==. + 既可以操作字符串也可以操作数字, == 运算符与 === 最大区别也是它会默认进行类型转换.
JS 既然允许类型转换自然有一套自己的法则: 其实隐式类型转换主要涉及三种转换.
通过 ToPrimitive 将值转换为原始值
ToPrimitive(input, PreferredType?)
- input 是要转换的值,
- PreferredType 是可选参数, 可以是 Number 或 String 类型. 它只是一个转换标志, 转化后的结果并不一定是这个参数所值的类型, 但是转换结果一定是一个原始值(或者报错).
如果 PreferredType 被标记为 Number, 则会进行下面的操作流程来转换输入的值.
1, 如果输入的值已经是一个原始值, 则直接返回它
2, 否则, 如果输入的值是一个对象, 则调用该对象的 valueOf()方法,
如果 valueOf()方法的返回值是一个原始值, 则返回这个原始值.
3, 否则, 调用这个对象的 toString()方法, 如果 toString()方法返回的是一个原始值, 则返回这个原始值.
4, 否则, 抛出 TypeError 异常.
2. 如果 PreferredType 被标记为 String, 则会进行下面的操作流程来转换输入的值.
1, 如果输入的值已经是一个原始值, 则直接返回它
2, 否则, 调用这个对象的 toString()方法, 如果 toString()方法返回的是一个原始值, 则返回这个原始值.
3, 否则, 如果输入的值是一个对象, 则调用该对象的 valueOf()方法,
如果 valueOf()方法的返回值是一个原始值, 则返回这个原始值.
4, 否则, 抛出 TypeError 异常.
3. 如果 PreferredType 参数不存在, 则按照下面规则
, 该对象为 Date 类型, 则 PreferredType 被设置为 String
, 否则, PreferredType 被设置为 Number
关于 valueOf 方法和 toString 方法解析规则:
先每个对象都有 toString()与 valueOf()方法.
先说下 valueOf()函数.
Number,Boolean,String 这三种构造函数生成的基础值的对象形式, 通过 valueOf 转换后会变成相应的原始值
Date 这种特殊的对象, 其原型 Date.prototype 上内置的 valueOf 函数将日期转换为日期的毫秒的形式的数值
除此之外返回的都为 this, 即对象本身
再来看看 toString 函数.
Number,Boolean,String,Array,Date,RegExp,Function 这几种构造函数生成的对象, 通过 toString 转换后会变成相应的字符串的形式, 因为这些构造函数上封装了自己的 toString 方法
除这些对象及其实例化对象之外, 其他对象返回的都是该对象的类型, 都是继承的 Object.prototype.toString 方法.
通过 ToNumber 将值转换为数字
- undefined => NaN
- null => +0
布尔值 => true 转换 1,false 转换为 + 0
数字 => 无需转换
字符串 => 有字符串解析为数字, 例如:'324'转换为 324,'qwer'转换为 NaN.(又有一套解析规则)
对象 => 先进行 ToPrimitive(obj, Number)转换得到原始值, 在进行 ToNumber 转换为数字
通过 ToString 将值转换为字符串
- undefined => 'undefined'
- null => 'null'
布尔值 => 转换为'true' 或'false'
数字 => 数字转换字符串, 比如: 1.765 转为'1.765'
字符串 => 无须转换
对象 => 先进行 ToPrimitive(obj, String)转换得到原始值, 在进行 ToString 转换为字符串
== 运算符隐式转换
比较运算 x==y, 其中 x 和 y 是值, 返回 true 或者 false. 这样的比较按如下方式进行:
1, 若 Type(x) 与 Type(y) 相同, 则
1* 若 Type(x) 为 Undefined, 返回 true.
2* 若 Type(x) 为 Null, 返回 true.
3* 若 Type(x) 为 Number, 则
(1), 若 x 为 NaN, 返回 false.
(2), 若 y 为 NaN, 返回 false.
(3), 若 x 与 y 为相等数值, 返回 true.
(4), 若 x 为 +0 且 y 为 −0, 返回 true.
(5), 若 x 为 −0 且 y 为 +0, 返回 true.
(6), 返回 false.
4* 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列 (长度相等且相同字符在相同位置) 时返回 true. 否则, 返回 false.
5* 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true. 否则, 返回 false.
6* 当 x 和 y 为引用同一对象时返回 true. 否则, 返回 false.
2, 若 x 为 null 且 y 为 undefined, 返回 true.
3, 若 x 为 undefined 且 y 为 null, 返回 true.
4, 若 Type(x) 为 Number 且 Type(y) 为 String, 返回比较 x == ToNumber(y) 的结果.
5, 若 Type(x) 为 String 且 Type(y) 为 Number, 返回比较 ToNumber(x) == y 的结果.
6, 若 Type(x) 为 Boolean, 返回比较 ToNumber(x) == y 的结果.
7, 若 Type(y) 为 Boolean, 返回比较 x == ToNumber(y) 的结果.
8, 若 Type(x) 为 String 或 Number, 且 Type(y) 为 Object, 返回比较 x == ToPrimitive(y) 的结果.
9, 若 Type(x) 为 Object 且 Type(y) 为 String 或 Number, 返回比较 ToPrimitive(x) == y 的结果.
10, 返回 false.
常见的强制类型转换
String()
处理非字符串到字符串的强制类型转换
Boolean()
可以被强制类型转换为 false 的值:
- undefined
- null
- false
- +0, -0, NaN
- ""
- Number()
将非数字值当作数字来使用
其中 true 转换为 1, false 转换为 0, undefined 转换为 NaN, null 转换为 0, 对字符串的处理遵循数字常量的相关规则, 处理失败时返回 NaN
那么有哪些方式进行类型判断呢?
typeof 操作符
typeof 是一个操作符, 其右侧跟一个一元表达式, 并返回这个表达式的数据类型. 返回的结果用该类型的字符串 (全小写字母) 形式表示, 包括以下 7 种: number,boolean,symbol,string,object,undefined,function 等.
- typeof ''; // string 有效
- typeof 1; // number 有效
- typeof Symbol(); // symbol 有效
- typeof true; //boolean 有效
- typeof undefined; //undefined 有效
- typeof null; //object 无效
- typeof [] ; //object 无效
- typeof new Function(); // function 有效
- typeof new Date(); //object 无效
- typeof new RegExp(); //object 无效
是的通过上述代码你会发下一些问题: 通过这个方法并不能达到我们想要的类型验证.
对于基本类型, 除 null 以外, 均可以返回正确的结果.
对于引用类型, 除 function 以外, 一律返回 object 类型.
对于 null , 返回 object 类型.
对于 function 返回 function 类型.
instanceof
instanceof 是用来判断 A 是否为 B 的实例, 表达式为: A instanceof B, 如果 A 是 B 的实例, 则返回 true, 否则返回 false. 在这里需要特别注意的是: instanceof 检测的是原型
- [] instanceof Array; // true
- {
- } instanceof Object;// true
- new Date() instanceof Date;// true
- function Person(){
- };
- new Person() instanceof Person;
- [] instanceof Object; // true
- new Date() instanceof Object;// true
- new Person instanceof Object;// true
虽然 instanceof 能够判断出 [ ] 是 Array 的实例, 但它认为 [ ] 也是 Object 的实例, 为什么呢?
我们简单分析一下 [ ],Array,Object 三者之间的关系:
从 instanceof 能够判断出 [ ].__proto__ 指向 Array.prototype, 而 Array.prototype.__proto__ 又指向了 Object.prototype, 最终 Object.prototype.__proto__ 指向了 null, 标志着原型链的结束. 因此,[],Array,Object 就在内部形成了一条原型链:
所以: instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型.
instanceof 还有一个问题: 它假定只有一个全局执行环境. 如果网页中包含多个 iframe, 那实际上就存在两个以上不同的全局执行环境, 从而存在两个以上不同版本的构造函数. 如果你从一个框架向另一个框架传入一个数组, 那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数.
- // ES6 的 Array.isArray 可以解决上述问题.
- constructor
基本认识: 当一个函数 F 被定义时, JS 引擎会为 F 添加 prototype 原型, 然后再在 prototype 上添加一个 constructor 属性, 并让其指向 F 的引用. 如下所示:
当执行 var f = new F() 时, F 被当成了构造函数, f 是 F 的实例对象, 此时 F 原型上的 constructor 传递到了 f 上, 因此 f.constructor == F.
可以看出, F 利用原型对象上的 constructor 引用了自身, 当 F 作为构造函数来创建对象时, 原型上的 constructor 就被遗传到了新创建的对象上.
同样, JavaScript 中的内置对象在内部构建时也是这样做的:
缺陷:
null 和 undefined 是无效的对象, 因此是不会有 constructor 存在的, 这两种类型的数据需要通过其他方式来判断.
函数的 constructor 是不稳定的, 这个主要体现在自定义对象上, 当开发者重写 prototype 后, 原有的 constructor 引用会丢失, constructor 会默认为 Object. 因此, 为了规范开发, 在重写对象原型时一般都需要重新给 constructor 赋值, 以保证对象实例的类型不被篡改.
toString(推荐使用)
toString() 是 Object 的原型方法, 调用该方法, 默认返回当前对象的 [[Class]] . 这是一个内部属性, 其格式为 [object Xxx] , 其中 Xxx 就是对象的类型.
对于 Object 对象, 直接调用 toString() 就能返回 [object Object] . 而对于其他对象, 则需要通过 call / apply 来调用才能返回正确的类型信息.
- 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(new Function()) ; // [object Function]
- Object.prototype.toString.call(new Date()) ; // [object Date]
- Object.prototype.toString.call([]) ; // [object Array]
- Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
- Object.prototype.toString.call(new Error()) ; // [object Error]
- Object.prototype.toString.call(document) ; // [object htmlDocument]
- Object.prototype.toString.call(Windows) ; //[object global] Windows 是全局对象 global 的引用
来源: https://juejin.im/entry/5bb77fda6fb9a05d3827f478