殷实基础-JavaScript 数据类型
一、数据类型概念
值得注意的一点:null 从原始类型中被拆出来了,我在1月26 写认识TypeScript的时候看还没拆出来,*我也比较好奇他为什么会拆出来,这里,求指教
-
基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量;
-
引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念。
为什引用类型要存储在堆中?
因为一般引用数据类型占据的空间都很大,栈的空间比较小,引用数据类型在堆中都是以字符串的形式存储的,只有当调用的时候才会被解析成代码来执行,并开辟一个私有的栈空间,执行结束后销毁这个栈空间,然后就又变成了一堆字符串
二、数据类型的检测
- typeof
- instanceof
- Object.prototype.toString()
1. typeof
typeof 1; // "number"
typeof '1'; // "string"
typeof true; // "boolean"
typeof 1n; // "bigint"
typeof Symbol(); // "symbol"
typeof undefined; // "undefined"
typeof null; // "object"
typeof []; // "object"
typeof {}; // "object"
typeof function fn() {}; // "function"
这虽然 typeof null 会输出 object,虽然现在标准把 null 当成引用类型,但说回来这是 JS 存在的一个悠久 Bug,所以初学者还是当成基础数据类型记得好,并且 null 本身也不是对象。
总结:
typeof 可以判断 null 除外的所有基础数据类型,引用类型除了 function 其他都无法判断
2. instancof
通过 instanceof 我们能判断这个对象是否是之前那个构造函数的实例对象,这样就基本可以判断出这个新对象的数据类型。
instanceof 判断主要是通过原型链,判断当前实例对象的原型链是否有和构造函数相同的原型对象。
总结:
instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;
手写 instanceof
function Instanceof(left, right) {
// 判断 left 是否为基础数据类型
if (typeof left !== "object" || left === null) {
return false;
}
// 拿到实例对象的原型对象
let proto = left.__proto__;
// 循环向上查找
while (true) {
if (proto === null) { // 判断是否到达原型链的顶端
return false;
} else if (proto === right.prototype) { // 原型对象是否相同
return true;
}
// 指向上一个的原型对象
proto = proto.__proto__;
}
}
console.log(Instanceof(new Number(123), Number)); // true
console.log(Instanceof(123, Number)); // false
console.log(Instanceof(new String(123), Number)); // false
3. Object.prototype.toString
toString() 是 Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。Object 对象可以直接调用,其他对象可以通过 call 改变 this 指向来调用;
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(/123/); // "[object RegExp]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"
Object.prototype.toString.call(window); // "[object Window]"
Object.prototype.toString.call() 可以准确判断数据类型,不过要注意,它返回的描述类型的字符串是以大写字母开头
全局通用的数据类型判断方法
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
let str = Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1') // 注意正则中间有个空格
return str.toLowerCase();
}
三、数据类型的转换
关于数据类型的转换,我看过的几本书和专栏都有提到,也写的很细,但我还是记不住,所以这里只写一些常用的,和有点刁钻的。
凡是通过逻辑运算符 (&&、 ||、 !)、运算符 (+、-、*、/)、关系操作符 (>、 <、 <= 、>=)、相等运算符 (==) 或者 if/while 条件的操作,如果遇到两个数据类型不一样的情况,都会出现隐式类型转换。
1. '+' 的隐式类型转换规则
-
如果其中有一个是字符串,另外一个是 undefined、null 或布尔型,则调用 toString() 方法进行字符串拼接;如果是纯对象、数组、正则等,则默认调用对象的转换方法会存在优先级,然后再进行拼接。
-
如果其中有一个是数字,另外一个是 undefined、null、布尔型或数字,则会将其转换成数字进行加法运算,对象的的话调用 object 的 valueOf/toString 方法进行转换。
-
如果其中一个是字符串、一个是数字,则按照字符串规则进行拼接。
-
如果是 Infinity + Infinity,则结果是 Infinity
-
如果是 -Infinity + -Infinity,则结果是 -Infinity
-
如果是 Infinity + -Infinity,则结果是 NaN
其它运算符的规则与此相似,Number、parseInt,parseFloat, Boolean, String 等强制转化可以自己试试。
2. Object 的转换规则
对象转换的规则,会先调用内置的 [ToPrimitive] 函数,其规则逻辑如下:
-
如果部署了 Symbol.toPrimitive 方法,优先调用再返回;
-
调用 valueOf(),如果转换为基础类型,则返回;
-
调用 toString(),如果转换为基础类型,则返回;
var obj = {
value: 1,
valueOf() {
return 2;
},
toString() {
return '3'
},
[Symbol.toPrimitive]() {
return 4
}
}
console.log(obj + 1); // 输出5
// 因为有Symbol.toPrimitive,就优先执行这个;
// 如果Symbol.toPrimitive这段代码删掉,则执行valueOf打印结果为3;
// 如果valueOf也去掉,则调用toString返回 "31" (字符串拼接)
10 + {}
// "10[object Object]",注意:{}会默认调用valueOf是{},不是基础类型继续转换,调用toString,返回结果"[object Object]",于是和 10 进行'+'运算,按照字符串拼接规则来
[1,2,undefined,4,5] + 10
// "1,2,,4,510",注意[1,2,undefined,4,5]会默认先调用 valueOf 结果还是这个数组,不是基础数据类型继续转换,也还是调用toString,返回"1,2,,4,5",然后再和10进行运算,
var a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
// 注意这里a又可以等于1、2、3
console.log(a == 1 && a == 2 && a ==3); //true 规则 Object隐式转换
console.log([] == false); // true 这里数组回调用 toString,转换成字符串
if ([]) { // 而这里空数组会被当成 true,执行下面的代码, 又是为什么那?
console.log('1');
}
这里要记住所有对象强制转换成布尔值都为 true, 即使是 new Boolean(false),null 除外