JavaScript 数据类型与内置方法
JavaScript 的数据类型体系、类型检测、类型转换,以及各类型原型链上的常用方法。
基本类型(7 种)
js
// 1. string
const str = 'hello'
const str2 = "hello"
const str3 = `hello ${str}`
// 2. number(整数和浮点数共用一种类型)
const int = 42
const float = 3.14
const nan = NaN // typeof NaN === 'number'(历史遗留)
const inf = Infinity
const negInf = -Infinity
// 3. boolean
const bool = true
const bool2 = false
// 4. undefined(变量已声明但未赋值,或函数无返回值)
let unsetVar
console.log(unsetVar) // undefined
console.log(undefined === undefined) // true
// 5. null(主动赋值,表示"空"或"无")
const emptyValue = null
console.log(null === null) // true
console.log(null == undefined) // true(宽松相等)
console.log(null === undefined) // false
// 6. symbol(唯一标识符)
const sym = Symbol('desc')
const sym2 = Symbol('desc')
console.log(sym === sym2) // false(每个 Symbol 都唯一)
console.log(sym.description) // 'desc'
// 7. bigint(大整数,超出 Number 安全整数范围)
const big = 9007199254740991n
const big2 = BigInt(9007199254740991)
console.log(big === big2) // true
console.log(big + 1n) // 9007199254740992n
// console.log(big + 1) // ❌ TypeError:不能混合运算基本类型的存储
栈内存(Stack):
┌──────────┐
│ str: 0x1 │ ──→ 堆内存: "hello"
├──────────┤
│ num: 42 │ (直接存值)
├──────────┤
│ flag: true│
└──────────┘基本类型存储在栈中(string 除外,字符串值存在堆中,栈中存引用),访问速度快。
引用类型
js
// 普通对象
const obj = { name: '张三', age: 25 }
// 数组(本质是对象,typeof [] === 'object')
const arr = [1, 2, 3]
// 函数(本质是对象,可调用)
function fn() {}
const arrow = () => {}
const asyncFn = async () => {}
// 日期
const date = new Date()
// 正则
const regex = /\d+/g
const regex2 = new RegExp('\\d+', 'g')
// Map / Set
const map = new Map([['a', 1]])
const set = new Set([1, 2, 3])
// Promise
const promise = new Promise((resolve) => resolve(42))
// 其他内置对象
new Error('错误')
new RegExp(/\d+/)
new WeakMap()
new WeakSet()基本类型 vs 引用类型
| 特性 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈(Stack) | 堆(Heap),栈中存引用地址 |
| 赋值方式 | 值拷贝 | 引用拷贝(共享同一对象) |
| 比较方式 | 比较值 | 比较引用地址 |
| 可变性 | 不可变 | 可变 |
| typeof | 返回具体类型 | 大部分返回 'object' |
js
// ---- 基本类型:值拷贝 ----
let price = 10
let discount = price
discount = 20
console.log(price) // 10(price 不受影响)
// ---- 引用类型:引用拷贝 ----
let obj1 = { count: 10 }
let obj2 = obj1 // obj2 和 obj1 指向同一个对象
obj2.count = 20
console.log(obj1.count) // 20(被修改了!)
// ---- 比较 ----
console.log(1 === 1) // true(值相同)
console.log('a' === 'a') // true(值相同)
console.log({ a: 1 } === { a: 1 }) // false(不同对象,不同地址)
console.log(obj1 === obj2) // true(同一对象,同一地址)函数参数传递
js
function change(price, obj) {
price = 100 // 基本类型:修改的是副本
obj.name = '李四' // 引用类型:修改的是同一对象
obj = { age: 30 } // 重新赋值引用:断开连接,不影响外部
}
let price = 10
let person = { name: '张三' }
change(price, person)
console.log(price) // 10(没变)
console.log(person.name) // '李四'(被修改了)
console.log(person.age) // undefined(obj 重新赋值不影响外部)类型检测
typeof
js
typeof 'hello' // 'string'
typeof 42 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof 42n // 'bigint'
typeof null // 'object'(历史遗留 bug,不能改)
typeof {} // 'object'
typeof [] // 'object'(数组也是 object)
typeof function(){} // 'function'
typeof class C {} // 'function'
typeof new Date() // 'object'
typeof /\d+/ // 'object'instanceof
检测构造函数的 prototype 是否在对象的原型链上。
js
[] instanceof Array // true
[] instanceof Object // true(Array.prototype 继承自 Object.prototype)
{} instanceof Object // true
new Date() instanceof Date // true
/\d+/ instanceof RegExp // true
// 基本类型不适用
'hello' instanceof String // false
42 instanceof Number // false
// 跨 iframe 检测可能失效Object.prototype.toString.call(最准确)
js
const type = (value) => Object.prototype.toString.call(value)
type('hello') // '[object String]'
type(42) // '[object Number]'
type(true) // '[object Boolean]'
type(undefined) // '[object Undefined]'
type(null) // '[object Null]'
type(Symbol()) // '[object Symbol]'
type(42n) // '[object BigInt]'
type({}) // '[object Object]'
type([]) // '[object Array]'
type(new Date()) // '[object Date]'
type(/\d+/) // '[object RegExp]'
type(new Map()) // '[object Map]'
type(new Set()) // '[object Set]'
type(new Promise(() => {})) // '[object Promise]'
type(function(){}) // '[object Function]'Array.isArray
js
Array.isArray([]) // true
Array.isArray({}) // false
Array.isArray('hello') // false
Array.isArray({ length: 0 }) // false类型转换
大白话解释: 类型转换就像"换衣服"。JavaScript 有时候需要把一种类型"换成"另一种类型才能进行运算。比如 '5' + 2 需要把数字 2 换成字符串 '2',结果是 '52'。
为什么要理解类型转换?
- 避免隐式转换坑:
'5' - 2是 3,但'5' + 2是'52',不同运算符转换规则不同 - 理解
==和===的区别:==会隐式转换,===不会 - 排查奇怪的 Bug:
[] + []是空字符串,[] + {}是'[object Object]'
显式转换
js
// ---- 转字符串 ----
String(123) // '123'
String(true) // 'true'
String(null) // 'null'
String(undefined) // 'undefined'
String(Symbol()) // 'Symbol()'
String(42n) // '42'
;(123).toString() // '123'
;(10).toString(2) // '1010'(二进制)
;(255).toString(16) // 'ff'(十六进制)
JSON.stringify({ a: 1 }) // '{"a":1}'
JSON.stringify([1, 2]) // '[1,2]'
JSON.stringify(undefined) // undefined(返回 undefined,不是字符串)
JSON.stringify(null) // 'null'
// ---- 转数字 ----
Number('123') // 123
Number('12.3') // 12.3
Number('abc') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Number(Symbol()) // ❌ TypeError
Number(42n) // 42
parseInt('123px') // 123(解析到非数字字符为止)
parseInt('0xff') // 255(自动识别十六进制)
parseInt('abc') // NaN
parseFloat('3.14px') // 3.14
+'42' // 42(一元加号)
+'abc' // NaN
+' 123 ' // 123(自动去空格)
// ---- 转布尔 ----
Boolean(0) // false
Boolean(-0) // false
Boolean('') // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean(false) // false
// 以上 7 个是假值(Falsy),其余全部是真值(Truthy)
Boolean(1) // true
Boolean('hello') // true
Boolean({}) // true(对象都是 true)
Boolean([]) // true(数组也是 true)
Boolean(function(){}) // true
!!'hello' // true(双感叹号转布尔)隐式转换(运算符触发)
js
// ---- 字符串拼接 ----
'hello' + 123 // 'hello123'(数字转字符串)
'hello' + null // 'hellonull'
'hello' + undefined // 'helloundefined'
'hello' + {} // 'hello[object Object]'
// ---- 数学运算 ----
'5' - 2 // 3(字符串转数字)
'5' * 2 // 10
'5' / 2 // 2.5
'5' % 2 // 1
+'123' // 123
-'123' // -123
// ---- 比较运算 ----
'5' > 3 // true(字符串转数字)
'5' > '20' // true(字符串比较:按字典序 '5' > '2')
'abc' > 'abd' // false(按字典序比较)
null == undefined // true
null === undefined // false
NaN === NaN // false(NaN 不等于任何值,包括自身)
Number.isNaN(NaN) // true(推荐用这个判断)
// ---- 逻辑运算 ----
'' || 'default' // 'default'(空字符串是假值)
0 || 42 // 42
null ?? 'default' // 'default'(只判断 null/undefined)
0 ?? 42 // 0(0 不是 null/undefined)经典面试题
js
[] + [] // ''(空字符串)
[] + {} // '[object Object]'
{} + [] // 0({} 被解析为空代码块,+[] 转数字为 0)
true + true // 2
true + false // 1
1 + null // 1
1 + undefined // NaN
'1' + 2 + 3 // '123'(从左到右,先拼接)
1 + 2 + '3' // '33'(先算 1+2=3,再拼接 '3')
[] == ![] // true
// ![] → false
// [] == false → 0 == 0 → true包装类型
基本类型在调用方法时,JS 引擎会临时创建包装对象,调用完立即销毁。
js
const str = 'hello'
console.log(str.length) // 5(临时创建 String 包装对象)
console.log(str.toUpperCase()) // 'HELLO'
// 等价于引擎内部临时操作:
// const temp = new String('hello')
// temp.length → 5
// temp = null(用完销毁)
// ❌ 不要手动创建包装对象
const strObj = new String('hello')
console.log(typeof strObj) // 'object'(不是 string!)
console.log(strObj === 'hello') // false
console.log(strObj == 'hello') // true(会自动拆箱)
// Number / Boolean 同理
const numObj = new Number(42)
console.log(typeof numObj) // 'object'
console.log(numObj + 1) // 43(自动拆箱参与运算)String 原型方法
查找
js
const str = 'Hello, World!'
// includes(searchStr) —— 判断是否包含指定字符串,返回布尔值
str.includes('World') // true
str.includes('xyz') // false
// indexOf(searchStr) —— 返回首次出现的索引,找不到返回 -1
str.indexOf('World') // 7
str.indexOf('xyz') // -1
// lastIndexOf(searchStr) —— 从后往前找,返回最后一次出现的索引
str.lastIndexOf('l') // 10
// startsWith(searchStr) —— 判断是否以指定字符串开头
str.startsWith('Hello') // true
str.startsWith('World') // false
// endsWith(searchStr) —— 判断是否以指定字符串结尾
str.endsWith('!') // true
str.endsWith('World') // false
// search(regexp) —— 正则匹配,返回首次匹配的索引,找不到返回 -1
str.search(/world/i) // 7(i 标志忽略大小写)
str.search(/\d+/) // -1(没有数字)
// charAt(index) —— 返回指定索引的字符
str.charAt(0) // 'H'
str.charAt(100) // ''(越界返回空字符串)
// charCodeAt(index) —— 返回指定索引字符的 Unicode 编码
str.charCodeAt(0) // 72('H' 的 Unicode 编码)
// at(index) —— ES2022,支持负数索引(从末尾算起)
str.at(0) // 'H'
str.at(-1) // '!'(最后一个字符)
str.at(-2) // 'd'截取
js
const str = 'Hello, World!'
// slice(start, end) —— 截取 [start, end) 区间的字符串,支持负数
str.slice(7, 12) // 'World'(索引 7 到 11)
str.slice(-6, -1) // 'World'(负数从末尾算起:-6='W',-1='!',左闭右开)
str.slice(7) // 'World!'(省略 end,截取到末尾)
str.slice(0) // 'Hello, World!'(浅拷贝整个字符串)
// substring(start, end) —— 类似 slice,但不支持负数,负数当 0 处理
str.substring(7, 12) // 'World'
str.substring(12, 7) // 'World'(自动交换参数,保证 start < end)
str.substring(-3) // 'Hello, World!'(负数当 0,从头开始)转换
js
const str = ' Hello, World! '
// toUpperCase() / toLowerCase() —— 大小写转换,返回新字符串
str.toUpperCase() // ' HELLO, WORLD! '
str.toLowerCase() // ' hello, world! '
// trim() —— 去除两端空格(包括 \t \n \r 等空白字符)
str.trim() // 'Hello, World!'
// trimStart() / trimEnd() —— 只去除左侧/右侧空格
str.trimStart() // 'Hello, World! '
str.trimEnd() // ' Hello, World!'
// padStart(targetLength, padStr) —— 在左侧填充到指定长度
str.padStart(20, '-') // '-------Hello, World!'
str.padStart(20) // ' Hello, World!'(默认填充空格)
// padEnd(targetLength, padStr) —— 在右侧填充到指定长度
str.padEnd(20, '-') // 'Hello, World!-------'
// repeat(count) —— 重复字符串指定次数
'abc'.repeat(3) // 'abcabcabc'
'abc'.repeat(0) // ''(0 次返回空字符串)
// 大小写转换常用于忽略大小写比较
const input = 'Hello'
input.toLowerCase() === 'hello' // true替换
js
const str = 'Hello, World! Hello!'
// replace(search, replacement) —— 替换第一个匹配项,返回新字符串
str.replace('Hello', 'Hi') // 'Hi, World! Hello!'(只替换第一个)
str.replace(/Hello/g, 'Hi') // 'Hi, World! Hi!'(正则 + g 标志全局替换)
// replaceAll(search, replacement) —— ES2021,替换所有匹配项
str.replaceAll('Hello', 'Hi') // 'Hi, World! Hi!'
// replace + 回调函数 —— 第二个参数可以是函数,接收 (match, group1, group2, ..., offset, string)
'hello-world'.replace(/-(\w)/g, (_, letter) => letter.toUpperCase())
// 'helloWorld'(转驼峰:匹配到 -w,捕获组为 w,转为大写 W)
// replaceAll + 正则 —— ⚠️ 正则必须带 g 标志,否则会抛 TypeError
'123-456'.replaceAll(/\d+/g, (match) => `[${match}]`)
// '[123]-[456]'
// ❌ '123-456'.replaceAll(/\d+/, 'x') // TypeError: replaceAll with non-global regex拆分与合并
js
// split(separator, limit) —— 按分隔符拆分为数组
'a,b,c'.split(',') // ['a', 'b', 'c'](按逗号拆分)
'hello'.split('') // ['h', 'e', 'l', 'l', 'o'](按字符拆分)
'hello'.split() // ['hello'](无参数返回整个字符串的数组)
'a--b--c'.split('--') // ['a', 'b', 'c'](按多字符拆分)
'a--b--c'.split('--', 2) // ['a', 'b'](limit 参数限制返回数量)
// join(separator) —— 数组合并为字符串
['a', 'b', 'c'].join('-') // 'a-b-c'
['a', 'b', 'c'].join('') // 'abc'
[1, 2, 3].join() // '1,2,3'(默认逗号分隔)链式调用
js
const result = ' Hello, World! '
.trim()
.toLowerCase()
.replace('world', 'js')
// 'hello, js!'
// 格式化手机号
'13812345678'
.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')
// '138-1234-5678'Number 原型方法与静态方法
实例方法
js
const num = 3.14159
// toFixed(digits) —— 保留指定小数位,返回字符串(四舍五入)
num.toFixed(0) // '3'
num.toFixed(2) // '3.14'
num.toFixed(5) // '3.14159'
// toPrecision(precision) —— 指定有效数字位数,返回字符串
num.toPrecision(4) // '3.142'(4 位有效数字)
num.toPrecision(2) // '3.1'(2 位有效数字)
// toString(radix) —— 转为指定进制的字符串
;(255).toString() // '255'(默认十进制)
;(255).toString(16) // 'ff'(十六进制)
;(255).toString(2) // '11111111'(二进制)
;(10).toString(8) // '12'(八进制)
;(0.5).toString(2) // '0.1'(二进制小数)
// toLocaleString() —— 按本地格式显示(千分位分隔等)
num.toLocaleString() // '3.142'
;(1234567).toLocaleString('zh-CN') // '1,234,567'静态方法
js
// ---- 解析 ----
// parseInt(string, radix) —— 解析字符串为整数,解析到非数字字符停止
Number.parseInt('123px') // 123(遇到 p 停止)
Number.parseInt('abc') // NaN(第一个字符就不是数字)
Number.parseInt('0xff') // 255(自动识别 0x 前缀为十六进制)
Number.parseInt('0b1010') // 10(自动识别 0b 前缀为二进制)
Number.parseInt('10', 2) // 2(指定二进制解析:1*2 + 0 = 2)
// parseFloat(string) —— 解析字符串为浮点数
Number.parseFloat('3.14px') // 3.14(遇到 p 停止)
Number.parseFloat('1.23e5') // 123000(支持科学计数法)
// ---- 判断 ----
// isNaN(value) —— 严格判断是否为 NaN(不会先转数字)
Number.isNaN(NaN) // true
Number.isNaN('abc') // false(字符串不是 NaN)
Number.isNaN(Number('abc')) // true(先转数字得 NaN,再判断)
// isFinite(value) —— 判断是否为有限数(排除 Infinity、-Infinity、NaN)
Number.isFinite(42) // true
Number.isFinite(Infinity) // false
Number.isFinite(NaN) // false
// isInteger(value) —— 判断是否为整数
Number.isInteger(42) // true
Number.isInteger(42.0) // true(42.0 等于 42)
Number.isInteger(42.5) // false
// isSafeInteger(value) —— 判断是否在安全整数范围内(-2^53 ~ 2^53)
Number.isSafeInteger(42) // true
Number.isSafeInteger(9007199254740992) // false(超出安全范围)
// ---- 常量 ----
Number.MAX_SAFE_INTEGER // 9007199254740991(2^53 - 1)
Number.MIN_SAFE_INTEGER // -9007199254740991
Number.MAX_VALUE // 1.7976931348623157e+308(最大正数)
Number.MIN_VALUE // 5e-324(最小正数,接近 0)
Number.EPSILON // 2.220446049250313e-16(最小精度差)浮点数精度问题
js
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
// 原因:0.1 和 0.2 的二进制表示都是无限循环小数
// 0.1 → 0.0001100110011...
// 0.2 → 0.0011001100110...
// 解决方案
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true
parseFloat((0.1 + 0.2).toFixed(1)) // 0.3
Math.round((0.1 + 0.2) * 10) / 10 // 0.3Array 原型方法
增删(修改原数组)
js
const arr = [1, 2, 3, 4, 5]
// ---- 末尾操作 ----
// push(item1, item2, ...) —— 在末尾添加元素,返回新长度
arr.push(6) // 返回 6(新长度),arr = [1,2,3,4,5,6]
arr.push(7, 8) // 返回 8,arr = [1,2,3,4,5,6,7,8]
// pop() —— 删除末尾元素,返回被删除的元素
arr.pop() // 返回 8,arr = [1,2,3,4,5,6,7]
// ---- 头部操作 ----
// unshift(item1, item2, ...) —— 在头部添加元素,返回新长度
arr.unshift(0) // 返回 9,arr = [0,1,2,3,4,5,6,7]
// shift() —— 删除头部元素,返回被删除的元素
arr.shift() // 返回 0,arr = [1,2,3,4,5,6,7]
// ---- 任意位置操作 ----
// splice(start, deleteCount, ...items) —— 万能方法:删除、插入、替换
const arr2 = [1, 2, 3, 4, 5]
// 删除:从索引 1 开始删 2 个
arr2.splice(1, 2) // 返回 [2, 3](被删元素),arr2 = [1, 4, 5]
// 插入:从索引 1 开始删 0 个,插入 2 和 3
arr2.splice(1, 0, 2, 3) // 返回 [](没删),arr2 = [1, 2, 3, 4, 5]
// 替换:从索引 1 开始删 1 个,插入 99
arr2.splice(1, 1, 99) // 返回 [2](被删元素),arr2 = [1, 99, 3, 4, 5]查找
js
const arr = [1, 2, 3, 4, 5]
// indexOf(value) —— 返回首次出现的索引,找不到返回 -1
arr.indexOf(3) // 2
arr.indexOf(99) // -1
// lastIndexOf(value) —— 从后往前找,返回最后一次出现的索引
arr.lastIndexOf(3) // 2
// includes(value) —— 判断是否包含指定值,返回布尔值
arr.includes(3) // true
arr.includes(99) // false
// find(callback) —— 返回第一个满足条件的元素,找不到返回 undefined
arr.find((x) => x > 3) // 4(第一个大于 3 的元素)
arr.find((x) => x > 99) // undefined
// findIndex(callback) —— 返回第一个满足条件的索引,找不到返回 -1
arr.findIndex((x) => x > 3) // 3
// findLast(callback) —— ES2023,从后往前找第一个满足条件的元素
arr.findLast((x) => x > 3) // 5
// findLastIndex(callback) —— ES2023,从后往前找第一个满足条件的索引
arr.findLastIndex((x) => x > 3) // 4
// ⚠️ NaN 的查找
const arr2 = [1, NaN, 3]
arr2.indexOf(NaN) // -1(indexOf 用 === 比较,NaN !== NaN)
arr2.includes(NaN) // true(includes 用 SameValueZero 算法)遍历(不修改原数组)
js
const arr = [1, 2, 3, 4, 5]
// forEach(callback) —— 遍历数组,无返回值,不能 break
arr.forEach((item, index, array) => {
// item: 当前元素
// index: 当前索引
// array: 原数组
console.log(index, item)
})
// map(callback) —— 映射,返回新数组(每个元素经过 callback 处理后的结果)
arr.map((x) => x * 2) // [2, 4, 6, 8, 10]
arr.map((x, i) => `${i}:${x}`) // ['0:1', '1:2', '2:3', '3:4', '4:5']
// filter(callback) —— 过滤,返回满足条件的元素组成的新数组
arr.filter((x) => x > 2) // [3, 4, 5]
arr.filter((x) => x % 2 === 0) // [2, 4](偶数)
// every(callback) —— 判断是否所有元素都满足条件,返回布尔值
arr.every((x) => x > 0) // true(所有元素都大于 0)
arr.every((x) => x > 3) // false
// some(callback) —— 判断是否有任意元素满足条件,返回布尔值
arr.some((x) => x > 4) // true(5 > 4)
arr.some((x) => x > 99) // false
// reduce(callback, initialValue) —— 累积计算,返回最终累积值
arr.reduce((acc, cur, index) => {
return acc + cur
}, 0) // 15(累加:0+1+2+3+4+5)
// reduce 求最大值
arr.reduce((max, cur) => cur > max ? cur : max, -Infinity) // 5
// reduce 实现 flat(扁平化)
[[1, 2], [3, 4], [5]].reduce((acc, cur) => acc.concat(cur), [])
// [1, 2, 3, 4, 5]排序
js
const arr = [3, 1, 4, 1, 5, 9]
// sort() —— 默认按字符串 Unicode 排序(不是数字大小!)
arr.sort() // [1, 1, 3, 4, 5, 9](巧合正确)
;[10, 9, 2].sort() // [10, 2, 9](按字符串 '10' < '2',错误!)
// sort(compareFn) —— 自定义排序函数
// compareFn(a, b) 返回值:
// 负数 → a 排在 b 前面
// 正数 → b 排在 a 前面
// 0 → 顺序不变
arr.sort((a, b) => a - b) // 升序 [1, 1, 3, 4, 5, 9]
arr.sort((a, b) => b - a) // 降序 [9, 5, 4, 3, 1, 1]
// 对象排序
const users = [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 },
{ name: '王五', age: 20 },
]
users.sort((a, b) => a.age - b.age) // 按年龄升序
// reverse() —— 反转数组(修改原数组)
arr.reverse() // [9, 5, 4, 3, 1, 1]转换与截取
js
const arr = [1, 2, 3, 4, 5]
// slice(start, end) —— 截取 [start, end) 区间,不修改原数组
arr.slice(1, 3) // [2, 3](索引 1 到 2)
arr.slice(-2) // [4, 5](最后两个)
arr.slice() // [1, 2, 3, 4, 5](浅拷贝整个数组)
// flat(depth) —— 扁平化嵌套数组,depth 为展开深度
;[1, [2, [3]]].flat() // [1, 2, [3]](默认 depth=1)
;[1, [2, [3]]].flat(Infinity) // [1, 2, 3](完全展开)
// flatMap(callback) —— ES2019,先 map 再 flat(1),等价于 map().flat(1)
;[1, 2, 3].flatMap((x) => [x, x * 2]) // [1, 2, 2, 4, 3, 6]
;['hello world', 'foo bar'].flatMap((s) => s.split(' '))
// ['hello', 'world', 'foo', 'bar']
// join(separator) —— 数组合并为字符串
arr.join('-') // '1-2-3-4-5'
arr.join('') // '12345'
// at(index) —— ES2022,支持负数索引
arr.at(0) // 1
arr.at(-1) // 5(最后一个)
// Array.from(arrayLike, mapFn) —— 将类数组/可迭代对象转为数组
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 3 }, (_, i) => i) // [0, 1, 2]
Array.from(new Set([1, 2, 2, 3])) // [1, 2, 3](去重)
// Array.of(...items) —— 用参数创建数组(解决 Array() 的歧义)
Array.of(1, 2, 3) // [1, 2, 3]
Array.of(3) // [3](不是 [undefined, undefined, undefined])判断
js
Array.isArray([]) // true
Array.isArray({}) // false
Array.isArray('hello') // false
Array.isArray({ length: 0 }) // false
// 判断是否为空数组
arr.length === 0 // true修改原数组 vs 不修改原数组
| 修改原数组 | 不修改原数组 |
|---|---|
| push / pop | slice |
| unshift / shift | concat |
| splice | map / filter |
| sort / reverse | flat / flatMap |
| fill / copyWithin | reduce / reduceRight |
| find / findIndex / includes | |
| join / at / Array.from |
Object 常用方法
获取键 / 值
js
const obj = { a: 1, b: 2, c: 3 }
// Object.keys(obj) —— 返回自身可枚举属性名组成的数组
Object.keys(obj) // ['a', 'b', 'c']
// Object.values(obj) —— 返回自身可枚举属性值组成的数组
Object.values(obj) // [1, 2, 3]
// Object.entries(obj) —— 返回自身可枚举键值对组成的二维数组
Object.entries(obj) // [['a',1], ['b',2], ['c',3]]
// 配合 for...of 遍历对象
for (const [key, value] of Object.entries(obj)) {
console.log(key, value) // 'a' 1, 'b' 2, 'c' 3
}
// 配合 Object.fromEntries(ES2019)将键值对转回对象
Object.fromEntries([['x', 10], ['y', 20]]) // { x: 10, y: 20 }合并与复制
js
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }
// Object.assign(target, ...sources) —— 合并到 target,后面的覆盖前面的同名属性
Object.assign({}, obj1, obj2) // { a: 1, b: 3, c: 4 }
// 展开运算符(推荐,更简洁)
const merged = { ...obj1, ...obj2 } // { a: 1, b: 3, c: 4 }
// ---- 浅拷贝(只拷贝一层,嵌套对象仍是引用) ----
const copy1 = Object.assign({}, obj1)
const copy2 = { ...obj1 }
// ---- 深拷贝(完全独立的副本) ----
// structuredClone —— Web API(非 ES 标准),Chrome 98+(2022)支持,支持循环引用、Date、Map、Set 等
const deep = structuredClone(obj1)
// JSON 方式(有限制:不能拷贝 undefined、函数、Symbol、循环引用、Date 会变字符串)
const deep2 = JSON.parse(JSON.stringify(obj1))
// ⚠️ 浅拷贝的坑
const original = { name: '张三', address: { city: '北京' } }
const shallow = { ...original }
shallow.address.city = '上海'
console.log(original.address.city) // '上海'(被修改了!因为 address 是引用)冻结 / 密封
js
const obj = { a: 1, b: { c: 2 } }
// Object.freeze(obj) —— 完全冻结:不能增删改属性(浅冻结)
Object.freeze(obj)
obj.a = 99 // 静默失败(严格模式报 TypeError)
obj.d = 4 // 静默失败(不能新增)
delete obj.a // 静默失败(不能删除)
Object.isFrozen(obj) // true
// ⚠️ freeze 只冻结第一层!嵌套对象仍可修改
obj.b.c = 99 // ✅ 可以修改(b 对象没有被冻结)
// Object.seal(obj) —— 密封:不能增删属性,但可以修改已有属性的值
const obj2 = { a: 1 }
Object.seal(obj2)
obj2.a = 99 // ✅ 可以改
obj2.b = 2 // 静默失败(不能新增)
delete obj2.a // 静默失败(不能删除)
Object.isSealed(obj2) // true
// 对比
// freeze:不能增、不能删、不能改
// seal: 不能增、不能删、可以改属性描述符
js
const obj = {}
Object.defineProperty(obj, 'name', {
value: '张三',
writable: false, // 不可写
enumerable: false, // 不可枚举(不出现在 for...in / Object.keys 中)
configurable: false, // 不可删除或重新配置
})
obj.name = '李四' // 静默失败(writable: false)
console.log(obj.name) // '张三'
Object.keys(obj) // [](enumerable: false,不出现)
delete obj.name // 静默失败(configurable: false)
// 获取属性描述符
Object.getOwnPropertyDescriptor(obj, 'name')
// { value: '张三', writable: false, enumerable: false, configurable: false }
// 定义多个属性
Object.defineProperties(obj, {
age: { value: 25, writable: true, enumerable: true },
id: { value: 1, writable: false, enumerable: false },
})原型操作
js
// 获取原型
Object.getPrototypeOf({}) // Object.prototype
Object.getPrototypeOf([]) // Array.prototype
// 设置原型
const proto = { greet() { return 'hi' } }
const obj = Object.create(proto)
obj.greet() // 'hi'
// 创建纯净对象(无原型)
const bare = Object.create(null)
bare.__proto__ // undefined
bare.toString // undefined
bare instanceof Object // falsehasOwnProperty 与 in
js
const obj = { a: 1 }
const proto = { b: 2 }
Object.setPrototypeOf(obj, proto)
// hasOwnProperty:只检查自身
obj.hasOwnProperty('a') // true
obj.hasOwnProperty('b') // false(在原型上)
// in:检查整个原型链
'a' in obj // true
'b' in obj // true
'c' in obj // false
// 推荐用 Object.hasOwn()(ES2022,更安全)
Object.hasOwn(obj, 'a') // true
Object.hasOwn(obj, 'b') // falseMath 常用方法
js
// ---- 常量 ----
Math.PI // 3.141592653589793(圆周率)
// ---- 取整 ----
Math.ceil(1.1) // 2(向上取整)
Math.ceil(-1.1) // -1(注意:-1 比 -1.1 大)
Math.floor(1.9) // 1(向下取整)
Math.floor(-1.1) // -2(注意:-2 比 -1.1 小)
Math.round(1.5) // 2(四舍五入,.5 向上)
Math.round(-1.5) // -1(负数 .5 向上,即向 0 方向)
Math.trunc(1.9) // 1(截断小数部分,只保留整数)
Math.trunc(-1.9) // -1
// ---- 绝对值 ----
Math.abs(-5) // 5
Math.abs(5) // 5
// ---- 最大 / 最小 ----
Math.max(1, 2, 3) // 3
Math.min(1, 2, 3) // 1
Math.max() // -Infinity(无参数返回 -Infinity)
Math.max(...[1, 2, 3]) // 3(配合展开运算符)
// ---- 幂运算 ----
Math.sqrt(9) // 3(平方根)
Math.cbrt(27) // 3(立方根,ES6)
Math.pow(2, 10) // 1024(幂运算)
2 ** 10 // 1024(ES7 幂运算符,推荐)
// ---- 随机数 ----
Math.random() // [0, 1) 随机小数
// 生成 [min, max] 的随机整数
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
randomInt(1, 100) // 1~100 的随机整数常见面试题
typeof null 为什么是 'object'
js
// JS 第一版的实现中,值的类型标签存储在低位
// 000: object
// 001: int
// 010: double
// 100: string
// 110: boolean
// null 的机器码是全 0,所以被误判为 object
typeof null // 'object'(永远修不了,因为会破坏现有代码)typeof NaN 为什么是 'number'
js
// NaN 是 IEEE 754 标准中的一个特殊值
// 它属于 number 类型,只是表示"不是一个有效数字"
typeof NaN // 'number'
NaN === NaN // false
Number.isNaN(NaN) // true(推荐)
isNaN('abc') // true(会先转数字,不推荐)[] == ![] 为什么是 true
js
// 1. ![] → false(对象是真值,取反得 false)
// 2. [] == false
// 3. 规则:布尔值先转数字 → 0
// 4. [] == 0
// 5. 规则:对象转原始值 → ''(空字符串调用 toString)
// 6. '' == 0
// 7. 规则:字符串转数字 → 0
// 8. 0 == 0 → true如何判断一个变量是数组
js
const arr = [1, 2, 3]
// 最推荐
Array.isArray(arr) // true
// 其他方式
arr instanceof Array // true
Object.prototype.toString.call(arr) === '[object Array]' // true
arr.constructor === Array // true
Array.prototype.isPrototypeOf(arr) // true如何判断空对象
js
const obj = {}
// 方案一
Object.keys(obj).length === 0 // true
// 方案二
JSON.stringify(obj) === '{}' // true
// 方案三
Object.getOwnPropertyNames(obj).length === 0 // true
// 方案四(包含原型属性的判断用 for...in)
function isEmpty(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false
}
return true
}