作者:前端小帅
掘金:https://juejin.cn/post/7092225590102589470
博客:https://ssscode.com/pages/b92fbe
JS类型
最新的 ECMAScript 标准定义了 8 种数据类型:
-
7种原始类型: Number
、String
、Boolean
、Null
、Undefined
、Symbol
、BigInt
-
Object引用类型: Array
、Object
、Date
、RegExp
、Error
、Map
、Set
、WeakMap
、WeakSet
,几乎所有通过new
创建的,即构造函数类型
-
Symbol类型: Symbol
-
ECMAScript 2015 新增 -
符号( Symbols
)类型是唯一且不可修改的原始值 -
可以用来作为对象的键(key) -
BigInt类型: BigInt
-
ECMAScript 2020 新增 -
BigInt
可以表示任意大的整数。 -
可以用在一个整数字面量后面加 n
的方式定义一个BigInt
,如:10n
,或者调用函数BigInt()
(但不包含new
运算符)并传递一个整数值或字符串值。
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true

堆和栈
为了更清晰的明白数据在内存中的存储结构,在介绍数据类型之前我们先了解下堆和栈的概念。
-
**栈(stack)**:是栈内存的简称,栈是自动分配相对固定大小的内存空间,并由系统自动释放,栈数据结构遵循 FILO
(first in last out)先进后出的原则 -
**堆(heap)**:是堆内存的简称,堆是动态分配内存,内存大小不固定,也不会自动释放,堆数据结构是一种无序的树状结构
在JS中的存在形式以及我们应该如何理解:
-
基本数据类型:都是直接按值存放在栈内存中,占用的内存空间的大小是确定的,并由系统自动分配和自动释放。 -
包括: Number
、String
、Boolean
、Null
、Undefined
、Symbol
、BigInt
-
这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间 -
引用数据类型:指那些可能由多个值构成的对象 -
如对象( Object
)、数组(Array
)、函数(Function
) ,它们是通过拷贝和new
出来的,这样的数据存储于堆中
传值和传址的区别:
-
基本类型:采用的是值传递。 -
基本类型是把数据的值存储于栈中 -
在赋值的时候,会把值复制一份到栈中,然后把栈顶的值赋给变量 -
引用类型:则是地址传递 -
引用类型的是把数据的地址指针存储于栈中 -
在赋值的时候是将存放在栈内存中的地址指针赋值给接收的变量
// 1. 基本类型
// b被赋值为a,传递的是值1
let a = 1;
let b = a;
b = 2
// a 1
// b 2
// 2. 引用类型
// obj2被赋值为obj1,传递的是地址
let obj1 = { name: 'zhangsan', age: 18 };
let obj2 = obj1;
obj2.age = 20;
// obj1、obj2都会变化,因为地址指向的是同一个
// obj1 {name: 'zhangsan', age: 20}
// obj2 {name: 'zhangsan', age: 20}
延伸:浅拷贝、深拷贝的作用
null和undefined区别
-
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined
和null
-
undefined
代表的含义是未定义,null
代表的含义是空对象。 -
一般变量声明了但还没有定义的时候会返回 undefined
,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。 -
undefined
在 JavaScript 中不是一个保留字 -
这意味着可以使用 undefined
来作为一个变量名,但是这样的做法是非常危险的,它会影响对undefined
值的判断。我们可以通过一些方法获得安全的undefined
值,比如说void 0
。 -
当对这两种类型使用 typeof
进行判断时,Null
类型会返回“object”
,这是一个历史遗留的问题(见下面类型判断部分)。 -
当使用双等号对两种类型的值进行比较时会返回 true
,使用三个等号时会返回false
。
拓展:
-
在实际开发过程中会使用 if(xx == null)
来判断变量xx
是否为undefined
和null
,这样可以更加简洁。(在目前最新的语法中??
就是只针对undefined
和null
做判断处理的,如let a = xx ?? 123
) -
使用引用类型时在数据不使用的时候赋值为 null
,这样可以避免内存泄漏。
0.1+0.2 ! == 0.3
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2
的时候,实际上是计算两个数的二进制的和,但是这两个数的二进制都是无限循环的数。
一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number
,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double
双精度浮点数。
在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从0舍1入的原则。
根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004
。
如何实现 0.1+0.2=0.3
:
对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2^-52
,在ES6中,提供了Number.EPSILON
属性,而它的值就是2^-52
,只要判断0.1+0.2-0.3
是否小于Number.EPSILON
,如果小于,就可以判断为 0.1+0.2 ===0.3
Number.EPSILON 属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值。
EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16,或者 2^-52。
function numberepsilon(arg1,arg2){
return Math.abs(arg1 - arg2) < Number.EPSILON;
}
console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
isNaN 和 Number.isNaN 函数的区别
-
函数 isNaN
接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回true
,因此非数字值传入也会返回true
,会影响NaN
的判断。 -
函数 Number.isNaN
会首先判断传入参数是否为数字,如果是数字再继续判断是否为NaN
,不会进行数据类型的转换,这种方法对于NaN
的判断更为准确。
JS类型判断
typeof
typeof
是由JS提供的,主要是检查数据类型,即这8种数据类型,不过对于引用类型返回值都是Object
(function除外),无法做到正确区分是哪种具体类型,比较合适的方式是使用 instanceof
关键字来判断引用类型。
返回值均为小写字符串:如 typeof undefined === "undefined"
、typeof {} === "object"
特别注意
-
typeof null === "object"
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 “object”。
-
typeof可以检测出函数类型
除 Function 外的所有构造函数的类型都是 ‘object’
两个特殊:
-
undefined
(JSVAL_VOID)是整数−2^30
(整数范围之外的数字) -
null
(JSVAL_NULL) 为机器码NULL
的空指针,或者说:为0
的object类型标签。
在判断数据类型时,是根据机器码低位标识来判断的,而null的机器码标识全为0,而对象的机器码低位标识为000。所以typeof null
的结果被误判为Object
。
function a() {}
console.log(typeof a); // function
var func = new Function();
typeof func; // 返回 'function'
instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
function Person() {}
var boy = new Person()
console.log(boy instanceof Person) // true,因为 Object.getPrototypeOf(o) === C.prototype
console.log(boy instanceof Object) // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
D.prototype = new Person() // 继承
var o3 = new D()
o3 instanceof Person // true 因为 Person.prototype 现在在 o3 的原型链上
Tips:有一个逐级向上查找的过程(原型的终点是null
)
o => Person.prototype => Object.prototype => null
实现原理:
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
// console.log(myInstanceof(boy, Person)) // true
// console.log(myInstanceof(boy, Object)) // true
Object.prototype.toString
toString()
方法返回一个表示该对象的字符串。
Object.prototype.toString.call(new Date) // [object Date]
同样是检测对象obj
调用toString
方法,obj.toString()
的结果和Object.prototype.toString.call(obj)
的结果不一样,这是为什么?
这是因为toString
是Object
的原型方法,而Array
、function
等类型作为Object
的实例,都重写了toString方法。
目前该种检测方法算是比较全面准确的,且使用较为广泛的一种。
isArray
Array.isArray([]) // true
类型检查通用方法
-
官方版本
function type(obj, fullClass) {
// get toPrototypeString() of obj (handles all types)
// Early JS environments return '[object Object]' for null, so it's best to directly check for it.
if (fullClass) {
return obj === null ? '[object Null]' : Object.prototype.toString.call(obj)
}
if (obj == null) {
return (obj + '').toLowerCase()
} // implicit toString() conversion
var deepType = Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
if (deepType === 'generatorfunction') {
return 'function'
}
// Prevent overspecificity (for example, [object HTMLDivElement], etc).
// Account for functionish Regexp (Android <=2.3), functionish <object> element (Chrome <=57, Firefox <=52), etc.
// String.prototype.match is universally supported.
return deepType.match(/^(array|bigint|date|error|function|generator|regexp|symbol)$/)
? deepType
: typeof obj === 'object' || typeof obj === 'function'
? 'object'
: typeof obj
}
-
Jquery版本
var class2type = {};
// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})
function type(obj) {
// 一箭双雕
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
类型转换、相等(==)判断
等于运算符(==
)检查其两个操作数是否相等,并返回Boolean
结果。与严格相等运算符(===
)不同,它会尝试强制类型转换并且比较不同类型的操作数。
相等运算符(==
和!=
)使用抽象相等比较算法比较两个操作数。可以大致概括如下:
-
如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回 true
。 -
如果一个操作数是null,另一个操作数是undefined,则返回 true
。(undefined == null
) -
如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型: -
当数字与字符串进行比较时,会尝试将字符串转换为数字值。 -
如果操作数之一是Boolean,则将布尔操作数转换为1或0。 -
如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的 valueOf()
和toString()
方法将对象转换为原始值。 -
如果操作数具有相同的类型,则对值进行比较
一些说明,针对对象转换的情况
目的就是为了转换为原始值
为了进行转换,JavaScript 尝试查找并调用三个对象方法:Symbol.toPrimitive
、toString
、valueOf
-
Symbol.toPrimitive
优先级最高,不过这只限于存在Symbol
类型时 -
对于字符串转换:优先级是 toString() > valueOf()
-
对于数学运算:优先级是 valueOf() > toString()
看个列子:
let user = {name: "John"};
user.toString() // [object Object]
user.valueOf() // user.valueOf() === user // true valueOf方法返回对象本身 {name: 'John'}
user == '[object Object]' // true
默认情况下,普通对象具有 toString
和 valueOf
方法:
-
toString
方法返回一个字符串"[object Object]"
-
valueOf
方法返回对象自身
历史原因:
由于历史原因,如果 toString
或 valueOf
返回一个对象,则不会出现 error,但是这种值会被忽略(就像这种方法根本不存在)。这是因为在 JavaScript 语言发展初期,没有很好的 “error” 的概念。
相反,Symbol.toPrimitive
必须返回一个原始值,否则就会出现 error。
验证数学运算:
从上面的规则我们知道数学运算转换规则的优先级是:valueOf() > toString()
let obj = {
// toString 在没有其他方法的情况下处理所有转换
toString() {
return "2";
}
};
console.log(obj * 2) // 4,对象被转换为原始值字符串 "2",之后它被乘法转换为数字 2
转换过程:
-
对象被转换为原始值(
valueOf
)obj.valueOf() // {toString: ƒ} 对象本身
-
如果生成的原始值的类型不正确,则继续进行转换(
toString
)obj.toString() // '2' 字符串
对象 — 原始值转换的阶段性总结
对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。
这里有三种类型(hint):
-
“string”(对于 alert 和其他需要字符串的操作) -
“number”(对于数学运算) -
“default”(少数运算符)
规范明确描述了哪个运算符使用哪个 hint。很少有运算符“不知道期望什么”并使用 “default” hint。通常对于内建对象,”default” hint 的处理方式与 “number” 相同,因此在实践中,最后两个 hint 常常合并在一起。
转换算法是:
-
调用 obj[Symbol.toPrimitive](hint)
如果这个方法存在 -
否则,如果 hint 是 “string” -
尝试 obj.toString()
和obj.valueOf()
,无论哪个存在。 -
否则,如果 hint 是 “number” 或者 “default” -
尝试 obj.valueOf()
和obj.toString()
,无论哪个存在。
一个简单测试
const string3 = new String("hello");
const string4 = new String("hello");
console.log(string3 == string4); // false
以上题目应该类比于下面的写法,即通过 new
构造的为对象类型,需要根据对象和对象的比较规则判断(因为是两个不同的引用地址,所以返回false
)
const object1 = {"key": "value"}
const object2 = {"key": "value"};
object1 == object2 // false
总结
数据类型做为一门语言的基础,是一个非常重要的概念。对于这方面的知识点一定要弄清楚,并深入理解,这样才能更好地应用在 JavaScript 中。
各种面试题的变种和骚操作,都是来自对语言的深入理解,对于一个 toString
的使用,就可以玩出很多花样来。
不过,由于JS的诞生背景比较特殊,我们现在再回头深入学习JS的时候,经常会发现一些有坑的地方,各种历史遗留和设计之类的问题等。对于这些“特性”我们也应该有所了解,做到知其然、知其所以然。但是,我们也不能以偏概全,不得不说JS确实是一门非常强大的语言(毕竟能用JS实现的最终都会用JS实现🤪)
新的ECMAScript规范和标准也在不断地完善JS语言,各种新特性,如ES6、ES7等等,以及TypeScript的出现,也使得JS这门语言更加丰富规范(扶我起来,我还能学🤓)
资料
-
JavaScript 数据类型和数据结构 -
JavaScript专题之类型判断 -
为什么JavaScript里面typeof(null)的值是”object”? -
【译】谈谈“typeof null为object”这一bug的由来 -
typeof -
Equality (==) -
对象 — 原始值转换
原文始发于微信公众号(前端小帅):彻底搞懂JS类型、类型判断、类型转换
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/114513.html