JavaScript 语法概要
词法结构
区分大小写、注释、标识符、保留字、支持 Unicode、代码换行可不加分号。
类型、值、变量
类型分类
原始类型(不可修改):number、string、boolean、symbol、null、undefined、bigint ;
对象类型(可修改):object 。
数值类型
使用 IEEE 754 标准 定义的 64 位双精度浮点型表示数值,可以准确表示 [-253, 253] 区间所有整数。
可以用数字分隔符 _
来进行数字的位数分组,提高可读性。
数值操作中出现上溢出会得到 ±Infinity ;下溢出会得到 ±0 ;零除以零会得到非数值 NaN 。
BigInt 数值类型来表示任意精度整数,字面量以 n 结尾( Math 对象不支持大数值)。
字符串
字符串是无符号 16 位值的不可修改的有序序列,每个值表示一个 Unicode 字符。
常见的 Unicode 字符的码点为 16 位。若超过 16 位,则用两个 16 位值表示,称为代理对。
ES6 中字符串是可迭代的,迭代的是字符而不是 16 位值。
用 '
或 "
包裹字符串,ES6 可以用反引号 `
(通过 ${表达式}
进行插值,支持多行)。
需要用 \
转义引号和其他一些字符。
字符串方法
获取子串:
substring()
slice()
split()
搜索:
indexOf()
lastIndexOf()
【ES6】startsWith()
endsWith()
includes()
访问字符:
charAt()
charCodeAt()
【ES6】codePointAt()
匹配:
match()
matchAll()
search()
replace()
replaceAll()
大小写:
toLowerCase()
toUpperCase()
归一化:【ES6】
normalize()
填充:【ES2017】
padStart()
padEnd()
去除空格:
trim()
【ES2019】trimStart()
trimEnd()
拼接:
concat()
【ES6】repeat()
布尔值
undefined
、null
、±0 、NaN、""
都会转为 false
,其余的值会转为 true
。
null & undefined
都表示空值。两者相等,但不严格相等。typeof null
为 "object" 。
符号类型
创建符号需要调用 Symbol()
函数,即使是相同的实参也永远不会返回相同的值。
可以用 Symbol.for()
进行全局符号的查询,行为类似字典,用于与别的代码共享符号值。
全局对象
JS 解释器启动后,都会创建一个新的全局对象,并为其设置一些初始属性及函数方法。
Node 的全局对象为 global
;浏览器为 window
。ES2020 定义了 globalThis
作为全局对象引用。
原始值 & 对象引用
原始值不可修改。比较两值时,原始值会纯粹比较值,而对象值会比较引用的是否为同一对象。
隐式类型转换
JS 会尝试将值转为想要的类型。相等 ==
会先尝试类型转换,而严格相等 ===
不会。
对于数组,JS 会调用其 join()
方法来转为字符串,通常用 ,
进行拼接。
显式类型转换
Boolean()
Number()
parseInt()
parseFloat()
String()
等函数。或手动触发隐式转换。
对象转换
对象转布尔值为
true
;对象转字符串,先
toString()
后valueOf()
(会期盼这些方法返回原始值,下同);对象转数值,先
valueOf()
后toString()
;无类型指向,则先转为数值(除了 Date 会先转为字符串)。
一般操作符,如 +
==
!=
等会先对对象进行无偏好转换。而大小于号会进行偏数值转换。
变量声明
var(函数作用域、声明提升、可以多次声明)、let 、const(常量,必须赋初值)
解构赋值
ES6 中左值可以模拟数组(或可迭代对象)或对象字面量来获取右值中对应的值。
支持嵌套、用 ,
跳跃数组元素。多余的变量会赋值为 undefined
。可以提供默认值。
表达式 & 操作符
表达式类型
主表达式、数组 & 对象字面量;
函数表达式(箭头函数)、属性访问(点语法、中括号)、函数/方法调用;
可选链
?.
、创建对象( new 操作符)。
操作符
操作符可以用于算术、比较、逻辑、赋值等表达式中,也有用 delete、instanceof 等关键字表示。
操作数分一元、二元、三元。?:
是唯一的三元操作符。
一元操作符的优先级最高,赋值操作符优先级很低。可以用圆括号确保优先级。
四则运算均为左结合性(从左往右),而幂、一元、赋值、三元操作符均为右结合性。
基本算术操作符
+
-
*
/
%
以及 ES2016 新增的 **
。幂操作会优先于乘、除、取模操作。
对于取模运算,求得的余数的符号与被除数一致(包括 -0 ),浮点数也可以取模。
+ 操作符的行为
如果存在操作数为对象,则先根据无偏好算法转为原始值;
将对象转为原始值后,如果存在操作数为字符串,则将另一个操作数也转为字符串,然后进行拼接;
否则,两个操作数都被转为数值,进行加法计算。
一元算数操作符
+
会将操作数转为数值,然后返回(如果原本是数值则什么都不做。不能用在大数值);-
先将操作数转为数值,然后更改符号。++
--
的操作数应当是个变量,返回值取决于操作符相对于变量的位置。
位操作符
&
|
^
~
<<
>>
>>>
(无符号右移)。除了 >>>
,其余可用在大数值上。
相等 & 严格相等
===
用于严格比较两个操作数是否完全相同,而 ==
会先尝试类型转换再进行比较。
一个对象只与自己严格相等,与其他任何对象都不相等,因为每一个对象都有独一无二的内存地址。
null
和 undefined
相等,但不严格相等。NaN
与任何值不等,包括自身。0 和 -0 严格相等。
两个字符串应当包含完全相同的 16 位值,否则即便看起来完全相同,也会判为不等。
比较操作符的转换规则
如果有操作数为对象,则先采用无偏好算法转为原始值;
字符串之间的比较会根据字母表顺序,即 16 位 Unicode 值的数值顺序;
如果存在一个操作数不是字符串,则会将两个操作数转为数值再进行比较。
其它关系操作符
in(判断左操作数是否为右操作数的一个属性);
instanceof(判断原型,左操作数如果为原始值返回
false
,右操作数如果不是对象抛出异常)。
逻辑操作符
&&
的左操作数是假值时,不会对右操作数求值。若左操作数为真值,整个表达式的值为右操作数。
||
会返回第一个为真值的操作数,并不再处理后面的操作数;若一个都没有,则返回最后一个操作数。
!
会对操作数取反,如操作数不是布尔值,会先转换为布尔值。可用 !!
直接转为布尔值。
赋值表达式
=
的左操作数应当是个左值(即变量标识符)。支持解构赋值。
算术及位操作符支持简写,如 +=
-=
等。
求值表达式
可将一个表达式字符串传给全局函数 eval()
来求得表达式的值。它拥有自己的作用域。
如果 eval()
以别名身份调用,则应当将表达式字符串当作全局代码来执行。
其他一些操作符
typeof 类型检查,"undefined" "boolean" "number" "bigint" "string" "symbol" "function" "object" 。
delete 删除变量/属性。删除失败返回 false
,严格模式只能作用于属性访问表达式。
?:
(三目运算符)、??
(可选符号)、await(异步)、void(丢弃返回值)。
逗号 ,
操作符会返回右操作数,且为左结合性。
语句
表达式语句
表达式语句为带副作用的表达式,比如赋值、函数调用等。
复合语句 & 空语句
可用一对 {}
表示语句块存放多个语句;或者只用 ;
表示空语句。
条件语句
if 语句、switch 语句(使用严格相等、default 标签可选)。
循环语句
while 、do-while 、for 、for-of(可迭代对象)、for-in(枚举对象)、for-await(异步迭代)。
跳转语句
语句标签、break 、continue 、return 、yield(生成器)、throw 、try-catch-finally 。
其他语句
with(作用域)、debugger(断点)、"use strict"(严格模式)、声明语句。
对象
四个特性
value、writable、enumerable、configurable 。
对象使用
创建对象:字面量、
new
操作符、Object.create()
。属性访问:点语法、中括号。
测试属性:
undefined
判断、in
操作符、Reflect.has()
。扩展属性:ES6 的
Object.assign(target, ...sources)
。
属性枚举
在用 for-in 进行枚举时,从原型继承的属性也会被枚举,但方法不会枚举。
获取属性名数组
Object.keys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Reflect.ownKeys()
属性枚举顺序
非负整数(即索引,升序枚举);
字符串(按添加顺序枚举,非字典序);
符号属性。
JSON 合法值
对象、数组、字符串、有限数值、
true
、false
、null
;NaN 、±Infinity 会被转为
null
;日期对象会调用
toJSON()
方法转为 ISO 格式的日期字符串;函数、RegExp 对象、Error 对象以及
undefined
无法序列化,会被直接删除。
更多语法
【ES6】属性简写、计算属性名(中括号)、符号属性、方法简写、访问器属性( get 、set );
【ES2018】对象展开(
...
扩展操作符)。
数组
数组长度
数组最大长度 232-1,即合法索引区间为 [0, 232-2] 。
创建数组
字面量(可用展开语法)、new Array()
( new 可不加)、Array.of()
、Array.from()
。
添加删除元素
push()
pop()
shift()
unshift()
splice()
手动指定 length
值,则会对数组进行末尾留空或删除元素。
遍历数组
for-of 语句会遍历数组中空值,而 forEach()
等迭代方法不会。
迭代方法
forEach()
map()
filter()
every()
some()
reduce()
reduceRight()
(空数组且无初始值报错,两者合并仍只有一个元素则直接返回)find()
(找不到返回undefined
)findIndex()
(找不到返回 -1 )
查找方法
除了 includes()
( NaN 与自身相等),其他方法均使用严格相等。
indexOf()
(找不到返回 -1 ,后同)lastIndexOf()
includes()
构建新数组
这些方法中的位置参数均可使用负数表示倒数。
slice()
concat()
(一级扁平化)fill()
copyWithin()
元素顺序
reverse()
sort()
(字母序,正数、0、负数)。这两个方法均会修改数组自身。
其他方法
扁平化:
flat()
flatMap()
(类似arr.map(fn).flat()
)键值对:
keys()
values()
entries()
转为字符串:
join()
类方法:
Array.isArray()
Array.from()
Array.of()
类数组对象
一个对象有一个数值属性 length
,且有相应的(可以不完全)非负整数属性,则可以视为类数组。
字符串
字符串的行为类似 UTF-16 Unicode 字符的只读数组,除了用 charAt()
方法,也可以用方括号语法。
函数
定义函数
函数声明、函数表达式、ES6 中的箭头函数。
还可以用 Function()
构造函数定义新函数。还有一些特殊函数,比如生成器函数、异步函数。
调用上下文
非严格模式下的函数调用,调用上下文 this
值是全局对象;严格模式下是 undefined
。方法调用时,对象会成为调用上下文。箭头函数会从定义自己的环境中继承 this
值,且没有 prototype
属性。
JS 运行的时候会有个调用栈,应当注意递归函数的调用次数。
构造函数
调用构造函数会创建一个新的空对象,这个对象继承构造函数的 prototype
属性。同时,这个对象会被用作函数的调用上下文(即便构造函数为对象方法)。
如果使用 return 返回了一个对象,则会返回这个对象;否则会隐式返回创建的新对象。
箭头函数没有 prototype
属性,不能作为构造函数调用。
函数参数
可选参数应当放在参数列表的末尾;
ES6 可以为形参设置默认值,通过
...
定义剩余参数(必须为最后一个参数);可以使用展开(
...
操作符)、解构语法。ES2018 支持剩余参数解构对象。
函数作为命名空间
在函数体内声明的变量在函数外部不可见。可以把函数用作临时的命名空间,不污染全局命名空间。
作用域 & 闭包
函数执行时使用的是定义函数时生效的变量作用域,而非调用函数时生效的变量作用域。JS 函数对象的内部状态除了包括函数代码,还要包括对函数定义所在作用域的引用。
函数对象与作用域组合起来解析函数变量的机制,称为闭包。
函数调用都会创建一个新的闭包,闭包之间的内部变量互不共享(可称为私有变量)。
ES6 引入了块级作用域。一对 {}
为一个块级作用域。let 和 const 声明的变量会限制在块级作用域里。
函数属性 & 方法
length
(声明的形参个数)、name
(定义函数时的名字)、prototype
(匿名函数没有原型)。
call(thisArg, arg1, arg2, ...)
& apply(thisArg, args)
间接调用函数。
bind(thisArg, arg1, arg2, ...)
返回一个新函数,其会绑定指定的 this
值。
toString()
返回一个表示当前函数源代码的字符串。
Funtion() 构造函数
Function()
允许在运行时动态创建编译 JS 函数。它的前几个参数为形参列表,最后一个参数为函数体代码字符串(无法指定函数名,因此也为匿名函数)。
高阶函数
高阶函数即操作函数的函数,它接受函数作为参数,或者返回一个新函数。
类
原型 & 构造函数
用 Object.create()
来根据一个原型创建对象。或者定义一个构造函数。
在函数里可以使用 new.target
来判断函数是否以构造函数方式调用。
和函数调用不同,class 定义的类必须使用 new 操作符来调用它们的构造函数。
instanceof 操作符实际是比较左操作数是否继承了右操作数的原型对象。也可以用对象的 isPrototypeOf()
方法判断。
构造函数的 constructor
属性不可枚举。
定义类
用 class 声明语句、表达式。用 extends 关键字继承另一个类。
如果一个类不需要初始化属性,则可以省略 constructor
,JS 会自动创建一个空构造函数。
方法 & 字段
静态方法( static 修饰)、get & set 、内部方法( "_" 前缀,非标准)。
公有字段(赋值语句)、私有字段( "#" 前缀)、静态字段( static )。
子类
设置子类的原型对象为父类(旧)、使用 extends 定义(新)。
通过 extends 创建子类,B 原型(实例)和 B 类自身(静态)都会从 A 中继承。
用 super 关键字来使用父类。子类构造函数中必须调用父类构造函数。
委托(组合)
除了继承,可以将要继承的实例作为当前类的一个属性,并在需要时委托这个实例干事。
抽象方法
可以在方法中抛出异常来达到抽象方法的效果。
模块
模块化的作用
模块化的作用主要体现在封装、隐藏私有实现细节、保证全局命名空间清洁。
Node 中的模块
Node 定义了一个全局 exports
对象,它是 module.exports
的引用。
通过 require("文件路径")
导入其他模块导出的对象(文件名可忽略 ".js" 后缀)。
ES6 中的模块
ES6 模块自动应用严格模式。导入导出代码只能出现在顶层代码。
导出内容可以是声明语句、大括号、默认导出( default )。
导入有提升效果。导入方式可以为大括号、标识符接收默认导出、*
接收所有常规导出。
导入导出均可重命名。支持来源文件的再导出 export ... from "file.js"
。
启用模块
浏览器在脚本标签中添加模块属性
<script type="module">
;Node 在 package.json 里添加
type: "module"
属性。
动态导入模块
ES2020 引入了 import()
来动态加载模块。它返回一个 Promise 对象。
迭代器 & 生成器
迭代器对象
指拥有 next()
方法的对象,该方法返回下一次的迭代结果对象(具有 value
或 done
属性)。
可以定义 return()
方法,来执行一些清理工作。
可迭代对象
指有 Symbol.iterator
方法的对象,该方法应当返回一个迭代器对象。
生成器
生成器自身既是可迭代对象,又是迭代器对象。
定义生成器
生成器使用 function*
声明、方法名前加 *
。
yield 关键字只能在生成器函数中使用。可以用 yield*
来委托迭代可迭代对象(包括生成器)。
可以有 return 语句,其对应的迭代结果为:value
为返回值,done
为 true
。
yield 表达式的值
生成器的 next()
方法时可以传入参数,它会成为上次暂停的 yield 表达式的值。
生成器方法
next()
、return()
(迭代终止)、throw()
(抛出异常,生成器中可以用 try-catch-finally 语句)。
异步
异步语法
ES6 的 Promise 对象、ES2017 的 async/await 关键字、ES2018 的 for-await 异步迭代。
使用回调的异步编程
定时器、事件监听、网络事件。
Promise 特征
两个优点:链式调用、标准化异常处理。
三种状态:pending
fulfilled
rejected
。
构造 Promise
new Promise()
Promise.resolve(value)
Promise.reject(reason)
。
Promise 方法
三个方法:then()
catch()
【ES2018】finally()
。
then()
方法类似事件注册,可以多次调用。传入 then()
方法的回调会被异步执行。
并行多个 Promise
Promise.all()
需要全被兑现才为兑现;Promise.allSettled()
全部落定后兑现(数组元素属性status
、value
、reason
);Promise.race()
返回第一个落定的。
串行多个 Promise
需要手动通过 then()
连接。
async & await
await 关键字接收一个 Promise 对象并将其转为一个返回值或一个异常。
只能在 async 关键字声明的函数(箭头函数、类方法都可用)内部使用 await 关键字。
async 函数会返回一个 Promise 对象。
for-await 循环
for-await 的循环体会先等待循环头中 Promise 的兑现,才去执行。
异步迭代器与常规迭代器区别
使用
Symbol.asyncIterator
代替Symbol.iterator
;next()
应当返回一个兑现结果为迭代结果的 Promise 对象。
异步生成器
通过 async function*
来生成一个异步生成器。
元编程
属性的特性
数据属性的 4 个特性:value、writable、enumerable、configurable 。
访问器属性的 4 个特性:get、set、enumerable、configurable 。
属性描述符
用
Object.getOwnPropertyDescriptor()
获取(自有)属性的属性描述符;用
Object.defineProperty()
方法来创建或修改属性,或Object.defineProperties()
。
对象的可扩展能力
Object.isExtensible()
、Object.preventExtensions()
不能添加属性 ;Object.isSealed()
、Object.seal()
不能添加删除属性;Object.isFrozen()
、Object.freeze()
只读。
原型特性
一般对象会用
Object.prototype
作为原型;用 new 创建的对象会用构造函数的原型作为原型;
使用
Object.create()
创建的对象则会以传入的第一个参数作为原型。
查询原型
Object.getPrototypeOf()
、Object 的 isPrototypeOf()
方法、instanceof 操作符。
公认符号
Symbol.iterator
&Symbol.asyncIterator
让对象或类把自己变为可迭代或异步可迭代对象;Symbol.hasInstance
使用 instanceof 前先检查该符号方法;Symbol.toStringTag
调用Object.prototype.toString()
前会先查找该符号属性;Symbol.toPrimitive
覆盖对象转换行为( "string"、"number"、"default" );Symbol.isConcatSpreadable
在 Array 的concat()
中是否展开;Symbol.unscopables
根据该符号属性判断对象各属性是否放入 with 语句的作用域中。
模板标签函数
第一个参数为模板字面量中被插值分割的字符串,其余参数则为插值。返回值不限于为字符串。
Reflect API
Reflect 对象的方法可以模拟一些核心语法的行为。与 Proxy 对象中的处理器对象的方法是一一对应的。
代理对象作用
拦截 JS 对象的基础行为。
创建代理对象
new Proxy()
第一个参数为目标对象,第二个为处理器对象( Reflect 上的方法 );Proxy.recovable()
创建代理,它返回一个对象,包含一个代理对象和一个撤销代理的函数。
代理不变式
代理对象所表现的特性是可以与目标对象不一致的,但应当合理。