话不多说,直接正文
前言
JS中的8种数据类型及区别:
基本类型(值类型): 保存在栈内存中
- Number(数字)
- String(字符串)
- Boolean(布尔)
- null(空)
- undefined(未定义)
- Symbol(符号,ES6新增) 创建唯一值 独立内存空间(Symbol('1')===Symbol('1') //false)
- BigInt(要创建BigInt,只需要在数字末尾追加n即可;ES10新增)
引用类型(复杂数据类型): 保存在堆内存中
- Object(对象)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)。
Symbol类型:是为了解决属性名冲突的问题,需要定义私有属性的时候,用 Symbol 表示对象内部状态,可以很好地隔离用户数据和程序状态。有了它,我们就不再需要某些命名约定了。
BigInt类型:在JS中,所有的数字都以双精度64位浮点格式表示,这导致JS中的Number无法精确表示非常大的整数,它会将非常大的整数四舍五入,超出此范围的整数值都可能失去精度;BigInt用于当整数值大于Number数据类型支持的范围时,这种数据类型允许我们安全地对 大整数 执行算术操作。
为什么基本类型在栈内存 引用类型在堆内存?
1、基本数据类型值与值之间是独立存在,修改一个变量不会影响其他的变量。
对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间, 而变量保存的是对象的引用地址,如果两个变量保存的是同一个对象引用, 则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。
2、因为引用类型数据占用的空间比较大,如果放在栈中,会影响到调用栈的执行上下文的切换效率,也可能导致栈空间不足;而堆空间比较大,能存放很多大的数据。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
垃圾回收:栈内存变量基本上用完就回收了,而推内存中的变量因为存在很多不确定的引用,只有当所有调用的变量全部销毁之后才能回收。
检测 js数据类型有几种方法呢
1、typeof
返回数据类型的字符串表达 "xxx" typeof 1 //"number"
缺点:
1.不能区分null,返回"object"
2.不能细分对象(普通对象、数组对象、正则对象、日期对象)等,都返回"object"

typeof function(){} // 'function'
typeof NaN //'number'
那么问题来了,对象分为普通对象、数组对象、正则对象、日期对象等,所以 对象 数组 正则 返回object情有可原,为什么null会返回是object呢
原来啊,所有计算机底层是用二进制存储的,typeof也是用二进制判断的,因为null存的是000,而typeof把以3个0开头的都判定为对象,所以返回object
typeof原理:
不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。
- 000: 对象
- 010: 浮点数
- 100: 字符串
- 110: 布尔
- 1: 整数
2、instanceof
检测当前实例是否属于这个类
因为typeof不能将对象细分,都返回object,而instanceof刚好可以解决typeof的不足。

然后又试了下

啊 这,为什么这也是true呢,原因如下:
instanceof底层机制:只要当前类出现在实例的原型链上,结果都是true
我们来看一下arr:


我们发现Array和Object都出现在了arr的原型链上,所以都为true
优点:能够将对象细分,区分普通对象,数组,正则,日期等
缺点:
1.Number,Boolean,String等基本数据类型不能判断,输出false
2.由于我们可以修改原型的指向,所以检测出来的结果是不准确的
instanceof原理:
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
instanceof 的工作原理就是将s每一层的__proto__与 F.prototype 做比较找到相同返回 true,至最后一层仍没有找到返回 false
手写一个instanceof:
function instanceOf(left, right) { //left 表示左表达式,right 表示右表达式
let proto = left.__proto__ // 取 left 的隐式原型
let prototype = right.prototype // 取 right 的显示原型
while(proto) {
// 这里重点:当 prototype 严格等于 proto时,返回 true
if(proto === prototype) return true
proto = proto.__proto__
}
return false // Object.prototype.__proto__===null 表示原型链顶端
}
3、constructor

因为arr的constructor就是Array,所以只有第一行输出true

那么用constructor检测基本数据类型试一下:发现可行

缺点:由于我们可以修改constructor的指向,所以检测出来的结果是不准确的
例如:

4、Object.prototype.toString.call()
能精准判断数据类型。 返回模板:"[object xxx]"
Object.prototype.toString不是转换为字符串,是返回当前实例所属类的信息

Object.prototype.toString.call(function(){}) // '[object Function]'
检测原理
首先,这句话的意思是让我们用Object原型上的toString方法作用在传入的obj的上下文中(通过call将this指向obj),
那么我们知道数组本身也有toString()方法,那我们为什么非要用Object上的呢?
.toString()返回这个对象的字符串表现
比如:console.log([1,2].toString());//1,2 返回的是数组对象的字符串表现
Array、function等类型作为Object的实例,都各自重写了自己的toString方法;
我们要得到对象的具体类型,需要调用Object的原型的 `未被重写` 的toString()方法;
Object.prototype.toString([]) //'[object Object]'
为什么要加 .call()?
Object.prototype返回的是原型的字符串的表现
原型 Obj
实例 Object.prototype
如果不加call()的话返回的就是原型obj的字符串的表现,
加call()其实就是把call中的参数传入原型obj中,指向被call的对象;
所以我们在使用时要加call()
实现一个检测方法:
function typeOf(obj) {
let res = Object.prototype.toString.call(obj).split(' ')[1];
res = res.substring(0,res.length-1).toLowerCase();
return res;
}
console.log(typeOf([])) //array
console.log(typeOf({})) //object
console.log(typeOf(new Date)) //date
增强版:


当然判断数组还有:Array.isArray() 方法