基本数据类型

  • 值存储于栈空间(string 不是,存储于字符串常量区,JS 屏蔽了这些细节,使其表现同其他基本数据类型)
  • 无需 GC,函数调用栈弹出时销毁(不是销毁,之后会被覆盖而已)

number

JavaScript 使用 IEEE 754 标准定义的 64 位浮点格式表示数值(绝大多数现代编程语言都使用该标准,如 C、Java)

这意味着:

  • 能表示的数有范围,超出则变成 Infinity-Infinity
    • Number.MAX_VALUE1.7976931348623157e+308
    • Number.MIN_VALUE5e-324
  • 能准确表示一个范围内的整数,超出则可能会在末尾的数字上损失一些精度
    • Number.MAX_SAFE_INTEGER2^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^9e 大小写都可以)
    • 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 / 0undefined - 10
    • 任何涉及 NaN 的操作都会返回 NaN
    • NaN 与任何值都不相等,包括它自身(即:NaN !== NaN
  • Infinity:无穷大
    • 如:5 / 0Infinity - 10000
    • Infinity 无论如何运算都将是 Infinity
    • 除了 Infinity - Infinity // NaN
  • -Infinity:无穷小
    • 如:-5 / 05 / -0
    • -Infinity 无论如何运算都将是 -Infinity
    • 除了 Infinity - Infinity // NaN

补充:

  • 0 / 5 // 0
  • 除法:被除数和除数同号,结果为正;否则为负
  • 取模:结果正负同被除数(除号前面的数)
  • +0-0 (+00)
    • +0 === -0 // true
    • Object.is(0, -0) // false

类型转换

Number(xxx)+xxx

  • booleantrue1false0
  • null0
  • undefinedNaN
    • +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

只有两个值:truefalse

布尔判定

  • truly 变量:!!a === true 的变量
  • falsely 变量:!!a === false 的变量

以下是 falsely 变量,除此之外都是 truly 变量:

  • number0NaN
  • string''
    • 注意:是空串,而不是空白串,!!' ' // true
  • null
  • undefined

特别强调:引用类型转换为 boolean 都是 true,即使是空对象或空函数

string

创建方式

  • 字面量:双引号 " 和单引号 ' 完全等价、ES6 新增 模板字符串 反引号
  • String():将传入的参数转换为字符串(调用其 toString 方法)
  • new String():返回的是一个字符串对象(前两种方法得到的都是基本数据类型)

模板字符串

  1. 带嵌入表达式的字符串插值
const age = 18
 
// 不使用模板字符串
const s1 = '刚满 ' + age + '岁'
 
// 使用模板字符串
const s2 = `刚满 ${age}岁`
 
// 转义
`\`` === "`"; // true
`\${1}` === "${1}"; // true
  1. 允许多行字符串
// 不使用模板字符串
const s1 = '<div>\n' +
    '    Hello World!\n' +
    '<div>'
 
// 使用模板字符串
const s2 = `<div>
    Hello World!
<div>`
  1. 带标签的模板与自定义标签函数
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 字符

nullundefined

JS 中变量的“无”

  • 未声明而直接访问,报错 ReferenceError: xxx is not defined
  • undefined:表示“未定义”
    • 一般不要显式赋值为 undefined
    • 什么时候出现 undefined
      1. 使用声明但未赋值(或显式赋值为 undefined)的变量
      2. 获取对象中未定义的属性
      3. 函数没有返回值
      4. 函数形参未传入实参
  • null:表示空对象、空指针,一般指引用数据类型指向为“空”,表示“引用空”
    • 什么时候出现 null
      1. 显式赋值为 null
      2. 无法获取指定 DOM 元素,返回 null
      3. 通过正则表达式匹配,没有匹配成功,返回 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"
    • 转换为 numbernull0undefinedNaN
      • null + 0 // 0
      • undefined + 0 // NaN

symbol

代表创建后独一无二不可变的数据类型

它的出现主要是为了解决可能出现的对象键名冲突的问题

#todo

bigint

是一种数字类型的数据,可以表示任意精度格式的整数

  • 使用 bigint 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围
  • 不过,bigint 的实现并不适合加密,因为它们没有考虑防止时序攻击

字面量(数字后跟小写字母 n):

1234n       // 十进制
0b10101010n // 二进制
0o7777n     // 八进制
0xffff56    // 十六进制

可以用 BigInt() 函数把 numberstring 转换为 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

注意:不能混合 bigintnumber 操作数进行算术运算

  • 因为:如果一种数值类型比另一种更通用,则比较容易定义混合操作数的计算并返回更通用的类型
  • 但是:上述两种类型都 不比另一种更通用
    • 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

自动装箱与自动拆箱

NumberBooleanString 即可作为普通函数使用也可以作为构造函数使用:

  • 作为普通函数:显式类型转换,如 Number(xxx)
  • 作为构造函数:创建对象,如 new Number(xxx)

自动装箱

基本数据类型要使用其对应包装类型的属性或方法时,会自动装箱成对应包装类型的实例对象

'aa'.split('')
 
// 相当于:
(new String('aa')).split('')

自动拆箱

包装类对象会自动拆箱成基本数据类型

new Number(1) + 2
 
// 相当于:
1 + 2

相等判断

  • ==
    • 如果两侧变量有一个是 nullnudefined,就判断另一个变量是否也是 nullnudefined
    • 如果两侧变量都不是 nullnudefined如果类型不同,先进行隐式类型转换,再判断值是否相同;类型相同则直接判断值是否相同
  • ===:先判断类型是否相同,不同直接返回 false,之后判断值是否相同
    • 基本数据类型:值是否相同
    • 引用数据类型的值是引用(即:地址):引用地址是否相同
  • Object.is(a, b):基本同 ===,除了:
    • NaN === NaN // falseObject.is(NaN, NaN) // true
    • +0 === -0 // trueObject.is(+0, -0) // true。其中 +00

注:switch...case 是使用 === 判断是否匹配

显式类型转换

  • number+xxxNumber(xxx)
  • boolean!!xxxBoolean(xxx)
  • stringxxx + ''(反引号)${xxx}(反引号)String(xxx)

隐式类型转换

  • 基本数据类型:转换成 number
  • 引用数据类型:先转换成基本数据类型,再将基本数据类型转换成 number

引用数据类型 → 基本数据类型:

  1. 对象是否有 Symbol.toPrimitive 方法?
    • 有:调用该方法
      • 返回值是基本数据类型:✅
      • 返回值不是基本数据类型:❌ TypeError: Cannot convert object to primitive value
    • 无:继续下一步
  2. 调用 valueOf 方法
    • 返回值是基本数据类型:✅
    • 返回值不是基本数据类型:继续下一步
  3. 调用 toString 方法
    • 返回值是基本数据类型:✅
    • 返回值不是基本数据类型:❌ TypeError: Cannot convert object to primitive value

判空

  • 未声明:
    • 错误方法:xxx == undefined,会报错 ReferenceError: xxx is not defined
    • 正确方法:typeof xxx === undefined
  • undefinedxxx === undefined
  • nullxxx === null
  • undefinednullxxx == 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

用于判断变量的数据类型。

numberbooleanstringundefinedsymbolbigintobject 都是正常的,只有两个特殊:

  • typeof null === 'object'
  • typeof 函数 === 'function'

两种使用方法:

  1. typeof xxx
  2. 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 方法执行步骤:

  1. 获取 this[[Class]] 属性的值,记为 xxx
  2. 拼接字符串并返回 [object xxx]

xxx 是什么:([[Class]] 类属性)

  • 内置构造函数创建的类对象:通过内置构造函数(ArrayDate 等)创建的对象包含“类属性”(class attribute),他与构造函数的名称相匹配
  • 自定义对象类型
    • 自定义了 Symbol.toStringTag 属性,并且值的类型是 string,则是该值
    • 否则是 "Object"

call()

  • 改变 Object.prototype.toStringthis 指向
  • 通过 call() 调用而不是直接使用 xxx.toString() 的原因是防止 toString() 方法被重写
  • call() 中直接传入基本数据类型也是可以的,因为会自动装箱
  • call() 中直接传入 undefinednull 也是可以的,因为它俩也有自己唯一的特定类属性(class attribute)标识
classof({ [Symbol.toStringTag]: 'Foo' }) // foo
 
class Bar {
    [Symbol.toStringTag] = 'Bar'
}
classof(new Bar()) // bar