站在各路大神与文档的肩膀上总结出的一篇文章,描述了 JavaScript 数据类型与变量的相关内容。
数据类型
ECMAScript 中有6中简单数据类型(原始类型):Undefined、Null、Boolean、Number、String、Symbol(ES6),一种复杂数据类型(引用类型):Object,ES10 新增一种特殊的原始类型:BigInt。
简单数据类型
Undefined 类型
Undefined 类型有且仅有一个值undefined,表示未定义。返回undefined值的场景如下:
- 使用
var或let声明变量但未初始化; - 访问一个对象不存在的属性
- 没有返回值的函数的返回值
- 调用函数时被省略的参数
undefined或者null的链式调用 ?.void <expression>计算表达式的返回结果
Null 类型
Null 类型有且仅有一个值null,表示一个空对象指针。null 是 JavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null。
Undefined 和 Null 的区别是什么?
typeof undefined=> 'undefined',typeof null=> 'object'undefined转换为数字是NaN,Null转换为数字是0undefined是全局对象的一个属性(全局变量),表示原始值undefined,如需显示赋值使用void 0undefined == null=> true,undefined === null=> false
Boolean 类型
Boolean(布尔值)类型有两个字面值:true 和 false。
Number 类型
ECMAScript 中 Number 类型使用 IEEE 754 格式表示整数和浮点数。不同数值类型的数字字面量表示如下:
// 十进制
let intNum = 56
// 八进制
let octalNum1 = 0o70 // 56
// 十六进制
let hexNum1 = 0xA // 10
ECMAScript 能够存储 2-1074(Number.MIN_VALUE)和 21024(Number.MAX_VALUE)之间的正浮点数,以及 -2-1074 和 -21024 之间的负浮点数,但是它仅能安全地存储在 -253 + 1(Number.MIN_SAFE_INTEGER)到 253 − 1(Number.MAX_SAFE_INTEGER)范围内的整数。数值超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity。
let min = Number.MIN_VALUE // 5e-324
let max = Number.MAX_VALUE // 1.797 693 134 862 315 7e+308
let negativeInfinity = Number.NEGATIVE_INFINITY // -Infinity
let positiveInfinity = Number.POSITIVE_INFINITY // Infinity
/* Number.isFinite() 要确定一个值是不是有限大 */
Number.isFinite(min) // true
Number.isFinite(positiveInfinity) // true
NUmber 类型有一个特殊值:NaN(not a number),其特性如下:
- 0、+0、-0 相除会返回 NaN
- 任何涉及 NaN 的操作始终返回 NaN
NaN == NaN=> false
String 类型
String(字符串)类型表示零或多个 16 位 Unicode 字符序列。String 有最大长度是 253 - 1。ECMAScript 中的字符串是不可变的。
Symbol 类型
Symbol 是 ES6 新增的数据类型,Symbol 是原始值,且唯一、不可变。其声明方式如下:
let sym = Symbol()
BigInt 类型
BigInt 可以表示任意大的整数。其声明方式如下:
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
在某些方面类似于Number,但是也有几个关键的不同点:
- 不能用于
Math对象中的方法; - 不能和任何
Number实例混合运算
复杂数据类型
Object
ECMAScript 中的对象是一组数据和功能的集合。在 ECMAScript 中, Object 是派生其他对象的基类。创建对象实例如下:
let obj = new Object()
每个 Object 实例存在的属性和方法:
- constructor 用于创建当前对象的函数
- hasOwnProperty(propertyName) 判断当前对象是否存在给定的属性
- isPrototypeof(object) 判断当前对象是否存在于另一个对象的原型链上
- propertyIsEnumerable(propertyName) 判断指定的属性是否可枚举
- toLocalString() 返回对象的字符串表示
- toString() 返回对象的字符串表示
- valueOf() 返回对象对应的字符串、数值或布尔值表示
类型转换
因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算(如加减乘除大于小于)都会先进行类型转换。常见的转换规则如下:
| 数据类型(from\to) | Boolean | Number | String | Object |
|---|---|---|---|---|
| Null | false | 0 | 'null' | TypeError |
| Undefined | false | NaN | 'undefined' | TypeError |
| Boolean | - | true -> 1 false -> 0 | true -> 'true' false -> 'false' | #装箱转换 |
| Number | 0/NaN -> false | - | #NumberToString | #装箱转换 |
| String | '' -> false | #StringToNumber | - | #装箱转换 |
| Symbol | true | TypeError | TypeError | #装箱转换 |
| Object | true | #拆箱转换 | #拆箱转换 | - |
所谓的隐式转换,就是偷偷调用 Number、String、Boolean 进行一个类型转换。
StringToNumber
-
Number()数据类型 转化为数值 NaN Boolean true -> 1 false -> 0 N/A Null null -> 0 N/A Undefined N/A undefined Number 有效的数值字面量格式 -> 对应数值
'' -> 0其他字符串 -
Number.parseFloat()从第一个非空格字符开始转换,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。
-
Number.parseInt()- 第二个参数指定进制数。
- 从第一个非空格字符开始转换,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。
- 如果第一个字符不是数字字符、加号或减号,
parseInt()立即返回 NaN。
多数情况下,Number() 是比 parseInt() 和 parseFloat() 更好的选择。
NumberToString
在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当 Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。
装箱转换
每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象。
var symObj = Object(Symbol("a"));
console.log(typeof symObj); // object
console.log(Object.prototype.toString.call(symObj)); // [object Symbol]
拆箱转换
拆箱转换是对象类型到基本类型的转换。拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。到 String 的拆箱转换会优先调用 toString。
var o = {
valueOf: () => {
console.log('valueOf')
return {}
},
toString: () => {
console.log('toString')
return {}
},
}
o * 2 // valueOf toString TypeError
String(o) // toString valueOf TypeError
变量
ECMAScript 变量是松散类型的,即变量可以用于保存任何类型的数据。ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。
声明变量
有 3 个关键字可以声明变量:var、const 和 let。
var、const和let有何异同?
- 变量提升:
var声明变量会自动提升到作用域顶部,即在函数作用域中可以先使用再声明变量;let和const声明的范围是块级作用域,而var声明的范围是函数作用域;let和const声明的变量不会在作用域中被提升,在其声明之前的执行瞬间被称为暂时性死区;var在全局作用域中生命的变量会成为全局对象的属性,而let和const声明的变量不会;- 在同一个作用域中,
var可以重复声明同一个变量。由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。let和const则会报错:SyntaxError。const声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
动态属性
对于引用值而言,可以随时添加、修改和删除其属性和方法。
let student = new Object()
student.age = 16
student.name = '张三'
存储方式
原始值是保存在栈内存中的简单数据段,值有固定的大小,保存原始值的变量是按值(by value)访问的。引用值是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,保存引用值的变量是按引用(by reference)访问的。
const a = 1
const arr = { name: '张三' }
graph LR
arr:访问地址 --> arrV
subgraph 堆内存
arrV["[0,1,2]"]
end
subgraph 栈内存
a:1
arr:访问地址
end
复制值
变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针。
// 原始值复制
const a = 1
const b = a
// 引用值复制
const obj = { name: '张三'}
const objC = obj
graph LR
obj:访问地址1 --> V
objC:访问地址1 --> V
subgraph 堆内存
V["{ name: '张三' }"]
end
subgraph 栈内存
a:1
b:1
obj:访问地址1
objC:访问地址1
end