搜索 K
主题
eval)、对象内枚举(通过 for...in 和 Object 工具方法)和源代码恢复(JavaScript 函数会存储其源代码文本,可以使用 toString() 进行检索)。Array、Date 和 Math)和一套核心语言元素(例如,运算符、控制结构和语句)。可以通过用额外的对象实现各种目的对核心 JavaScript 进行扩展;例如: | JavaScript | Java |
|---|---|
| 面向对象。不区分对象类型。继承通过原型机制实现,可以向对象动态地添加属性和方法。 | 基于类。对象分为类和实例,所有的继承通过类层级实现。不能向类和实例动态地添加属性或方法。 |
| 不需要声明变量数据类型(动态类型,松散类型)。 | 必须声明变量数据类型(静态类型,强类型)。 |
| 不能自动写入硬盘。 | 可以自动写入硬盘。 |
früh 和 Früh 是两个不同的变量)。;)分隔;语句独占一行时分号可省略(ASI 自动分号补全),但始终在语句末尾写分号是最佳实践,可减少代码出问题的可能性。// 单行注释;/* */ 多行注释,块注释不能嵌套(注释内出现 */ 会提前结束注释)。#!/usr/bin/env node),只出现在文件开头,用于指定执行脚本的 JavaScript 引擎路径。var:声明一个变量,可选初始化;非块级作用域(属于函数或全局作用域)。let:声明块级作用域的局部变量,可选初始化。const:声明块级作用域的只读命名常量,必须初始化。_)或美元符号($)开头,后续字符还可以是数字;也可使用大部分 Unicode 字母。const { bar } = foo。let x = 42 中 let x 是声明、= 42 是初始化器;var / let 未初始化时值为 undefined;const 缺少初始化器直接 SyntaxError。let x // 只有声明,没有初始化器
console.log(x) // undefined —— 未初始化的变量默认值为 undefined
let y = 42 // “let y” 是声明,“= 42” 是初始化器
console.log(y) // 42
// const 缺少初始化器直接报语法错误(解析期就失败,无法被 try/catch 捕获)
// const z // SyntaxError: Missing initializer in const declarationlet / const 受块语句限制;var 无视块,只属于所在函数或全局作用域。var 只有声明(并默认初始化为 undefined)被提升,赋值不提升——声明前访问得到 undefined。let / const 在声明前访问会抛出 ReferenceError:从块开始到声明处的区间是「暂时性死区」(TDZ)。window(window.variable);跨运行时统一用 globalThis。if (true) {
var x = 5 // var 无视块,属于所在函数(或全局)作用域
let y = 6 // let 被限制在块语句内
}
console.log(x) // 5 —— 块外仍能访问
try {
console.log(y)
} catch (e) {
console.log(`${e.name}: ${e.message}`) // ReferenceError: y is not defined
}console.log(x === undefined) // true —— 声明(并默认初始化为 undefined)被提升,赋值不提升
var x = 3
;(function () {
console.log(x) // undefined —— 函数内的 var x 声明被提升到函数顶部,遮蔽了外层 x
var x = '局部值'
})()try {
console.log(x) // 从块开始到声明处是“暂时性死区”,访问即抛错
const x = 3
} catch (e) {
console.log(`${e.name}: ${e.message}`) // ReferenceError: Cannot access 'x' before initialization
}
// 函数声明是整体提升:声明之前也能安全调用
console.log(add(1, 2)) // 3
function add(a, b) {
return a + b
}const 仅阻止重新赋值,不阻止修改:被赋值为常量的对象属性、数组元素仍然可变(MY_OBJECT.key = "其他值"、MY_ARRAY.push(...) 都合法)。const PI = 3.14
try {
PI = 3.14159 // const 禁止重新赋值
} catch (e) {
console.log(`${e.name}: ${e.message}`) // TypeError: Assignment to constant variable.
}// const 仅阻止“重新赋值”,不阻止“修改”
const MY_OBJECT = { key: '值' }
MY_OBJECT.key = '其他值' // 对象属性不受保护
console.log(MY_OBJECT) // { key: '其他值' }
const MY_ARRAY = ['HTML', 'CSS']
MY_ARRAY.push('JAVASCRIPT') // 数组元素同样不受保护
console.log(MY_ARRAY) // [ 'HTML', 'CSS', 'JAVASCRIPT' ]true 和 false。Null / NULL)。42、3.14159。9007199254740992n。"Howdy"。+ 运算符中数字遇到字符串时,数字被转换成字符串:"37" + 7 // "377";其他运算符不会转换:"37" - 7 // 30、"37" * 7 // 259。parseInt():只返回整数;最佳实践是总是带上进制参数,如 parseInt("101", 2) // 5。parseFloat():解析浮点数。+ 运算符:(+"1.1") + (+"1.1") // 2.2。// “+” 运算符中数字遇到字符串:数字被转换成字符串
console.log('答案是 ' + 42) // 答案是 42
console.log(42 + ' 是答案') // 42 是答案
console.log('37' + 7) // 377
// 其他运算符不会把数字转成字符串,而是把字符串转成数字
console.log('37' - 7) // 30
console.log('37' * 7) // 259// parseInt 最佳实践:总是带上进制参数
console.log(parseInt('101', 2)) // 5 —— 按二进制解析
console.log(parseInt('101', 10)) // 101 —— 按十进制解析
console.log(parseFloat('3.14')) // 3.14
// 一元 “+” 也能把字符串转为数字
console.log('1.1' + '1.1') // 1.11.1 —— 字符串拼接
console.log(+'1.1' + +'1.1') // 2.2 —— 先各自转成数字再相加[...]:每次求值都创建新数组;连续逗号会留下空槽(empty,与 undefined 不同,遍历方法如 map 会跳过空槽,但索引访问返回 undefined);只有最后一个尾逗号被忽略;书写时应显式写 undefined 或用注释标出缺失元素。true 和 false(不要与 Boolean 包装对象混淆)。0x)、八进制(0o)、二进制(0b)表示,BigInt 加后缀 n;浮点数至少一位数字,且必须带小数点或指数部分(e / E),如 3.1E+12、.1e-23。{...}:不能出现在语句开头({ 会被解析为语句块);属性名不是合法标识符或数字时必须加引号,且只能用方括号 [] 访问;增强的对象字面量支持 __proto__ 设置原型、foo: foo 同名简写、方法简写、super 调用和计算属性名。const re = /ab+c/;。String 对象的所有方法(JavaScript 自动转换为临时字符串对象,用完即弃);模板字面量用反引号包围,支持多行字符串与 ${} 插值,带标签的模板是函数调用的语法糖;特殊字符用反斜杠转义(\n、\t、\uXXXX、\u{XXXXX} 等),反斜杠还可转义引号、反斜杠自身与换行。const fish = ['Lion', , 'Angel'] // 连续逗号会留下空槽(empty)
console.log(fish) // [ 'Lion', <1 empty item>, 'Angel' ]
console.log(fish[1]) // undefined —— 索引访问返回 undefined
console.log(fish.map((x) => x.toUpperCase())) // 空槽被 map 等遍历方法跳过
const myList = ['home', , 'school', ,] // 只有最后一个尾逗号被忽略
console.log(myList.length) // 4const handler = () => 'handled'
const obj = {
handler, // “handler: handler” 赋值的简写
greet() {
// 方法简写(省略 function 关键字)
return 'hello'
},
['prop_' + (() => 42)()]: 42, // 计算(动态的)属性名
}
console.log(obj.handler()) // handled
console.log(obj.greet()) // hello
console.log(obj.prop_42) // 42// 字符串插值
const name = 'Lev'
const time = 'today'
console.log(`你好 ${name},${time} 过得怎么样?`)
// 多行字符串:模板字面量可以跨行,单双引号包裹的字符串不行
console.log(`第一行
第二行`)
// 带标签的模板:函数调用的语法糖
const tag = (strings, ...values) =>
strings.reduce((acc, s, i) => acc + s + (values[i] ?? ''), '')
console.log(tag`合计 ${1 + 2} 元`) // 合计 3 元{ ... } 组合多条语句,通常配合控制流语句(if、for、while)使用。var 声明的变量不是块级作用域:块内的 var x 与块外的 var x 是同一个变量,设置效果会泄漏到块外(C / Java 中等效代码则是两个变量);用 let / const 可消除这个作用域效果。var x = 1
{
var x = 2 // var 不是块级作用域:与块外的 x 是同一个变量
}
console.log(x) // 2 —— 在 C / Java 中等效代码会输出 1
let y = 1
{
let y = 2 // let 在块内是独立的变量,消除了这个作用域效果
}
console.log(y) // 1if 在条件为真时执行语句,可选的 else 子句在条件为假时执行;else if 按顺序测试多个条件,只有第一个求值为真的分支会执行。if 的时候。x = y)当条件——看起来像手误的 ==,易读性差。false、undefined、null、0、NaN、""(空字符串);其他一切都为真——包括所有对象。Boolean 包装对象——new Boolean(false) 是对象,条件里恒为真,但 b == true 按原始值比较为假。// 6 个假值:false、undefined、null、0、NaN、""(空字符串)
const falsy = [false, undefined, null, 0, NaN, '']
console.log(falsy.filter(Boolean)) // [] —— 全部为假
// 其他一切都为真,包括所有对象(空数组、空对象也是)
if ([] && {} && '0') {
console.log('对象、非空字符串都是真值')
}
// 陷阱:Boolean 包装对象是“对象”,条件里恒为真
const b = new Boolean(false)
console.log(b ? 'b 为真' : 'b 为假') // b 为真
console.log(b == true ? 'b == true' : 'b != true') // b != true —— 比较时按原始值 falseswitch 求表达式的值并与 case 标签匹配(严格相等);匹配成功执行关联语句,无匹配则找 default 子句(惯例放最后,但位置不限)。case 关联一个可选的 break:执行后跳出 switch;省略 break 会继续贯穿(fallthrough)执行下一个 case 的语句。function price(fruitType) {
switch (fruitType) {
case 'Oranges':
console.log('橙子是 $0.59 一磅')
break
case 'Bananas':
console.log('香蕉是 $0.48 一磅')
break // 去掉这个 break,会继续“贯穿”执行 Cherries 分支
case 'Cherries':
console.log('樱桃是 $3.00 一磅')
break
default:
console.log(`对不起,${fruitType} 卖完了`)
}
}
price('Bananas') // 香蕉是 $0.48 一磅
price('Durian') // 对不起,Durian 卖完了throw 任意值(字符串、数字、布尔、对象都行),但使用专门为此设计的异常类型通常更高效: Error 及其子类(RangeError、ReferenceError、SyntaxError、TypeError 等)。DOMException:Web API 抛出的异常。throw expression; 抛出异常,指明要抛出的值。try...catch 由 try 块和 catch 块组成:try 块中抛出异常时,控制立即转移到 catch 块;没有异常则跳过 catch 块。catch (e) 的标识符存储被抛出的值,只在 catch 块存续期间可用。catch 块中输出错误用 console.error() 而不是 console.log()——它会格式化为错误并加入页面的错误消息列表。finally 在 try 和 catch 之后、try...catch...finally 语句后面的代码之前执行;无论是否抛异常都会执行(即使没有 catch 块)。finally 块有返回值,该值会覆盖整个流程的返回值——不管 try / catch 里 return 了什么,甚至会覆盖 catch 中抛出或重新抛出的异常。try...catch 可以嵌套;内部 try 块没有对应 catch 时必须有 finally,异常交由外层的 catch 处理。Error 构造函数,之后可通过 e.name(错误类名)和 e.message(比错误对象转字符串更简明)获取信息。function getMonthName(mo) {
const months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
if (months[mo - 1]) return months[mo - 1]
throw new Error('无效的月份数值') // 用 throw 抛出异常
}
let monthName
try {
monthName = getMonthName(13) // 可能抛出异常的函数
} catch (e) {
monthName = '未知'
console.error(e.message) // 调试时建议用 console.error 而非 console.log
} finally {
console.log('无论是否抛异常,finally 都会执行') // 常用于释放资源(如关闭文件)
}
console.log(monthName) // 未知function f() {
try {
console.log(0)
throw new Error('bogus')
} catch (e) {
console.log(1)
return true // 这个 return 会被挂起,直到 finally 块结束
} finally {
console.log(3)
return false // 覆盖前面的 return true(也会覆盖 catch 中重新抛出的异常)
}
}
console.log(f()) // 依次输出 0、1、3、falsefunction doSomethingErrorProne() {
throw new Error('这里出了点问题') // 自定义异常用 Error 构造函数抛出
}
try {
doSomethingErrorProne()
} catch (e) {
console.error(e.name) // Error —— 常规的错误类名
console.error(e.message) // 这里出了点问题 —— 比整个错误对象转字符串更简明
}for、do...while、while、label、break、continue、for...in、for...of。for (initialization; condition; afterthought) statement。condition 整个省略时视为 true。while (condition) statement:先判断后执行——条件检测发生在每次执行之前,条件为假时循环体一次都不执行。do statement while (condition);:先执行后判断——语句在检查条件之前先执行一次,因此至少执行一次。while (true) 这类条件永远不会为假的循环永不停止。// for (初始化; 条件; 更新表达式) —— 执行顺序:初始化 → 判断条件 → 循环体 → 更新 → 回到判断
for (let step = 0; step < 5; step++) {
console.log('向东走一步') // 运行 5 次,step 值从 0 到 4
}// while:条件检测发生在每次执行“之前”
let n = 0
let x = 0
while (n < 3) {
n++
x += n
console.log(`n = ${n}, x = ${x}`)
}
// n = 1, x = 1
// n = 2, x = 3
// n = 3, x = 6 —— 之后 n < 3 不再为真,循环终止
// ⚠️ 避免无限循环:保证条件最终会变成假
// while (true) { console.log('你好,世界!') }// do...while:语句在检查条件“之前”先执行一次
let i = 0
do {
i += 1
console.log(i) // 1 2 3 4 5
} while (i < 5)
// 与 while 的关键区别:先执行后判断,至少执行一次
let j = 100
do {
console.log(`j = ${j},条件一开始就为假,也执行了一次`)
} while (j < 5)label: statement:给语句一个标识符,供 break / continue 引用;值是任何非保留字的合法标识符。break: while / do...while / for / switch,控制权交回其后面的语句。break label;):终止指定的带标记语句——多层循环中可一次跳出全部。continue: continue label;):结束内层本轮迭代,跳到 label 标识的循环继续下一次迭代。i === 5 && j === 5 时中断):裸 break 计数 95、break label 计数 55、continue label 计数 95——后者与裸 break 结果相同,但多层循环下流程更明晰。// 不带 label 的 continue:跳过本轮剩余代码,直接进入下一次循环
let i = 0
let n = 0
while (i < 5) {
i++
if (i === 3) continue // i 为 3 时跳过累加
n += i
console.log(n) // 1 3 7 12
}// 不带 label 的 break:只跳出当前(内层)循环
let num = 0
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) break // 只跳出 j 循环,i 循环继续
num++
}
}
console.log(num) // 95
// break label:跳出 label 标识的整个(外层)循环
num = 0
outerBreak: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) break outerBreak // 跳出所有循环
num++
}
}
console.log(num) // 55
// continue label:结束内层本轮,跳到 label 标识的循环继续下一次迭代
num = 0
outerContinue: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) continue outerContinue
num++
}
}
console.log(num) // 95 —— 结果与不带 label 的 break 相同,但多层循环时流程更明晰for (variable in object):遍历对象所有可枚举属性(键),为每个不同的属性执行一次语句。for...in 迭代数组:它返回的除了数字索引,还可能有自定义属性名;迭代数组应使用带数字索引的传统 for 循环、for...of 或 forEach()。for (variable of object):在可迭代对象(Array、Map、Set、arguments 等)上循环,迭代的是值。for...in 遍历键(索引/属性名),for...of 遍历值(元素本身)。const arr = [3, 5, 7]
arr.foo = 'hello' // 给数组添加一个自定义属性
// for...in 遍历“键”:除数字索引外,自定义属性也会被遍历到
for (const i in arr) {
console.log(i) // 0 1 2 foo
}
// for...of 遍历“值”:只迭代可迭代对象的元素,没有 foo
for (const v of arr) {
console.log(v) // 3 5 7
}function 关键字 + 函数名 + 参数列表 + 花括号包裹的函数体;return 语句指定返回值。map(f, a))。Function 构造函数从字符串创建函数(类似 eval())。// 参数本质上是“按值”传递:函数内给参数赋新值,不影响外部
function reassign(n) {
n = 100
}
let num = 1
reassign(num)
console.log(num) // 1
// 但传对象/数组时,函数内修改“属性/元素”对外部可见
function upgrade(theObject) {
theObject.make = 'Toyota'
}
const mycar = { make: 'Honda', model: 'Accord', year: 1998 }
upgrade(mycar)
console.log(mycar.make) // Toyota
function replaceFirst(theArr) {
theArr[0] = 30
}
const arr = [45]
replaceFirst(arr)
console.log(arr[0]) // 30// 函数声明:整体提升,声明之前也能调用
console.log(square(5)) // 25
function square(n) {
return n * n
}
// 函数表达式:不提升,声明前访问会抛错
try {
cube(3)
} catch (e) {
console.log(`${e.name}: ${e.message}`) // ReferenceError: Cannot access 'cube' before initialization
}
const cube = (x) => x * x * x
// 具名函数表达式:名字只在函数内部可用,用于自指(递归)与调试堆栈识别
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1)
}
console.log(factorial(3)) // 6ReferenceError)。call() 和 apply() 可用于动态调用、指定运行时上下文。// 递归函数:调用自身,需要终止条件(避免无限递归)
function factorial(n) {
return n === 0 || n === 1 ? 1 : n * factorial(n - 1)
}
console.log(factorial(5)) // 120
// 递归本身就使用了栈(函数栈):先进后出
function foo(i) {
if (i < 0) return
console.log(`开始:${i}`)
foo(i - 1)
console.log(`结束:${i}`)
}
foo(3)
// 开始:3 开始:2 开始:1 开始:0
// 结束:0 结束:1 结束:2 结束:3// 嵌套(内部)函数形成闭包:可以“继承”容器函数的参数和变量
function outside(x) {
function inside(y) {
return x + y
}
return inside
}
const fnInside = outside(3) // 一个“把传入值加 3”的函数——x 被闭包保存了下来
console.log(fnInside(5)) // 8
console.log(outside(3)(5)) // 8
// 命名冲突:更近的作用域优先(作用域链)
function outer() {
const x = 5
function inner(x) {
return x * 2 // inner 的参数 x 遮蔽了 outer 的 x
}
return inner
}
console.log(outer()(10)) // 20(而不是 10)// 闭包封装:内部变量只能通过暴露的方法访问,像“保险柜”
const createPet = function (name) {
let sex
return {
setName(newName) {
name = newName
},
getName() {
return name
},
setSex(newSex) {
if (typeof newSex === 'string' && ['male', 'female'].includes(newSex.toLowerCase())) {
sex = newSex
}
},
getSex() {
return sex
},
}
}
const pet = createPet('Vivie')
console.log(pet.getName()) // Vivie
pet.setName('Oliver')
pet.setSex('male')
console.log(pet.getSex()) // male
console.log(pet.getName()) // Oliver
// IIFE + 闭包:保护不希望外部修改的数据
const getCode = (function () {
const apiCode = '0]Eal(eh&2'
return function () {
return apiCode
}
})()
console.log(getCode()) // 0]Eal(eh&2arguments 对象中:arguments[i] 按序访问、arguments.length 是实参个数——但它不具备数组的操作方法。undefined;function multiply(a, b = 1) 在函数头直接设定默认值,替代函数体内的 typeof b !== "undefined" 手动检查。...theArgs 把不定数量的参数收集成真正的数组(可直接用 map 等数组方法)。// arguments 是“类数组”:有索引和 length,但没有数组方法
function myConcat(separator) {
let result = ''
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator
}
return result
}
// 可以传入比声明更多的参数
console.log(myConcat('、', '红', '橙', '蓝')) // 红、橙、蓝、
console.log(myConcat(';', '大象', '长颈鹿', '狮子')) // 大象;长颈鹿;狮子;// 过去:在函数体内手动检查 undefined
function multiplyOld(a, b) {
b = typeof b !== 'undefined' ? b : 1
return a * b
}
console.log(multiplyOld(5)) // 5
// 默认参数:直接在函数头设定
function multiply(a, b = 1) {
return a * b
}
console.log(multiply(5)) // 5
// 剩余参数:把不定数量的参数收集成真正的数组
function multiplyAll(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x)
}
console.log(multiplyAll(2, 1, 2, 3)) // [ 2, 4, 6 ]this、arguments、super 和 new.target;箭头函数总是匿名的。a.map((s) => s.length))和 this 的无绑定性。this 值(构造函数中是新对象、严格模式下是 undefined、对象方法中指向该对象……),回调里经常拿不到预期的 this——过去要靠 const self = this; 或 bind() 修复。this 值,回调场景不再需要 self/bind 补丁。const person = {
name: 'Alice',
friends: ['Bob', 'Carol'],
printRegular() {
this.friends.forEach(function (f) {
// 普通函数有“自己的 this”(这里不指向 person),拿不到 name
console.log(`${this?.name} 的朋友:${f}`)
})
},
printArrow() {
this.friends.forEach((f) => {
// 箭头函数没有自己的 this,沿用封闭执行上下文(printArrow)的 this
console.log(`${this.name} 的朋友:${f}`)
})
},
}
person.printRegular() // undefined 的朋友:Bob / Carol
person.printArrow() // Alice 的朋友:Bob / CarolJavaScript 的顶级内置函数:
| 函数 | 作用 |
|---|---|
eval() | 执行以字符串表示的 JavaScript 代码 |
isFinite() | 判断传入值是否是有限数值(必要时先转为数值) |
isNaN() | 判断一个值是否是 NaN(内部强制转换规则需注意,推荐用 Number.isNaN()) |
parseFloat() | 解析字符串参数,返回浮点数 |
parseInt() | 解析字符串参数,返回指定基数的整数 |
encodeURI() / decodeURI() | 对 URI 进行编码 / 解码 |
encodeURIComponent() / decodeURIComponent() | 对 URI 组件进行编码 / 解码 |
escape() / unescape() | 已弃用,用 encodeURI(Component) / decodeURI(Component) 代替 |
Array、TypedArray)。Array 对象处理。new Array(...)、Array(...)、字面量 [...]——字面量更短,通常是首选。Array() 会被解释为 arrayLength 而非元素:Array(42) 是长度 42 的空数组,[42] 才是单元素数组;Array(9.3)(非整数)抛 RangeError;Array.of(9.3) 可安全创建单元素数组。length。length 总是比最大索引大 1(cats[30] = ... → length 31),且可写:写小值截断数组、写 0 清空、写大值拉长(产生空槽)。// Array(数字) 是“长度”,不是元素——单元素数字数组必须用字面量
const a = Array(42)
console.log(a.length, a[0]) // 42 undefined
const b = [42]
console.log(b.length, b[0]) // 1 42
console.log(Array.of(9.3)) // [ 9.3 ] —— Array.of 不会把数字当长度
// length 可写:截断、清空、拉长
const cats = ['Dusty', 'Misty', 'Twiggy']
cats.length = 2
console.log(cats) // [ 'Dusty', 'Misty' ] —— Twiggy 被移除
cats.length = 0
console.log(cats) // []
cats.length = 3
console.log(cats) // [ <3 empty items> ]
// 非整数“索引”不是元素,而是对象属性,不计入 length
const arr = []
arr[3.4] = 'Oranges'
console.log(arr.length, Object.hasOwn(arr, 3.4)) // 0 truepush(尾加)/ pop(尾删)/ shift(头删)/ unshift(头加)/ splice(任意位置删除并替换,原地修改)。slice(提取片段返回新数组)/ at(支持负索引从末尾数)/ indexOf / lastIndexOf。concat(连接)/ join(拼成字符串)/ reverse(原地颠倒)/ flat(展平)/ sort(原地排序,可接受比较函数:负数 a 在前、正数 b 在前、0 保持原序)。const arr = ['1', '2', '3']
console.log(arr.concat('a', 'b')) // 连接并返回新数组
console.log(arr.join(' - ')) // 1 - 2 - 3
arr.push('4') // 尾部添加,返回新 length
console.log(arr.pop()) // 4 —— 移出尾部并返回
console.log(arr.shift()) // 1 —— 移出头部并返回
arr.unshift('0') // 头部添加
console.log(arr) // [ '0', '2', '3' ]
const letters = ['a', 'b', 'c', 'd', 'e']
console.log(letters.slice(1, 4)) // [ 'b', 'c', 'd' ] —— 提取片段,原数组不变
console.log(letters.at(-2)) // d —— 负索引从末尾数
const nums = ['1', '2', '3', '4', '5']
console.log(nums.splice(1, 3, 'a', 'b')) // [ '2', '3', '4' ] —— 返回被删元素,原地修改
console.log(nums) // [ '1', 'a', 'b', '5' ]
console.log([1, 2, [3, 4]].flat()) // [ 1, 2, 3, 4 ] —— 展平嵌套数组
console.log(['Wind', 'Rain', 'Fire'].sort()) // [ 'Fire', 'Rain', 'Wind' ] —— 默认字典序
// sort 接受比较函数(负数 a 在前 / 正数 b 在前 / 0 保持原序):按最后一个字母排序
console.log(['Wind', 'Rain', 'Fire'].sort((a, b) => a.at(-1).localeCompare(b.at(-1)))) // [ 'Wind', 'Fire', 'Rain' ]thisArg(sort() 例外——它不是迭代方法)。map / flatMap(map + 深度 1 的 flat)、filter、find / findLast / findIndex / findLastIndex、every(全部满足)/ some(至少一个)、forEach。reduce / reduceRight:把数组元素两两递归处理成单个值;指定 initialValue 时从它开始累积。const a = ['a', 10, 'b', 20, 'c', 30]
const isNumber = (v) => typeof v === 'number'
console.log(a.filter(isNumber)) // [ 10, 20, 30 ] —— 所有令回调为真的元素
console.log(a.find(isNumber)) // 10 —— 第一个令回调为真的元素
console.log(a.findLast(isNumber)) // 30 —— 最后一个
console.log(a.findIndex(isNumber)) // 1 —— 第一个的索引
console.log(a.findLastIndex(isNumber)) // 5 —— 最后一个的索引
console.log(['a', 'b', 'c'].map((x) => x.toUpperCase())) // [ 'A', 'B', 'C' ]
console.log(['a', 'b'].flatMap((x) => [x, x.toUpperCase()])) // [ 'a', 'A', 'b', 'B' ] —— map + 深度 1 的 flat
console.log([1, 2, 3].every(isNumber)) // true —— 全部满足
console.log([1, '2', 3].every(isNumber)) // false
console.log([1, '2', 3].some(isNumber)) // true —— 至少一个满足
// reduce:把数组两两递归处理成单个值
console.log([10, 20, 30].reduce((acc, cur) => acc + cur, 0)) // 60Array(5)、字面量连续逗号、越界赋值、拉长 length、delete 元素。for...of / 展开把空槽当 undefined;迭代方法(forEach、filter 等)与 Object.keys 跳过空槽;手动赋值 undefined 的元素不会被跳过。exec() / match() 返回的数组带匹配信息)。arguments、NodeList)有 length 和索引但没有数组方法;可用 Array.prototype.forEach.call(arrayLike, ...) 间接调用。const arr = [1, 2, , , 5] // 稀疏数组(含空槽)
// 这些操作把空槽当作 undefined
console.log(arr[2]) // undefined
for (const i of arr) console.log(i) // 1 2 undefined undefined 5
console.log([...arr]) // [ 1, 2, undefined, undefined, 5 ]
// 迭代方法跳过空槽
arr.forEach((i) => console.log(i)) // 1 2 5
console.log(arr.filter(() => true)) // [ 1, 2, 5 ]
console.log(Object.keys(arr)) // [ '0', '1', '4' ]
// 手动赋值 undefined 的元素“不会”被跳过
const dense = ['first', 'second', undefined, 'fourth']
dense.forEach((el) => console.log(el)) // first second undefined fourthMap 是键/值对映射集合,可按插入顺序遍历;核心 API:set / get / has / delete / clear / size,for...of 每次迭代得到 [key, value] 数组。Object 用作映射的优势:键可以是任意类型(Object 的键只能是字符串或 Symbol)、有 size、可直接迭代、无原型带来的默认键干扰。Map;有针对单个元素的逻辑 → 用 Object。const sayings = new Map()
sayings.set('dog', 'woof')
sayings.set('cat', 'meow')
sayings.set('elephant', 'toot')
console.log(sayings.size) // 3
console.log(sayings.get('dog')) // woof
console.log(sayings.get('fox')) // undefined
console.log(sayings.has('bird')) // false
sayings.delete('dog')
for (const [key, value] of sayings) {
console.log(`${key} goes ${value}`) // cat goes meow / elephant goes toot —— 按插入顺序
}
sayings.clear()
console.log(sayings.size) // 0
// 键值相等基于 SameValueZero:NaN 也能当键(与 === 不同),+0 与 -0 视为相等
const m = new Map()
m.set(NaN, '找到我了')
console.log(m.get(NaN)) // 找到我了Set 是一组唯一值的集合,按添加顺序遍历,值只能出现一次;核心 API:add / has / delete / size。Array.from(set) 或 [...set];new Set(array)——转换时重复值自动删除(常用来去重)。has 比 indexOf 快(哈希查找)、删除元素不需要 splice、NaN 也能被正确查找。Map / Set 的键值相等基于 SameValueZero 算法:与 === 基本一致,但 NaN 等于自身、+0 与 -0 相等。const mySet = new Set()
mySet.add(1)
mySet.add('some text')
mySet.add('foo')
console.log(mySet.has(1)) // true
mySet.delete('foo')
console.log(mySet.size) // 2
for (const item of mySet) console.log(item) // 1 / some text —— 按添加顺序
// 数组 ⇆ Set:转换时自动去重
const dedup = new Set([1, 2, 2, 3, 3, 3])
console.log([...dedup]) // [ 1, 2, 3 ] —— 展开语法
console.log(Array.from(dedup)) // [ 1, 2, 3 ] —— Array.fromobj.prop;方括号 obj["prop"](对象因此也叫“关联数组”)。非法标识符属性名(含空格、连字符、数字开头)只能用方括号,方括号也支持运行时动态判定的属性名。undefined(而不是 null)。for...in(自身 + 原型链上的可枚举属性)、Object.keys(o)(自身可枚举)、Object.getOwnPropertyNames(o)(自身全部,无论是否可枚举)。const myCar = {}
myCar.make = 'Ford' // 点访问
myCar['model'] = 'Mustang' // 方括号访问(对象有时被叫作“关联数组”)
myCar.year = 1969
console.log(myCar.noProperty) // undefined —— 未赋值的属性是 undefined(不是 null)
// 非法标识符属性名只能用方括号;方括号里的键都会转成字符串
const myObj = {}
myObj['date created'] = '带空格的属性名'
myObj[''] = '空字符串也可以'
myObj[Math.random()] = '动态键'
console.log(Object.keys(myObj).length) // 3
// 枚举属性的三种原生方法
console.log(Object.keys(myCar)) // [ 'make', 'model', 'year' ] —— 自身可枚举属性
console.log(Object.getOwnPropertyNames(myCar)) // 自身所有属性(无论是否可枚举)
for (const key in myCar) console.log(key) // 自身 + 原型链上的可枚举属性{ prop: value, ... }——每次遇到该表达式都会创建新对象。new:函数名首字母大写是惯例;用 this 把传入值赋给对象属性;属性值可以是另一个对象;通过 Constructor.prototype.x 可为该类型所有对象增加属性。Object.create():直接为新对象指定原型对象,无需定义构造函数。// 方式一:对象初始化器(字面量)
const myHonda = { color: 'red', wheels: 4, engine: { cylinders: 4, size: 2.2 } }
console.log(myHonda.engine.size) // 2.2
// 方式二:构造函数 + new(约定首字母大写)
function Car(make, model, year, owner) {
this.make = make
this.model = model
this.year = year
this.owner = owner
}
const rand = { name: 'Rand McKinnon', age: 33 }
const car2 = new Car('Nissan', '300ZX', 1992, rand)
console.log(car2.owner.name) // Rand McKinnon —— 属性值可以是另一个对象
// 方式三:Object.create —— 直接指定原型对象,无需构造函数
const Animal = {
type: 'Invertebrates',
displayType() {
console.log(this.type)
},
}
const fish = Object.create(Animal)
fish.type = 'Fishes'
fish.displayType() // Fishesget prop() / set prop(x) 定义(getter 无参数、setter 恰一个参数);也可以随时用 Object.defineProperty / Object.defineProperties 给已有对象补加。delete 操作符可删除非继承属性。== / === 都是 false;只有比较同一个对象的引用才为 true。// 对象字面量中定义 getter / setter
const o = {
a: 7,
get b() {
return this.a + 1
},
set c(x) {
this.a = x / 2
},
}
console.log(o.a) // 7
console.log(o.b) // 8 —— getter 像属性一样读取
o.c = 50 // setter 像属性一样赋值
console.log(o.a) // 25
// 也可以随时用 Object.defineProperties 给已有对象补加
const p = { a: 0 }
Object.defineProperties(p, {
b: {
get() {
return this.a + 1
},
},
c: {
set(x) {
this.a = x / 2
},
},
})
p.c = 10
console.log(p.b) // 6// 对象是引用类型:两个独立创建的同形对象永不相等
const fruit = { name: 'apple' }
const fruitbear = { name: 'apple' }
console.log(fruit == fruitbear) // false
console.log(fruit === fruitbear) // false
// 只有比较同一个对象的引用时才为 true
const ref = fruit
console.log(fruit === ref) // true
// delete 可删除非继承属性
delete fruit.name
console.log(fruit) // {}constructor、实例字段、实例方法、静态字段 / 静态方法 / 静态块,以及以 # 开头的私有形式。let / const);类也有表达式形式,表达式的名字只在类体内可见。new,否则 TypeError;构造函数中 this 指向新实例并自动作为 new 的结果返回(不建议手动返回对象——会顶替 this)。class Color {
constructor(r, g, b) {
this.values = [r, g, b]
}
getRed() {
return this.values[0]
}
get red() {
// 访问器字段:像“实际属性”一样读写
return this.values[0]
}
set red(value) {
this.values[0] = value
}
static isValid(r, g, b) {
// 静态方法定义在类本身上,不在实例上
return [r, g, b].every((v) => v >= 0 && v <= 255)
}
}
const red = new Color(255, 0, 0)
console.log(red.getRed()) // 255
red.red = 0
console.log(red.red) // 0
// 实例方法定义在原型(Color.prototype)上,所有实例共享同一个函数
console.log(new Color(0, 0, 0).getRed === new Color(1, 1, 1).getRed) // true
console.log(Color.isValid(255, 0, 0)) // true
console.log(Color.isValid(1000, 0, 0)) // false
console.log(new Color(0, 0, 0).isValid) // undefined —— 实例上没有静态方法
// 类的“调用”必须使用 new
try {
Color(1, 2, 3)
} catch (e) {
console.log(`${e.name}: ${e.message}`) // TypeError: Class constructor Color cannot be invoked without 'new'
}Color.prototype),所有实例共享同一个函数(在构造函数里用 this.fn = ... 则每个实例都新建一份)。get red() / set red(v) 让方法像“实际属性”一样读写;只有 getter 没有 setter 时字段只读(严格模式下赋值抛 TypeError,非严格模式静默忽略)。luckyNumber = ...)几乎等价于在构造函数里给 this 赋属性。new Color().isValid === undefined);静态初始化块在类第一次加载时运行,且可访问静态私有元素。# 开头,必须先在类体中声明才能引用;永远不会与公共字段命名冲突。SyntaxError(静态分析即可发现),没有任何机制能从类外碰到它——放心重构内部结构,只要暴露方法的行为不变。undefined),可用 #field in obj 检查。delete;方法、getter/setter 也可以是私有的。setRed 拒绝 1000)、防止用户依赖内部数据结构(如 RGB → HSL 重构不破坏 API)。class Color {
#values // 私有字段必须先在类体中声明,以 # 开头
constructor(r, g, b) {
this.#values = [r, g, b]
}
getRed() {
return this.#values[0]
}
setRed(value) {
if (value < 0 || value > 255) throw new RangeError('无效的 R 值') // 封装:入口处校验
this.#values[0] = value
}
redDifference(anotherColor) {
if (!(#values in anotherColor)) throw new TypeError('期望 Color 实例') // in 检查私有字段是否存在
return this.#values[0] - anotherColor.#values[0] // 可访问“同类其他实例”的私有字段
}
}
const red = new Color(255, 0, 0)
console.log(red.getRed()) // 255
try {
red.setRed(1000)
} catch (e) {
console.log(`${e.name}: ${e.message}`) // RangeError: 无效的 R 值
}
const crimson = new Color(220, 20, 60)
console.log(red.redDifference(crimson)) // 35
// 类外访问 red.#values 是“早期语法错误”(SyntaxError),代码根本无法运行extends 声明,可访问父类所有公共属性;访问 this 之前必须先调用 super()(它调用父类构造函数初始化 this)。toString())并用 super.method() 在避免重复的前提下增强父类逻辑。color instanceof Color 与 color instanceof ColorWithAlpha 都为 true。Map / Date / Error 家族);注意可变性与深层继承树带来的维护成本。class Color {
#values
constructor(r, g, b) {
this.#values = [r, g, b]
}
get red() {
return this.#values[0]
}
toString() {
return this.#values.join(', ')
}
static isValid(r, g, b) {
return [r, g, b].every((v) => v >= 0 && v <= 255)
}
}
class ColorWithAlpha extends Color {
#alpha
constructor(r, g, b, a) {
super(r, g, b) // 访问 this 之前必须先调用 super()
this.#alpha = a
}
toString() {
return `${super.toString()}, ${this.#alpha}` // super 调用父类方法:增强而不重复
}
static isValid(r, g, b, a) {
return super.isValid(r, g, b) && a >= 0 && a <= 1 // 静态方法同样被继承
}
}
const color = new ColorWithAlpha(255, 0, 0, 0.5)
console.log(color.red) // 255 —— 继承了父类的访问器
console.log(color.toString()) // 255, 0, 0, 0.5 —— 覆盖并增强了 toString
console.log(ColorWithAlpha.isValid(255, 0, 0, -1)) // false
console.log(color instanceof Color) // true —— 派生类实例同时也是父类实例
console.log(color instanceof ColorWithAlpha) // truePromise 代表一个异步操作的最终完成或失败;回调函数是附加到 Promise 上的(.then()),而不是作为参数传入函数——这解决了回调地狱。then() 返回一个新的 Promise:回调返回普通值则传给下一个 then;返回 Promise 则等待其兑现后把兑现值传下去。then 回调中 return 你启动的 Promise:忘记 return 会产生“漂浮”的 Promise——无法追踪其敲定状态,有竞态时下一个 then 会提前调用、读到不完整的值。catch(cb) 等同于 then(null, cb),错误处理统一放链尾。async / await 让代码几乎与同步版本一样(并发语义与 Promise 链相同:await 只挂起依赖其值的部分)。// 把 setTimeout 封装进 Promise:旧式回调 API 的标准包装法
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
// then() 返回一个“新的 Promise”,因此可以链式衔接;catch 统一收尾
wait(10, 1)
.then((result) => result * 2) // 返回普通值:传给下一个 then
.then((result) => wait(10, result + 1)) // 返回 Promise:等待其兑现后再继续(务必 return!)
.then((result) => {
console.log(`得到最终结果:${result}`) // 3
// 在 catch 之后还能继续链式操作
return Promise.resolve()
.then(() => {
throw new Error('有哪里不对了')
console.log('执行「这个」') // 不可达 —— throw 导致拒绝
})
.catch(() => console.log('执行「那个」'))
.then(() => console.log('执行「这个」,无论前面发生了什么'))
})
.catch((e) => console.error(e))const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
// async/await:与同步代码几乎一样的写法,错误处理回归 try/catch
async function main() {
try {
const a = await wait(10, 1)
const b = await wait(10, a + 1)
console.log(`得到最终结果:${b}`) // 2
throw new Error('有哪里不对了')
} catch (e) {
console.log(`执行「那个」:${e.message}`)
}
console.log('执行「这个」,无论前面发生了什么')
}
main()
// await 挂起时不会停止整个程序,其他异步任务仍可运行.catch()(或 onRejected)——工作方式与同步代码的 try...catch 高度对称;async / await 下直接用 try...catch。catch 的作用域:内层 catch 只捕获其作用域内的失败(可选步骤失败不影响主链继续),外层错误不会被内层吞掉——细粒度错误恢复。catch 之后可以继续链式操作(失败恢复后接着做)。unhandledrejection / rejectionhandled 全局事件;Node.js 用 process.on("unhandledRejection", ...) 捕获。Promise.all:全部兑现才兑现,任何一个拒绝立即拒绝。Promise.allSettled:等所有 Promise 敲定(无论成败),逐个给出状态。Promise.any:第一个兑现的获胜。Promise.race:第一个敲定(无论成败)的获胜。reduce 把函数数组折叠成 Promise 链,或 for...of + await;但除非确有依赖,优先并发。new Promise(executor) 从零创建 Promise 只应在封装旧式回调 API 时使用(如把 setTimeout 包成 wait(ms)),避免同步/异步混叫的“Zalgo 状态”。then() 的函数永远不会被同步调用(即使 Promise 已解决)——Promise 回调进微任务队列,setTimeout 回调进任务队列,微任务先于下一个任务执行。const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms))
const fail = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms))
// Promise.all:并发启动,全部兑现才兑现;任何一个拒绝则立即拒绝
Promise.all([wait(10, 1), wait(20, 2), wait(5, 3)]).then(([r1, r2, r3]) => {
console.log('all:', r1, r2, r3) // all: 1 2 3
})
// Promise.allSettled:等所有 Promise 敲定(无论成败),逐个给出状态
Promise.allSettled([wait(10, 'ok'), fail(10, 'boom')]).then((results) => {
console.log('allSettled:', results.map((r) => r.status)) // [ 'fulfilled', 'rejected' ]
})
// Promise.race:第一个敲定的获胜
Promise.race([wait(50, '慢'), wait(10, '快')]).then((winner) => {
console.log('race:', winner) // race: 快
})// Promise 回调是“微任务”,setTimeout 回调是“任务”——微任务先于下一个任务执行
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
wait().then(() => console.log(4)) // 任务(setTimeout)之后的微任务,最后执行
Promise.resolve()
.then(() => console.log(2)) // 微任务:当前同步代码退出后立刻执行
.then(() => console.log(3))
console.log(1) // 同步代码最先执行
// 输出顺序:1 2 3 4 —— 传给 then() 的函数“永远不会被同步调用”,即使 Promise 已经解决next() 方法返回 { value, done }——value 是序列下一个值,done 为 true 时迭代结束(此时的 value 是迭代器的返回值)。next() 应持续返回 { done: true }。// 迭代器协议:实现 next() 方法,返回 { value, done }
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start
let iterationCount = 0
return {
next() {
if (nextIndex < end) {
const result = { value: nextIndex, done: false }
nextIndex += step
iterationCount++
return result
}
return { value: iterationCount, done: true } // done 时的 value 是迭代器的“返回值”
},
}
}
const it = makeRangeIterator(1, 10, 2)
let result = it.next()
while (!result.done) {
console.log(result.value) // 1 3 5 7 9
result = it.next()
}
console.log(`已迭代序列的大小:${result.value}`) // 5
// 迭代器按需分配,可以表示无限序列(数组则必须完整分配)function*)是自定义迭代器的强大替代:无需显式维护内部状态;初次调用不执行代码而是返回生成器,每次 next() 执行到下一个 yield。[Symbol.iterator]() 方法的对象;String / Array / TypedArray / Map / Set 是内置可迭代对象。for...of、展开语法、yield*、解构。[Symbol.iterator]() 返回自身 → 只能迭代一次;可多次迭代的对象每次调用须返回新迭代器。// 生成器函数:function* + yield,无需手动维护内部状态
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0
for (let i = start; i < end; i += step) {
iterationCount++
yield i
}
return iterationCount
}
console.log([...makeRangeIterator(1, 10, 2)]) // [ 1, 3, 5, 7, 9 ]
// 自定义可迭代对象:实现 [Symbol.iterator]() 方法
const myIterable = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
},
}
for (const value of myIterable) console.log(value) // 1 2 3
console.log([...myIterable]) // [ 1, 2, 3 ] —— for...of、展开、解构都适用于可迭代对象
// 生成器既是迭代器也是可迭代对象,但只能迭代一次
const gen = makeRangeIterator(0, 3)
console.log(gen[Symbol.iterator]() === gen) // true —— 返回自身
console.log([...gen]) // [ 0, 1, 2 ]
console.log([...gen]) // [] —— 已被消耗yield 的值,可高效表示计算昂贵的序列甚至无限序列。next(x) 的参数会成为 yield 表达式的值——外部可以向生成器注入状态(传给第一个 next() 的值会被忽略)。throw() 强制生成器从当前挂起的 yield 处抛出异常;return(value) 返回给定值并终结生成器。// next(x) 的参数会成为 yield 表达式的值——可以与生成器“对话”
function* fibonacci() {
let current = 0
let next = 1
while (true) {
const reset = yield current
;[current, next] = [next, next + current]
if (reset) {
current = 0
next = 1
}
}
}
const sequence = fibonacci()
console.log(sequence.next().value) // 0
console.log(sequence.next().value) // 1
console.log(sequence.next().value) // 1
console.log(sequence.next().value) // 2
console.log(sequence.next().value) // 3
console.log(sequence.next(true).value) // 0 —— 传入 true 重启序列(传给第一个 next 的值会被忽略)
console.log(sequence.next().value) // 1
// throw() / return() 可从外部控制生成器
const g = fibonacci()
g.next()
console.log(g.return(42)) // { value: 42, done: true } —— 返回给定值并终结生成器