基本数据类型
- 值存储于栈空间(
string
不是,存储于字符串常量区,JS 屏蔽了这些细节,使其表现同其他基本数据类型) - 无需 GC,函数调用栈弹出时销毁(不是销毁,之后会被覆盖而已)
number
JavaScript 使用 IEEE 754 标准定义的 64 位浮点格式表示数值(绝大多数现代编程语言都使用该标准,如 C、Java)
这意味着:
- 能表示的数有范围,超出则变成
Infinity
或-Infinity
Number.MAX_VALUE
:1.7976931348623157e+308
Number.MIN_VALUE
:5e-324
- 能准确表示一个范围内的整数,超出则可能会在末尾的数字上损失一些精度
Number.MAX_SAFE_INTEGER
:2^53 - 1
Number.MIN_SAFE_INTEGER
:-(2^53 - 1)
- 二进制浮点数与舍入错误:实数有无限多个,但 IEEE 754 浮点格式只能表示其中有限个
- 意味着操作实数(浮点数)时,数值表示的经常是实际数值的近似值
- 如:
0.1+0.2 === 0.3 // false
- 可以这样:
Math.abs((0.1+0.2) - 0.3) < Number.EPSILON // true
- 或者根据业务自己指定误差范围:
Math.abs((0.1+0.2) - 0.3) < 0.0001 // true
进制
- 二进制:
0b1101
- 八进制:
0o74
- 还有一种写法是
074
,但基本禁止使用(如严格模式、ESLint),因为:074
是八进制084
是十进制(超出 0~7)
- 由于历史原因,保留该写法,但强烈建议使用
0o74
,因为0o84
会报语法错误,而不是莫名其妙以十进制解析
- 还有一种写法是
- 十进制:
123
- 十六进制:
0x5F
(字母大小写都可以)
其他写法
- 科学记数法:
1.6e9 // 1.6 * 10^9
(e
大小写都可以)1.6e+9 // 1.6 * 10^9
1.6e-9 // 1.6 * 10^-9
- 分割符:
10_000 // 10000
- 省略小数点之前的 0:
.16 === 0.16 // true
- 省略小数点之后的小数:
16. === 16 // true
16.toString() // SyntaxError: Invalid or unexpected token
- 其中
16.
被认为是一个数字,数字toString()
语法错误 - 正确写法:
16..toString()
、(16).toString()
数字中的特殊值
NaN
:Not a Number,非法数字- 如:
0 / 0
、undefined - 10
- 任何涉及
NaN
的操作都会返回NaN
NaN
与任何值都不相等,包括它自身(即:NaN !== NaN
)
- 如:
Infinity
:无穷大- 如:
5 / 0
、Infinity - 10000
Infinity
无论如何运算都将是Infinity
- 除了
Infinity - Infinity // NaN
- 如:
-Infinity
:无穷小- 如:
-5 / 0
、5 / -0
-Infinity
无论如何运算都将是-Infinity
- 除了
Infinity - Infinity // NaN
- 如:
补充:
0 / 5 // 0
- 除法:被除数和除数同号,结果为正;否则为负
- 取模:结果正负同被除数(除号前面的数)
+0
和-0
(+0
即0
)+0 === -0 // true
Object.is(0, -0) // false
类型转换
Number(xxx)
、+xxx
boolean
:true
→1
;false
→0
null
→0
undefined
→NaN
+undefined // NaN
Number(undefined) // undefined
- 注意:
Number() // 0
- 字符串 → 数字
- 空白字符串 →
0
+'' // 0
+' ' // 0
+'\t' // 0
- 字符串符合数字格式
- 二进制:
+"0b110" // 6
- 八进制:
+"0o46" // 38
- 十进制:
+"46" // 46
- 开头的
0
被省略,而不是认为八进制+"046" // 46
- 直接传入数字则不同
Number(046) // 38
- 开头的
- 浮点数,开头的 0 可以省略,开头多余的 0 被省略
Number('0.16') // 0.16
Number('.16') // 0.16
Number('000.16') // 0.16
Number('010.16') // 10.16
- 二进制:
- 字符串不符合数字格式 →
NaN
Number('0o468')
Number('66NB')
Number('NB666')
- 空白字符串 →
parseInt
- 参数:
parseInt(字符串, 第一个参数的进制)
,第二个参数默认是 10,介于 2~36(36:0~9 + A~Z)- 如果超出这个范围,将返回
NaN
- 指定
0
或未指定,基数将会根据字符串的值进行推算
- 如果超出这个范围,将返回
- 前置匹配(从左开始匹配,直到不是数字)
- 传入的参数不是字符串,会先自动转换成字符串,再匹配
parseInt
:向零取整;Math.floor
:向下取整
// 二进制
parseInt("0b110") // 16
// 八进制
parseInt('0o46') // 38
// 十进制
parseInt('46') // 46
// ECMAScript 5 移除了八进制解析
parseInt('046') // 46,ECMAScript 5 之前会是 38
parseInt('049') // 49
// 十六进制
parseInt('0x6a') // 106
// 前面可以有空白
parseInt(' 46') // 46
parseInt('\t46') // 46
// 空白串是 NaN
parseInt('') // NaN
parseInt(' ') // NaN
// --------------------------------
parseInt("0b113") // NaN
parseInt('46真不错') // 46
parseInt('46.6') // 46
parseInt('46+5') // 46
parseInt('a46') // NaN
// 传入的参数不是字符串,会自动转换成字符串
parseInt(true) // NaN
parseInt({}) // NaN
parseInt(4*5) // 20
parseInt(014) // 12,这里是先运算,再自动转换成字符串
const arr = ['1', '2', '3']
const result = arr.map(parseInt)
console.log(result) // [1, NaN, NaN]
// 为什么?
// 因为
arr.map(parseInt)
// 相当于
arr.map((item, index) => parseInt(item, index))
// 第二个参数为 0,基数将会根据字符串的值进行推算
parseFloat
同 parseInt
,但没有进制概念
parseFloat('3.14159265ufo') // 3.14159265
parseFloat('12.3.4') // 12.3
其他
Number
Number.POSITIVE_INFINITY // 同 Infinity
Number.NEGATIVE_INFINITY // 同 -Infinity
Number.NaN // 同 NaN
Number.EPSILON // 2 ** -52,数值与数值之间最小的差
Number.parseInt() // 同全局 parseInt() 函数
Number.parseFloat() // 同全局 parseFloat() 函数
Number.isNaN(x) // 判断 x 是不是 NaN
Number.isFinite(x) // 判断 x 是不是有限
Number.isInteger(x) // 判断 x 是不是整数
Number.isSafeInteger(x) // 判断 x 是不是安全范围内的整数
isNaN()
VS. Number.isNaN()
isNaN()
:判断传入的参数是否可以转换为 NaN
isNaN(1) // false
isNaN('1') // false
isNaN(true) // false,布尔 true 可以转化为 1
isNaN('abc') // true
isNaN(NaN) // true
isNaN(undefined) // true
Number.isNaN()
:ES6 新增,只有传入 NaN
才返回 true
,传入其他值都是 false
boolean
只有两个值:true
、false
布尔判定
- truly 变量:
!!a === true
的变量 - falsely 变量:
!!a === false
的变量
以下是 falsely 变量,除此之外都是 truly 变量:
number
:0
、NaN
string
:''
- 注意:是空串,而不是空白串,
!!' ' // true
- 注意:是空串,而不是空白串,
null
undefined
特别强调:引用类型转换为 boolean
都是 true
,即使是空对象或空函数
string
创建方式
- 字面量:双引号
"
和单引号'
完全等价、ES6 新增 模板字符串 反引号 String()
:将传入的参数转换为字符串(调用其toString
方法)new String()
:返回的是一个字符串对象(前两种方法得到的都是基本数据类型)
模板字符串
- 带嵌入表达式的字符串插值
const age = 18
// 不使用模板字符串
const s1 = '刚满 ' + age + '岁'
// 使用模板字符串
const s2 = `刚满 ${age}岁`
// 转义
`\`` === "`"; // true
`\${1}` === "${1}"; // true
- 允许多行字符串
// 不使用模板字符串
const s1 = '<div>\n' +
' Hello World!\n' +
'<div>'
// 使用模板字符串
const s2 = `<div>
Hello World!
<div>`
- 带标签的模板与自定义标签函数
function myTag(strings, ...exps) {
const arr = []
const len = exps.length
for (let i = 0; i < len; i++) {
arr.push(strings[i], exps[i])
}
arr.push(strings[len])
return arr.join('')
}
const res = myTag`刚满 ${18} 岁`
console.log(res) // 刚满 18 岁
字符、码点和字符串
JavaScript 使用 Unicode 字符集的 UTF-16 编码,因此 JavaScript 字符串是无符号 16 位值的序列
- 常用的 Unicode 字符的码点(code point)是 16 位的,可以用字符串中的一个元素来表示
- 码点超出 16 位的 Unicode 字符使用 UTF-16 规则编码为两个 16 位值的序列,称为代理对(surrogate pair)
- 这意味着一个长度为 2(两个 16 位值)的字符串可能表示的只是一个 Unicode 字符
null
和 undefined
JS 中变量的“无”
- 未声明而直接访问,报错
ReferenceError: xxx is not defined
undefined
:表示“未定义”- 一般不要显式赋值为
undefined
- 什么时候出现
undefined
?- 使用声明但未赋值(或显式赋值为
undefined
)的变量 - 获取对象中未定义的属性
- 函数没有返回值
- 函数形参未传入实参
- 使用声明但未赋值(或显式赋值为
- 一般不要显式赋值为
null
:表示空对象、空指针,一般指引用数据类型指向为“空”,表示“引用空”- 什么时候出现
null
?- 显式赋值为
null
- 无法获取指定 DOM 元素,返回
null
- 通过正则表达式匹配,没有匹配成功,返回
null
- …
- 显式赋值为
- 什么时候出现
异同点
- 相同点
- 都只有一个字面量(其本身)
- 转换为
boolean
,是false
- 转换为
string
,是其本身null + '' // "null"
undefined + '' // "undefined"
- 转换为对象,抛
TypeError
null.toString() // TypeError: Cannot read properties of null (reading 'toString')
undefined.toString() // TypeError: Cannot read properties of undefined (reading 'toString')
==
但不===
null == undefined // true
- 一般使用
xxx == null
代替xxx === null || xxx === undefined
- 不同点
null
是关键字;undefined
是全局变量typeof
typeof null // "object"
typeof undefined // "undefined"
- 转换为
number
:null
是0
;undefined
是NaN
null + 0 // 0
undefined + 0 // NaN
symbol
代表创建后独一无二且不可变的数据类型
它的出现主要是为了解决可能出现的对象键名冲突的问题
bigint
是一种数字类型的数据,可以表示任意精度格式的整数
- 使用 bigint 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围
- 不过,bigint 的实现并不适合加密,因为它们没有考虑防止时序攻击
字面量(数字后跟小写字母 n
):
1234n // 十进制
0b10101010n // 二进制
0o7777n // 八进制
0xffff56 // 十六进制
可以用 BigInt()
函数把 number
或 string
转换为 bigint
:
BigInt(562) // 562n
const str = '1' + '0'.repeat(100) // 1 后跟 100 个 0
BigInt(str) // 一个天文数字:10n**100n
bigint
值的算术运算与 number
的算术运算类似,只不过是整数,如:除法会丢弃余数并且会向零舍入
1000n + 2000n // 3000n
3000n - 2000n // 1000n
2000n * 3000n // 6_000_000n
3000n / 997n // 3n
3000n % 997n // 9n
(2n ** 131071n) - 1n
注意:不能混合 bigint
和 number
操作数进行算术运算
- 因为:如果一种数值类型比另一种更通用,则比较容易定义混合操作数的计算并返回更通用的类型
- 但是:上述两种类型都 不比另一种更通用
bigint
:可以表示超大值,但只能是整数number
:不能表示超大值,但可以是整数或浮点数
比较操作符允许混合操作数类型:
1 < 2n // true
2 > 1n // true
0 == 0n // true
0 === 0n // false,先比较类型是否相同
位操作符通常可以用于 bigint
操作数;但 Math
对象的任何函数都不接收 bigint
操作数
引用数据类型
引用数据类型只有一种:object
- 字面量:
{ a: 1, b: 2 }
new Object()
- 基本数据类型转对象:
new Object(1)
或Object(1)
- 基本数据类型转对象:
Object.create(原型对象)
Object.create(null)
:创建没有原型的“纯”对象
特殊对象:Function
特殊点:
- 可以进行
[[call]]
调用 - 可以作为构造器创建对象(使用
new
关键字) - 有
prototype
属性以实现原型体系
特殊对象:Array
字面量:[1, 2, 3]
特殊点:
length
属性自动更新
特殊对象:RegExp
字面量:/pattern/flags
自动装箱与自动拆箱
Number
、Boolean
、String
即可作为普通函数使用也可以作为构造函数使用:
- 作为普通函数:显式类型转换,如
Number(xxx)
- 作为构造函数:创建对象,如
new Number(xxx)
自动装箱
基本数据类型要使用其对应包装类型的属性或方法时,会自动装箱成对应包装类型的实例对象
'aa'.split('')
// 相当于:
(new String('aa')).split('')
自动拆箱
包装类对象会自动拆箱成基本数据类型
new Number(1) + 2
// 相当于:
1 + 2
相等判断
==
:- 如果两侧变量有一个是
null
或nudefined
,就判断另一个变量是否也是null
或nudefined
- 如果两侧变量都不是
null
或nudefined
:如果类型不同,先进行隐式类型转换,再判断值是否相同;类型相同则直接判断值是否相同
- 如果两侧变量有一个是
===
:先判断类型是否相同,不同直接返回false
,之后判断值是否相同- 基本数据类型:值是否相同
- 引用数据类型的值是引用(即:地址):引用地址是否相同
Object.is(a, b)
:基本同===
,除了:NaN === NaN // false
;Object.is(NaN, NaN) // true
+0 === -0 // true
;Object.is(+0, -0) // true
。其中+0
即0
注:switch...case
是使用 ===
判断是否匹配
显式类型转换
number
:+xxx
、Number(xxx)
boolean
:!!xxx
、Boolean(xxx)
string
:xxx + ''
、(反引号)${xxx}(反引号)
、String(xxx)
隐式类型转换
- 基本数据类型:转换成
number
- 引用数据类型:先转换成基本数据类型,再将基本数据类型转换成
number
引用数据类型 → 基本数据类型:
- 对象是否有
Symbol.toPrimitive
方法?- 有:调用该方法
- 返回值是基本数据类型:✅
- 返回值不是基本数据类型:❌
TypeError: Cannot convert object to primitive value
- 无:继续下一步
- 有:调用该方法
- 调用
valueOf
方法- 返回值是基本数据类型:✅
- 返回值不是基本数据类型:继续下一步
- 调用
toString
方法- 返回值是基本数据类型:✅
- 返回值不是基本数据类型:❌
TypeError: Cannot convert object to primitive value
判空
- 未声明:
- 错误方法:
xxx == undefined
,会报错ReferenceError: xxx is not defined
- 正确方法:
typeof xxx === undefined
- 错误方法:
undefined
:xxx === undefined
null
:xxx === null
undefined
或null
:xxx == null
对象判空
function isEmptyObject(obj: object): boolean {
for (let key in obj) {
if (obj.hasOwnProperty(key)) return false
}
return true
}
数组判空
function isEmptyArray(arr: array): boolean {
return (arr instanceof Array && arr.length === 0)
}
// 或者使用 ES6 新增的 Array.isArray() 方法
类型判断
typeof
用于判断变量的数据类型。
number
、boolean
、string
、undefined
、symbol
、bigint
、object
都是正常的,只有两个特殊:
typeof null === 'object'
typeof 函数 === 'function'
两种使用方法:
typeof xxx
typeof(xxx)
(不推荐)
底层机制:直接在计算机底层基于数据类型的值(二进制)进行检测
typeof 12 // "number"
typeof NaN // "number"
typeof '' // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof 11n // "bigint"
typeof Symbol() // "symbol"
typeof {} // "object"
// 特殊
typeof null // "object"
// 对象存储在计算机中,都是以 000 开始的二进制存储,null 也是,所以检测出来的结果是对象
typeof function() {} // "function"
typeof () => {} // "function"
// 其他
typeof [] // "object"
typeof /^.$/ // "object"
// 数组和正则都是对象
typeof Symbol // function
instanceof
用于判断变量是否是类的实例,作用于引用数据类型
使用方法:实例 instanceof 类
注意点:
- 右侧必须是引用数据类型,否则报错
1 instanceof 2 // TypeError: Right-hand side of 'instanceof' is not an object
- 不会 自动装箱
1 instanceof Number // false
new Number(1) instanceof Number // true
底层机制:只要当前类出现在实例的原型链上,结果都是 true
[] instanceof Array // true
/^.$/ instanceof ReqExp // true
new Date() instanceof Date // true
// 问题 1
1 instanceof Number // false
// instanceof 不能检测基本数据类型
// 问题 2
[] instanceof Object // true
// 无法手动控制查询第几层原型链
// 问题 3
function Fn() {}
Fn.prototype = Array.prototype
new Fn() instanceof Array // true
// 可以肆意修改原型指向
// 当然,实际开发中,几乎不会修改原型指向
// 问题 4
const window2 = window.frames[0]; // 拿到 iframe window 对象
[] instanceof window2.Array // flase
// 必须是同一个全局执行上下文
constructor
与 instanceof
一样,但是:
- 可以检测基本数据类型(自动装箱)
- 可以手动控制查询第几层原型链
// 解决 instanceof 的问题 1
// 可以检测基本数据类型
const num = 1
1..constructor === Number // true
// 解决 instanceof 的问题 2
// 可以自己写代码,手动控制查询第几层原型链
// 但是性能要比 instanceof 差(无关紧要,几乎无差别)
const arr = []
arr.constructor === Array // true,相当于 arr.__proto__.constructor
arr.__proto__.__proto__.constructor === Object // true
// 问题
// constructor 可以随便改
// 同样,实际开发很少改 constructor
Number.prototype.constructor = 'AA'
1..constructor === Number // false
Object.prototype.toString.call()
// const toType = target => {
// return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
// }
// 其实上面就可以达到效果了,但是还可以优化效率
// typeof 效率更高:避免函数调用和自动装箱
const classof = target => {
const type = typeof target
return type === 'object'
? Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
: type
}
Object.prototype.toString
方法执行步骤:
- 获取
this
的[[Class]]
属性的值,记为xxx
- 拼接字符串并返回
[object xxx]
xxx
是什么:([[Class]]
类属性)
- 内置构造函数创建的类对象:通过内置构造函数(
Array
、Date
等)创建的对象包含“类属性”(class attribute),他与构造函数的名称相匹配 - 自定义对象类型
- 自定义了
Symbol.toStringTag
属性,并且值的类型是string
,则是该值 - 否则是
"Object"
- 自定义了
call()
:
- 改变
Object.prototype.toString
的this
指向 - 通过
call()
调用而不是直接使用xxx.toString()
的原因是防止toString()
方法被重写 call()
中直接传入基本数据类型也是可以的,因为会自动装箱call()
中直接传入undefined
和null
也是可以的,因为它俩也有自己唯一的特定类属性(class attribute)标识
classof({ [Symbol.toStringTag]: 'Foo' }) // foo
class Bar {
[Symbol.toStringTag] = 'Bar'
}
classof(new Bar()) // bar