JavaScript 数据类型分为 基础数据类型和引用数据类型
其中基础数据类型有:undefined、Null、Boolean、Number、 String、Symbol、BigInt
引用数据类型(Object)有:RegExp、Math、Date、Function、Array
JavaScript 的数据类型最后都会在初始化之后放在不同的内存中,因此基础数据类型和引用数据类型可以分为两类来进行存储:
- 基础数据类型存储在 **栈内存 ,**在被引用或者拷贝的事就,会创建一个完全相等的变量;
- 引用类型存储在**堆内存,**存储的是地址,多个应用指向同一个地址时会相互引用。
什么是栈内存?栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单的存储。包括 undefined、Null、Boolean、Number、 String、Symbol、BigInt。
什么是堆内存?堆内存存储的对象类型的数据,对于大小这方面,一般都是未知的。这也是为什么 null 作为 Object 类型的变量却存在栈内存中的原因。
如下图所示:
数据类型的检测
对于数据类型的检测一般有 typeof 、instanceof 等
1. typeOf 会返回一个字符串,表示未经计算的操作数类型。
typeof 1
typeof '1'
typeof null
typeof undefined
typeof truetypeof {}
typeof [];
typeof Symbol()
typeof function f() {}
输出结果如下图:
如上图所示:typeof 可以判断出 undefined、Boolean、Number、 String、Symbol 等数据类型,但是 null 的 typeof 是 Object ,这是 js 中 的一个 bug ,并不表示 null 就是引用类型,并且 null 本身也不是对象。因此 typeof 判断 null 返回的结果是有问题的,一定要注意,不能作为判断 null 的方法。
引用类型的数据,用 typeof 来判断的话,除了 function 是 OK 的,其他的都是 Object,是无法判断出来的。
2. instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例的原型链上。
当我们 new 一个对象的时候,那么这个新的对象就是在它原型链上面的对象了,通过 instanceof 我们能够去判断这个对象是否是之前那个构造函数生成的对象,这样基本上就可以判断出这个新对象的数据类型。
const p = new Person();
console.log(p instanceof Person); // true
const str = '1111';
console.log(str instanceof String); // false
const str1 = new String('456');
console.log(str1 instanceof String); // true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
上面就是 instanceof 方法去判断引用类型数据类型,下面我们自己实现一个 instanceof 。
function _instanceof(left, right) {
// 1. 首先判断 left 是不是基础数据类型,如果是基础数据类型,直接返回 false
if (typeof left !== 'object' || typeof left === null) {
return false;
}
// 2. 获取 left 的 proto
// const leftProto = left.__proto__;
const leftProto = Object.getPrototypeOf(left);
while (true) {
// 3. 如果 leftProto 为 null,说明 left 为 Object.prototype , leftProto 就找到了原型链的末端,还未找到,直接发返回false
if (leftProto === null) {
return false;
}
if (leftProto === right.prototype) {
return true;
}
// 4. 如果没有找到,继续往原型链上层搜寻
leftProto = Object.getPrototypeOf(leftProto);
}
}
插叙:
new 关键字会做出如下操作:
-
创建一个空对象 {};
-
链接该对象(设置该对象的 constructor)到另一个对象,其实也就是将新的空对象的 __proto__ 指向构造函数的 prototype;
-
将步骤 1 所创建的新的对象作为 this 的上下文,改变 this 的指向
-
如果该函数没有返回新的对象,则返回 this。
模拟 new 的实现,如下:
function _new(ctr, ...args) {
// ctr 校验
if (typeof ctr != 'function') {
throw 'ctr is must be a function';
}
// 1. 创建一个新的对象
const obj = {};
// 2. 链接该对象(设置该对象的 constructor)到另一个对象,其实也就是将新的空对象的 __proto__ 指向构造函数的 prototype;
obj.__proto__ = Object.create(ctr.prototype);
// 3. 改变 this 的指向
const res = ctr.apply(obj, [...args]);
// 如果该函数没有返回对象,则返回this
const isObject = typeof res === 'object' && typeof res != null; \
const isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
}
下面总结一下 typeof 和 instanceof 的区别
- instanceof 可以 准确的判断复杂引用数据类型,但是不能准确的判断基础数据类型
- typeof 虽然可以判断基础数据类型(null 除外),但是引用类型数据 中除了 function 外,其他的不能判断。
3. Object.prototype.toSring,该方法返回一个表示该对象的字符串。
每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每Object 对象继承。如果此方法在自定义对象中未被覆盖,toString()返回 "[object type]",其中 type 是对象的类型。
Object.prototype.toString({});
Object.prototype.toString.call({});
Object.prototype.toString.call(1);
Object.prototype.toString.call('1111');
Object.prototype.toString.call(null);
Object.prototype.toString.call(undefined);
Object.prototype.toString.call(true);
Object.prototype.toString.call(/258/g);
Object.prototype.toString.call(new Date());
Object.prototype.toString.call([]);
Object.prototype.toString.call(Symbol());
Object.prototype.toString.call(window);
Object.prototype.toString.call(document);
Object.prototype.toString.call(function () { });
从上面的代码可以看出 Object.prototype.toString.call() 可以很好地判断引用类型,甚至可以把 document 和 window 都区分开来,但是在写判断条件的时候一定要注意,使用这个方法最后返回统一字符串格式为 "[object Xxx]" ,而这里字符串里面的 "Xxx" ,第一个首字母要大写。
下面我们实现一个全局通用的可以判断数据类型的方法。
function getObjectType(obj) {
// 判断是否是基础类型
if (typeof obj !== 'object') {
return typeof obj;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}