Babel
- ES6转码器,可将ES6转为ES5代码;
- 配置文件:设置转码规则和插件;
- 命令行工具,进行命令行转码;
- babel-node命令,提供一个支持 ES6 的 REPL 环境(Read-Eval-Print Loop,读入-求值-输出循环,是一种交互式编程环境);
- @babel/register使 require加载.js、.jsx、.es和.es6后缀名的文件,就会先用 Babel 进行转码;
- Polyfill:解决Babel不转换新API
- 浏览器环境:ES6实时转为ES5,对性能有影响,生产环境需要加载已经转码完成的脚本
let
- 块级作用域
- 不存在变量提升
- 暂时性死区(封闭作用域,let声明变量之前该变量不可用)
- 相同作用域内,不允许重复声明
块级作用域
- 需有大括号才有块级作用域
- 其他环境:在块级作用域之中声明函数,在块级作用域之外不可引用
- ES6浏览器:函数声明类似于var,即会提升到全局作用域或函数作用域的头部。同时,函数声明还会提升到所在的块级作用域的头部。
const
- 声明变量为只读,值不可改
- 块级作用域
- 不允许变量提升
- 暂时性死区
- 不可重复声明
- 本质:变量指向的那个内存地址所保存的数据不得改动
- 简单类型:值保存在变量指向的内存地址,等同于常量
- 复杂类型:内存地址保存的是指向实际数据的指针(地址不可变)
- Object.freeze方法可将对象冻结,变成不可改
顶层对象属性
- 浏览器环境:window对象
- Node:global对象
- var和function声明的全局变量 = 顶层对象的属性
- let、const、class声明的全局变量 ≠ 顶层对象属性
globalThis对象
- 任何环境下,都可以从globalThis拿到顶层对象,指向全局环境下的this
数组形式解构赋值
-
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值(Iterator接口返回的对象包含 next() 方法,每次调用 next() 会返回一个包含 value(当前值)和 done(是否遍历完成)的对象。如Array、String、Map、Set等)
- Generator函数返回一个遍历器对象,具有Iterator接口
-
Generator函数通过function*声明
-
通过next()方法逐步执行,遇到yield暂停
-
yield关键字在函数内部标记暂停点,返回{value:..,done:bollean}结果,直到再次调用next()
-
一般应用场景:惰性求值、异步编程控制、状态机
-
- Generator函数返回一个遍历器对象,具有Iterator接口
-
允许设置默认值
- 数组成员为undefined时默认值会生效(因为ES6内部使用严格相等符号===来判断是否有值);
- 数组成员为null时默认值不会生效(因为null不严格等于undefined);
- 默认值为表达式时,这个表达式为惰性求值,用到的时候,才会求值;
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
对象的解构赋值
- 对象的属性没有次序,变量必须与属性同名;
- 内部机制是先找到同名属性,然后再赋给对应的变量;
- 可以取到继承的属性;
- 默认值生效条件是对象的属性值严格等于undefined
字符串的解构赋值
- 字符串被转换成了一个类似数组的对象;
- 可以对length属性解构赋值。
数值和布尔值的解构赋值
- 数值和布尔值会先转为对象;
- undefined和null无法转为对象,无法进行解构赋值。
函数参数的解构赋值
- 可以指定默认值
圆括号问题
- 只要有可能导致解构的歧义,就不得使用圆括号
- 不能使用圆括号的情况:变量声明、函数参数、赋值语句的模式
- 可以使用圆括号的情况:赋值语句的非模式部分,如
[(b)] = [3];
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3];
解构赋值的用途
-
交换变量的值
let x = 1; let y = 2; [x, y] = [y, x]; -
从函数返回多个值
function example() { return [1, 2, 3]; } let [a, b, c] = example(); -
函数参数的定义
function f([x, y, z]) { ... } f([1, 2, 3]); -
提取JSON数据
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; -
指定函数参数的默认值
-
遍历Map结构,用for...of获取键名和键值时更方便
const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); } -
输入模块的指定方法
字符串的扩展
-
uniCode表示法,采用\uxxx形式表示一个字符
-
添加了遍历器接口,字符串可以被
for...of遍历(可以识别大于0xFFFF的码点) -
JSON.stringify()现在可能返回不符合UTF-8的字符串,所以如果遇到
0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串 -
引入模板字符串,用反引号(`)标识,将变量名写在${}
-
需要使用反引号,则前面要用反斜杠转义
-
所有模板字符串的空格和换行,都会被保留,不想要这个换行,可以使用trim方法消除
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim()); -
可以调用函数
-
大括号中的值不是字符串,将按照一般的规则转为字符串
-
可以进行嵌套
-
标签模板:紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串
// 无变量 alert`hello` // 等同于 alert(['hello']) // 有变量 let a = 5; let b = 10; tag`Hello ${ a + b } world ${ a * b }`; // 等同于 tag(['Hello ', ' world ', ''], 15, 50);-
防止用户输入恶意内容
let sender = '<script>alert("abc")</script>'; // 用户输入的恶意代码 let message = SaferHTML`<p>${sender} has sent you a message.</p>`; message // <p><script>alert("abc")</script> has sent you a message.</p> -
可以使用标签模板,在 JavaScript 语言之中嵌入其他语言(jsx、java等)
-
模板处理函数的第一个参数,还有一个
raw属性,因为接受的参数,实际上是一个数组。该数组有一个raw属性,保存的是转义后的原字符串console.log`123` // ["123", raw: Array[1]]
-
-
-
模板字符串的限制
- 模板字符串默认会将字符串转义,导致无法嵌入其他语言,为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回
undefined,而不是报错,并且从raw属性上面可以得到原始字符串。
- 模板字符串默认会将字符串转义,导致无法嵌入其他语言,为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回
字符串的新增方法
-
String.fromCodePoint():用于从UniCode码点返回对应字符,可以识别码点大于
0xFFFF的字符 -
String.raw():可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用
-
codePointAt():能够正确处理 4 个字节储存的字符,返回一个字符的码点
-
normalize():用来将字符的不同表示方法统一为同样的形式,
normalize方法可以接受一个参数来指定normalize的方式NFC,默认参数,表示“标准等价合成”NFD,表示“标准等价分解”NFKC,表示“兼容等价合成”NFKD,表示“兼容等价分解”
-
实例方法:includes(), startsWith(), endsWith()
- includes():返回布尔值,表示是否找到了参数字符串
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部
- 这三个方法都支持第二个参数,表示开始搜索的位置。
-
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次- 参数如果是小数,会被取整
- 参数是负数或者
Infinity,会报错 - 参数是 0 到-1 之间的小数,则等同于 0
- 参数为
NaN等同于 0 - 参数是字符串,则会先转换成数字
-
实例方法:padStart(),padEnd()
-
padStart()用于头部补全,padEnd()用于尾部补全'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba' -
接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串
-
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串
-
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串
-
如果省略第二个参数,默认使用空格补全长度
-
-
实例方法:trimStart(),trimEnd()
-
trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格,返回新字符串,不会修改原始字符串const s = ' abc '; s.trim() // "abc" s.trimStart() // "abc " s.trimEnd() // " abc" -
对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效
-
-
实例方法:matchAll()
-
返回一个正则表达式在当前字符串的所有匹配
const str = "2023-01-15, 2024-02-20"; const regex = /(\d{4})-(\d{2})-(\d{2})/g; const matches = str.matchAll(regex); for (const match of matches) { console.log("完整匹配:", match[0]); // 如 "2023-01-15" console.log("年:", match[1]); // 如 "2023" console.log("月:", match[2]); // 如 "01" console.log("日:", match[3]); // 如 "15" console.log("匹配索引:", match.index); // 如 0 }
-
-
实例方法:replaceAll()
- 一次性替换所有匹配
- 返回一个新字符串,不会改变原字符串
replaceAll()的第二个参数replacement是一个字符串,表示替换的文本
-
实例方法:at()
- 接受一个整数作为参数,返回参数指定位置的字符,支持负索引
- 参数位置超出了字符串范围,
at()返回undefined
-
实例方法:toWellFormed()
- 将原始字符串里面的单个代理字符对,都替换为
U+FFFD,返回一个新的字符串
- 将原始字符串里面的单个代理字符对,都替换为
正则的扩展
-
RegExp构造函数
- 第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符
-
字符串的正则方法
match()、replace()、search()和split()4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上
-
u修饰符
-
含义为“Unicode 模式”,用来正确处理四个字节的 UTF-16 编码
/^\uD83D/u.test('\uD83D\uDC2A') // false 识别为一个字符 /^\uD83D/.test('\uD83D\uDC2A') // true -
点字符
- 含义是除了换行符以外的任意单个字符,点字符不能识别,必须加上
u修饰符
- 含义是除了换行符以外的任意单个字符,点字符不能识别,必须加上
-
Unicode字符表示法
- 使用大括号表示 Unicode 字符,加上
u修饰符,才能识别当中的大括号,否则会被解读为量词
- 使用大括号表示 Unicode 字符,加上
-
量词
- 使用
u修饰符后,所有量词都会正确识别大于0xFFFF的 Unicode 字符
- 使用
-
预定义模式
- 匹配所有非空白字符。只有加了
u修饰符,它才能正确匹配码点大于0xFFFF的 Unicode 字符
- 匹配所有非空白字符。只有加了
-
i修饰符
- 不加
u修饰符,就无法识别非规范的K字符
- 不加
-
转义
- 没有
u修饰符的情况下,正则中没有定义的转义(如逗号的转义\,)无效,而在u模式会报错
- 没有
-
-
RegExp.prototype.unicode 属性
- 表示是否设置了
u修饰符
- 表示是否设置了
-
y修饰符
- 全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始
- 确保匹配必须从剩余的第一个位置开始
y修饰符不会忽略非法字符
-
RegExp.prototype.sticky 属性
- 表示是否设置了
y修饰符
- 表示是否设置了
-
RegExp.prototype.flags 属性
- 返回正则表达式的修饰符
-
s 修饰符:dotAll 模式
- 使得
.可以匹配任意单个字符 dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式
- 使得
-
后行断言
- 与“先行断言”相反,
x只有在y后面才匹配
- 与“先行断言”相反,
-
Unicode 属性类
- Unicode 属性类的标准形式,需要同时指定属性名和属性值
- 对于某些属性,可以只写属性名,或者只写属性值
-
v 修饰符:Unicode 属性类的运算
- 对属性类进行运算,一种是差集运算,另一种是交集运算
-
具名组匹配
- 允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用
-
d 修饰符:正则匹配索引
- 让
exec()、match()的返回结果添加indices属性,在该属性上面可以拿到匹配的开始位置和结束位置
- 让
-
String.prototype.matchAll()
-
一次性取出所有匹配。返回一个遍历器(Iterator),而不是数组
const string = 'test1test2test3'; const regex = /t(e)(st(\d?))/g; for (const match of string.matchAll(regex)) { console.log(match); } // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"] // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"] // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
-
数值的扩展
-
二进制和八进制表示法
-
分别用前缀
0b(或0B)和0o(或0O) -
将数值转为十进制,要使用
Number方法Number('0b111') // 7 Number('0o10') // 8
-
-
数值分隔符
-
使用下划线(
_)作为分隔符 -
注意点:
-
不能放在数值的最前面或最后面。
-
不能两个或两个以上的分隔符连在一起。
-
小数点的前后不能有分隔符。
-
科学计数法里面,表示指数的
e或E前后不能有分隔符// 全部报错 3_.141 3._141 1_e12 1e_12 123__456 _1464301 1464301_
-
-
-
Number.isFinite(), Number.isNaN()
Number.isFinite()检查一个数值是否为有限的,即不是InfinityNumber.isNaN()用来检查一个值是否为NaN
-
Number.parseInt(), Number.parseFloat()
- 将全局方法
parseInt()和parseFloat(),移植到了Number对象上面
- 将全局方法
-
Number.isInteger()
-
用来判断一个数值是否为整数
-
整数和浮点数采用的是同样的储存方法
Number.isInteger(25) // true Number.isInteger(25.0) // true -
数值存储为64位双精度格式,如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,
Number.isInteger可能会误判Number.isInteger(3.0000000000000002) // true -
一个数值的绝对值小于
Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨的最小值,会被自动转为 0
-
-
Number.EPSILON
- 表示 1 与大于 1 的最小浮点数之间的差,实际上是 JavaScript 能够表示的最小精度
-
安全整数和 Number.isSafeInteger()
- JavaScript 能够准确表示的整数范围在
-2^53到2^53之间(不含两个端点) Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内
- JavaScript 能够准确表示的整数范围在
-
Math 对象的扩展
- Math.trunc()
- 用于去除一个数的小数部分,返回整数部分
- 对于非数值,
Math.trunc内部使用Number方法将其先转为数值 - 对于空值和无法截取整数的值,返回
NaN
- Math.sign()
- 用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值
- 参数为正数,返回
+1; - 参数为负数,返回
-1; - 参数为 0,返回
0; - 参数为-0,返回
-0; - 其他值,返回
NaN。
- Math.cbrt()
- 用于计算一个数的立方根
- 对于非数值,
Math.cbrt()方法内部也是先使用Number()方法将其转为数值
- Math.clz32()
- 将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0
- 对于小数,只考虑整数部分
- 对于小数,只考虑整数部分
- Math.imul()
- 返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数
- Math.fround()
- 返回一个数的32位单精度浮点数形式
- Math.hypot()
- 返回所有参数的平方和的平方根
- Math.expm1()
- 回 ex - 1,即
Math.exp(x) - 1 - Math.log1p()
- 返回
1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。
- 返回
- Math.log10()
- 以 10 为底的
x的对数。如果x小于 0,则返回 NaN
- 以 10 为底的
- Math.log2()
- 返回以 2 为底的
x的对数。如果x小于 0,则返回 NaN
- 返回以 2 为底的
- 双曲函数方法
Math.sinh(x)返回x的双曲正弦(hyperbolic sine)Math.cosh(x)返回x的双曲余弦(hyperbolic cosine)Math.tanh(x)返回x的双曲正切(hyperbolic tangent)Math.asinh(x)返回x的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)返回x的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)返回x的反双曲正切(inverse hyperbolic tangent)
- Math.trunc()
-
BigInt 数据类型
-
BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示
-
BigInt 类型的数据必须添加后缀
n1234 // 普通整数 1234n // BigInt // BigInt 的运算 1n + 2n // 3n -
可以使用各种进制表示,都要加上后缀
n -
BigInt 与普通整数是两种值,它们之间并不相等
-
可以使用负号(
-),但是不能使用正号(+)
-
-
BigInt 函数
-
可以用它生成 BigInt 类型的数值
-
必须有参数,而且参数必须可以正常转为数值
new BigInt() // TypeError BigInt(undefined) //TypeError BigInt(null) // TypeError BigInt('123n') // SyntaxError BigInt('abc') // SyntaxError -
参数不能是小数
-
转换规则
- 可以使用
Boolean()、Number()和String()这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型 - 转为字符串时后缀
n会消失 - 取反运算符(
!)也可以将 BigInt 转为布尔值
- 可以使用
-
数学运算
- 除法运算
/会舍去小数部分,返回一个整数 - 以下不能用在BigInt:
- 不带符号的右移位运算符
>>> - 一元的求正运算符
+
- 不带符号的右移位运算符
- 不能与普通数值进行混合运算
- 除法运算
-
其他运算
-
比较运算符(比如
>)和相等运算符(==)允许 BigInt 与其他类型的值混合计算0n < 1 // true 0n < true // true 0n == 0 // true 0n == false // true 0n === 0 // false -
BigInt 与字符串混合运算时,会先转为字符串,再进行运算
-
-
函数的扩展
-
函数参数的默认值
-
直接写在参数定义的后面
-
使用参数默认值时,函数不能有同名参数
-
参数默认值可以与解构赋值的默认值,结合起来使用
function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined // 调用时没提供参数 function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5 -
参数默认值的位置
-
非尾部的参数设置默认值,无法只省略该参数,而不省略它后面的参数,除非显式输入
undefined。function f(x = 1, y) { return [x, y]; } f() // [1, undefined] f(2) // [2, undefined] f(, 1) // 报错 f(undefined, 1) // [1, 1]
-
-
函数的 length 属性
-
返回没有指定默认值的参数个数
-
设置默认值的参数不是尾参数,
length属性不会计入后面的参数(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
-
-
作用域
- 设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。
-
应用
-
指定某一个参数不得省略,如果省略就抛出一个错误
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter -
将参数默认值设为
undefined,表明这个参数是可以省略的
-
-
-
rest 参数
-
形式为
...变量名,用于获取函数的多余参数 -
该变量将多余的参数放入数组中
-
注意点
-
rest 参数之后不能再有其他参数
// 报错 function f(a, ...b, c) { // ... } -
函数的
length属性,不包括 rest 参数(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
-
-
-
严格模式
-
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式
-
可以设定全局性的严格模式
-
可以把函数包在一个无参数的立即执行函数里面
const doSomething = (function () { 'use strict'; return function(value = 42) { return value; }; }());
-
-
name 属性
- 返回该函数的函数名
- 将一个匿名函数赋值给一个变量,ES5 的
name属性,会返回空字符串,ES6 的name属性会返回实际的函数名。 - 将一个具名函数赋值给一个变量,则 ES5 和 ES6 的
name属性都返回这个具名函数原本的名字。
-
箭头函数
-
大括号被解释为代码块,如果箭头函数直接返回一个对象,必须在对象外面加上括号
-
注意点
- 箭头函数没有自己的
this对象,内部的this就是定义时上层作用域中的this - 不可以当作构造函数,也就是说,不可以对箭头函数使用
new命令,否则会抛出一个错误。 - 不可以使用
arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 - 不可以使用
yield命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数没有自己的
-
不适用场合
-
定义对象的方法,且该方法内部包括
this,此时this会指向全局对象,因为对象不构成单独的作用域,导致箭头函数定义时的作用域就是全局作用域globalThis.s = 21; const obj = { s: 42, m: () => console.log(this.s) }; obj.m() // 21 -
需要动态
this的时候,也不应使用箭头函数var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); // 这里的this指向全局对象而不是被点击的按钮对象 });
-
-
嵌套的箭头函数
const pipeline = (...funcs) => val => funcs.reduce((a, b) => b(a), val); // (5+1)*2 const plus1 = a => a + 1; const mult2 = a => a * 2; const addThenMult = pipeline(plus1, mult2); // pipeline函数调用返回val=>funcs... addThenMult(5) // 5作为初始值 // 12
-
-
尾调用优化
-
尾调用是某个函数的最后一步是调用另一个函数
-
优化:尾递归
- 函数尾调用自身
- 只存在一个调用帧,所以永远不会发生“栈溢出”错误
-
柯里化
-
将多参数的函数转换成单参数的形式
function currying(fn, n) { return function (m) { return fn.call(this, m, n); }; } function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } const factorial = currying(tailFactorial, 1); factorial(5) // 120
-
-
严格模式
- 尾调用优化只在严格模式下开启,正常模式是无效的
-
尾递归优化的实现(不在严格模式下)
-
蹦床函数
function trampoline(f) { while (f && f instanceof Function) { // 当f调用后返回的是函数就会进入循环 f = f(); // 调用f返回一个新函数或值 } return f; } function sum(x, y) { if (y > 0) { return sum.bind(null, x + 1, y - 1); // 创建一个新函数,指向null } else { return x; } } trampoline(sum(1, 100000)) // sum函数的每次执行,都会返回自身的另一个版本 // 100001 -
真正的尾递归优化
function tco(f) { var value; // 最终结果 var active = false; // 标记是否在循环中 var accumulated = []; // 参数队列 return function accumulator() { accumulated.push(arguments); // 收集每次递归调用的参数 if (!active) { active = true; while (accumulated.length) { // 循环处理队列 value = f.apply(this, accumulated.shift()); // bind以给定的 this 值和作为数组提供的 arguments 调用该函数 } active = false; return value; } }; } var sum = tco(function(x, y) { if (y > 0) { return sum(x + 1, y - 1) } else { return x } }); sum(1, 100000) // 100001
-
-
-
允许函数的最后一个参数有尾逗号
-
Function.prototype.toString()
- 返回一模一样的原始代码,不省略注释和空格
-
catch 命令的参数省略
- 允许
catch语句省略参数
- 允许
数组的扩展
-
扩展运算符
-
将一个数组转为用逗号分隔的参数序列
-
后面还可以放置表达式
const arr = [ ...(x > 0 ? ['a'] : []), 'b', ]; -
只有函数调用时,扩展运算符才可以放在圆括号中
-
应用
-
复制数组
const a1 = [1, 2]; // 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1; -
合并数组(浅拷贝)
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ] -
与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5] -
字符串:将字符串转为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ] -
实现了 Iterator 接口的对象
-
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组
let nodeList = document.querySelectorAll('div'); let array = [...nodeList];
-
-
Map 和 Set 结构,Generator 函数
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3] const go = function*(){ yield 1; yield 2; yield 3; }; [...go()] // [1, 2, 3]
-
-
-
Array.from()
-
将两类对象转为真正的数组
-
类似数组的对象( 本质是有
length属性,如DOM 操作返回的 NodeList 集合、函数内部的arguments对象)let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5 的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6 的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] -
可遍历(iterable)的对象
-
-
可以接受第二个参数,对每个元素进行处理,处理后的值放入返回的数组
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); -
可以传入
Array.from()的第三个参数,用来绑定this
-
-
Array.of()
-
用于将一组值,转换为数组
-
如果没有参数,就返回一个空数组
Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1
-
-
实例方法:copyWithin()
-
当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组
-
接受三个参数,这三个参数都应该是数值,如果不是,会自动转为数值
-
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
-
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
-
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
// 将3号位复制到0号位 [1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5] // -2相当于3号位,-1相当于4号位 [1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5] // 将3号位复制到0号位 [].copyWithin.call({length: 5, 3: 1}, 0, 3) // {0: 1, 3: 1, length: 5} // 将2号位到数组结束,复制到0号位 let i32a = new Int32Array([1, 2, 3, 4, 5]); i32a.copyWithin(0, 2); // Int32Array [3, 4, 5, 4, 5] // 对于没有部署 TypedArray 的 copyWithin 方法的平台 // 需要采用下面的写法 [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); // Int32Array [4, 2, 3, 4, 5]
-
-
-
实例方法:find(),findIndex(),findLast(),findLastIndex()
-
find()
-
用于找出第一个符合条件的数组成员
-
如果没有符合条件的成员,则返回
undefined -
接受三个参数,依次为当前的值、当前的位置和原数组
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
-
-
findIndex()
- 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回
-1。
- 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回
-
find()与findIndex()
-
两个方法都可以接受第二个参数,用来绑定回调函数的
this对象。function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); // 26 回调函数中的this对象指向person对象 -
两个方法都可以发现
NaN[NaN].indexOf(NaN) // -1 [NaN].findIndex(y => Object.is(NaN, y)) // findIndex()方法可以借助Object.is()方法做到 // 0
-
-
findLast()与findLastIndex()
- 从数组的最后一个成员开始,依次向前检查
-
-
实例方法:fill()
-
使用给定值,填充一个数组
-
可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c'] -
如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象
-
-
实例方法:entries(),keys() 和 values()
-
用于遍历数组,都返回一个遍历器对象
-
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
-
-
实例方法:includes()
-
返回一个布尔值,表示某个数组是否包含给定的值
-
第二个参数表示搜索的起始位置,默认为
0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度,则会重置为从0开始[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true -
缺点
- 内部使用严格相等运算符(
===)进行判断,会导致对NaN的误判
- 内部使用严格相等运算符(
-
-
实例方法:flat(),flatMap()
-
flat()
-
将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响
-
参数为一个整数,表示想要拉平的层数,默认为1。
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]
-
-
flatMap()
-
对原数组的每个成员执行一个函数,然后对返回值组成的数组执行
flat()方法。该方法返回一个新数组,不改变原数组 -
参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。有第二个参数,用来绑定遍历函数里面的
thisarr.flatMap(function callback(currentValue[, index[, array]]) { // ... }[, thisArg])
-
-
-
实例方法:at()
-
接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组
const arr = [5, 12, 8, 130, 44]; arr.at(2) // 8 arr.at(-2) // 130 -
如果参数位置超出了数组范围,
at()返回undefined
-
-
实例方法:toReversed(),toSorted(),toSpliced(),with()
- 对数组进行操作时,不改变原数组,而返回一个原数组的拷贝
toReversed()对应reverse(),用来颠倒数组成员的位置。toSorted()对应sort(),用来对数组成员排序。toSpliced()对应splice(),用来在指定位置,删除指定数量的成员,并插入新成员。with(index, value)对应splice(index, 1, value),用来将指定位置的成员替换为新的值。
-
实例方法:group(),groupToMap()
-
group()
-
以接受三个参数,依次是数组的当前成员、该成员的位置序号、原数组
-
分组函数返回值应该是字符串,以作为分组后的组名。
-
group()的返回值是一个对象,该对象的键名就是每一组的组名const array = [1, 2, 3, 4, 5]; array.group((num, index, array) => { return num % 2 === 0 ? 'even': 'odd'; }); // { odd: [1, 3, 5], even: [2, 4] }
-
-
groupToMap()
-
与group()完全一致,唯一的区别是返回值是一个 Map 结构,而不是对象
const array = [1, 2, 3, 4, 5]; const odd = { odd: true }; const even = { even: true }; array.groupToMap((num, index, array) => { return num % 2 === 0 ? even: odd; }); // Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
-
-
-
数组的空位
- 空位不是
undefined,空位是没有任何值
- 空位不是
对象的扩展
-
方法的 name 属性
- 方法的
name属性返回函数名(即方法名) - 对象的方法使用了
getter和setter,则name属性在该方法的属性的描述对象的get和set属性上面
- 方法的
-
属性的遍历
- for...in
- Object.keys(obj)
- Object.getOwnPropertyNames(obj)
- Object.getOwnPropertySymbols(obj)
- Reflect.ownKeys(obj)
-
super 关键字
-
用来指向当前对象的原型对象
-
表示原型对象时,只能用在对象的方法之中
const proto = { foo: 'hello' }; const obj = { foo: 'world', find() { return super.foo; } }; Object.setPrototypeOf(obj, proto); obj.find() // "hello" -
不可在属性、箭头函数或普通函数中使用
-
-
解构赋值
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }-
要求等号右边是一个对象,不能是
undefined``或null` -
解构赋值必须是最后一个参数,否则会报错
-
不能复制继承自原型对象的属性
-
-
扩展运算符
-
取出参数对象的所有可遍历属性,拷贝到当前对象之中
let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 }
-
-
完整克隆对象(包含原型链的克隆方式)
const clone = Object.assign( Object.create(Object.getPrototypeOf(obj)), obj );
对象的新增方法
-
Object.is()
-
比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
-
不同之处只有两个:一是
+0不等于-0,二是NaN等于自身+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
-
-
Object.assign()
-
合并对象(浅拷贝),将源对象的可枚举属性复制到目标对象
const target = { a: 1, b: 1 }; const source1 = { b: 2, c: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3} -
如果该参数不是对象,则会先转成对象,然后返回
-
undefined和null无法转成对象,不能作为参数,但是如果不在首参数,就不会报错 -
限制:只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性
-
遇到同名属性,
Object.assign()的处理方法是替换 -
用处
-
为对象添加属性
class Point { constructor(x, y) { Object.assign(this, {x, y}); } } -
为对象添加方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· }; -
合并多个对象
const merge = (...sources) => Object.assign({}, ...sources); -
为属性指定默认值
const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { options = Object.assign({}, DEFAULTS, options); // options是用户提供的参数 console.log(options); // ... }
-
-
-
Object.getOwnPropertyDescriptors()
-
返回指定对象所有自身属性(非继承属性)的描述对象
-
可以拷贝
get属性和set属性const source = { set foo(v) { console.log(v); } }; const target = {}; Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
-
-
Object.setPrototypeOf(),Object.getPrototypeOf()
- Object.setPrototypeOf():设置一个对象的原型对象,返回参数对象本身
- Object.getPrototypeOf():用于读取一个对象的原型对象
-
Object.keys(),Object.values(),Object.entries()
- Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。
- Object.values():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
- Object.entries():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
-
Object.fromEntries():用于将一个键值对数组转为对象
-
Object.hasOwn()
-
判断是否为自身的属性
-
接受两个参数,第一个是所要判断的对象,第二个是属性名
const foo = Object.create({ a: 123 }); foo.b = 456; Object.hasOwn(foo, 'a') // false Object.hasOwn(foo, 'b') // true
-
运算符的扩展
-
指数运算符
**- 右结合:多个指数运算符连用时,是从最右边开始计算的
-
链判断运算符
?.-
直接在链式调用的时候判断,左侧的对象是否为
null或undefined。如果是的,就不再往下运算,而是返回undefinedconst firstName = message?.body?.user?.firstName || 'default'; const fooValue = myForm.querySelector('input[name=foo]')?.value -
对于没有实现的方法
if (myForm.checkValidity?.() === false) { // 表单校验失败 return; } -
写法
-
obj?.prop// 对象属性是否存在 -
obj?.[expr]// 同上 -
func?.(...args)// 函数或对象方法是否存在 -
常见形式
a?.b // 等同于 a == null ? undefined : a.b
-
-
注意点
-
如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
-
以下写法报错
// 构造函数 new a?.() new a?.b() // 链判断运算符的右侧有模板字符串 a?.`{b}` a?.b`{c}` // 链判断运算符的左侧是 super super?.() super?.foo // 链运算符用于赋值运算符左侧 a?.b = c
-
-
-
Null 判断运算符
??-
只有运算符左侧的值为
null或undefined时,才会返回右侧的值 -
与?.配合使用
const animationDuration = response.settings?.animationDuration ?? 300; -
判断函数参数是否赋值
function Component(props) { const enable = props.enabled ?? true; // … } -
如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错
-
-
逻辑赋值运算符
||=、&&=、??=-
先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算
// 老的写法 user.id = user.id || 1; // 新的写法 user.id ||= 1;
-
-
#!命令:放在脚本的第一行,用来指定脚本的执行器
Symbol
-
概述
-
类似于字符串的数据类型,保证每个属性的名字都是独一无二
-
通过
Symbol()函数生成 -
参数是一个对象,就会调用该对象的
toString()方法,将其转为字符串,然后才生成一个 Symbol 值const obj = { toString() { return 'abc'; } }; const sym = Symbol(obj); sym // Symbol(abc) -
相同参数的
Symbol函数的返回值是不相等的// 没有参数的情况 let s1 = Symbol(); let s2 = Symbol(); s1 === s2 // false // 有参数的情况 let s1 = Symbol('foo'); let s2 = Symbol('foo'); s1 === s2 // false -
不能与其他类型的值进行运算
-
-
Symbol.prototype.description:直接返回 Symbol 值的描述
-
作为属性名的 Symbol
-
作为对象属性名时,不能用点运算符
const mySymbol = Symbol(); const a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!" // 不再代表Symbol 值 -
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中(不用方括号则不再代表Symbol 值)
let s = Symbol(); let obj = { [s]: function (arg) { ... } }; obj[s](123);
-
-
实例:消除魔术字符串
-
魔术字符串:在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值
-
常用方法:写成变量
const shapeType = { triangle: 'Triangle' }; function getArea(shape, options) { let area = 0; switch (shape) { case shapeType.triangle: area = .5 * options.width * options.height; break; } return area; } getArea(shapeType.triangle, { width: 100, height: 100 });
-
-
属性名的遍历
-
Object.getOwnPropertySymbols()方法:-
可以获取指定对象的所有 Symbol 属性名
-
返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const objectSymbols = Object.getOwnPropertySymbols(obj); objectSymbols // [Symbol(a), Symbol(b)]
-
-
Reflect.ownKeys()
-
返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
-
-
-
Symbol.for(),Symbol.keyFor()
-
Symbol.for()
-
接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
let s1 = Symbol.for('foo'); let s2 = Symbol.for('foo'); s1 === s2 // true
-
-
Symbol.keyFor()
-
返回一个已登记的 Symbol 类型值的
keylet s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
-
-
-
实例:模块的 Singleton 模式
-
Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例——把实例放到顶层对象
global -
为防止其他文件进行修改,键名使用
Symbol方法生成,那么外部将无法引用这个值,就无法改写// mod.js const FOO_KEY = Symbol.for('foo'); function A() { this.foo = 'hello'; } if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); } module.exports = global[FOO_KEY];
-
-
内置的 Symbol 值
Set 和 Map 数据结构
-
Set
-
类似于数组,但是成员的值都是唯一的,没有重复的值
-
用处
-
去除数组重复成员
// 去除数组的重复成员 [...new Set(array)] -
去除字符串里面的重复字符
[...new Set('ababbc')].join('') // "abc"
-
-
加入值的时候,不会发生类型转换
-
两个对象总是不相等的
-
配合Array.from()去除数组重复成员
function dedupe(array) { return Array.from(new Set(array)); } dedupe([1, 1, 2, 3]) // [1, 2, 3] -
Set实例的属性
Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。
-
Set实例的方法
-
Set.prototype.add(value):添加某个值,返回 Set 结构本身。 -
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 -
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。 -
Set.prototype.clear():清除所有成员,没有返回值。s.add(1).add(2).add(2); // 注意2被加入了两次 s.size // 2 s.has(1) // true s.has(2) // true s.has(3) // false s.delete(2) // true s.has(2) // false
-
-
遍历操作
-
Set.prototype.keys():返回键名的遍历器 -
Set.prototype.values():返回键值的遍历器 -
Set.prototype.entries():返回键值对的遍历器 -
Set.prototype.forEach():使用回调函数遍历每个成员let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"] let set = new Set(['red', 'green', 'blue']); for (let x of set) { console.log(x); } // red // green // blue let set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + ' : ' + value)) // 1 : 1 // 4 : 4 // 9 : 9
-
-
-
WeakSet
- 与 Set 有两个区别
- 成员只能是对象和 Symbol 值
- 对象都是弱引用,即如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存
- 与 Set 有两个区别
-
Map
-
类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
-
任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作
Map构造函数的参数 -
读取一个未知的键,则返回
undefined -
Map实例的属性与方法
- size:返回 Map 结构的成员总数
- Map.prototype.set(key, value):设置键名
key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键 - Map.prototype.get(key):读取
key对应的键值,如果找不到key,返回undefined - Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中
- Map.prototype.delete(key):删除某个键,返回
true。如果删除失败,返回false - Map.prototype.clear():清除所有成员,没有返回值
-
遍历方法
Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员。- 可以接受第二个参数,用来绑定
this
-
与其他数据结构的相互转换
-
Map转数组
const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ] -
数组转Map
new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // } -
Map转对象
function strMapToObj(strMap) { let obj = Object.create(null); for (let [k,v] of strMap) { obj[k] = v; } return obj; } const myMap = new Map() .set('yes', true) .set('no', false); strMapToObj(myMap) // { yes: true, no: false } -
对象转Map
let obj = {"a":1, "b":2}; let map = new Map(Object.entries(obj)); -
Map转JSON
-
Map 的键名都是字符串,可以转为对象 JSON
function strMapToJson(strMap) { return JSON.stringify(strMapToObj(strMap)); } let myMap = new Map().set('yes', true).set('no', false); strMapToJson(myMap) // '{"yes":true,"no":false}' -
Map 的键名有非字符串,可以转为数组 JSON
-
-
JSON转Map
function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}
-
-
-
WeakMap
-
与
Map的区别有两点- 只接受对象(
null除外)和 Symbol 值作为键名,不接受其他类型的值作为键名 WeakMap的键名所指向的对象,不计入垃圾回收机制
- 只接受对象(
-
用途
-
DOM 节点作为键名
let myWeakmap = new WeakMap(); myWeakmap.set( document.getElementById('logo'), {timesClicked: 0}) ; document.getElementById('logo').addEventListener('click', function() { let logoData = myWeakmap.get(document.getElementById('logo')); logoData.timesClicked++; }, false); -
部署私有属性
const _counter = new WeakMap(); const _action = new WeakMap(); class Countdown { constructor(counter, action) { _counter.set(this, counter); _action.set(this, action); } dec() { let counter = _counter.get(this); if (counter < 1) return; counter--; _counter.set(this, counter); if (counter === 0) { _action.get(this)(); } } } const c = new Countdown(2, () => console.log('DONE')); c.dec() c.dec() // DONE
-
-
-
WeakRef
-
直接创建对象的弱引用
let target = {}; let wr = new WeakRef(target); // 垃圾回收机制不会计入wr这个引用,也就是说,wr的引用不会妨碍原始对象target被垃圾回收机制清除
-
-
FinalizationRegistry
-
用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数
const registry = new FinalizationRegistry(heldValue => { // .... }); registry.register(theObject, "some value"); // 用来注册所要观察的目标对象
-
Proxy
-
概述
-
在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,可以对外界的访问进行过滤和改写
-
ES6 原生提供 Proxy 构造函数
var proxy = new Proxy(target, handler); // target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为 -
小技巧
-
将 Proxy 对象,设置到
object.proxy属性,从而可以在object对象上调用var object = { proxy: new Proxy(target, handler) };
-
-
支持的拦截操作
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo和proxy['foo']。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v或proxy['foo'] = v,返回一个布尔值。 - has(target, propKey):拦截
propKey in proxy的操作,返回一个布尔值。 - deleteProperty(target, propKey):拦截
delete proxy[propKey]的操作,返回一个布尔值。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy),返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy),返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy),返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)。
- get(target, propKey, receiver):拦截对象属性的读取,比如
-
-
Proxy 实例的方法
-
get()
- 接受三个参数,依次为目标对象、属性名、 proxy 实例本身(可选)
-
set()
- 接受四个参数,依次为目标对象、属性名、属性值、 Proxy 实例本身(可选)
-
apply()
-
拦截函数的调用、
call和apply操作 -
接受三个参数,分别是目标对象、目标对象的上下文对象(
this)和目标对象的参数数组var target = function () { return 'I am the target'; }; var handler = { apply: function () { return 'I am the proxy'; } }; var p = new Proxy(target, handler); p() // "I am the proxy"
-
-
has()
-
用来拦截
HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效,对for...in循环不生效 -
接受两个参数,分别是目标对象、需查询的属性名
// 隐藏某些属性 var handler = { has (target, key) { if (key[0] === '_') { return false; } return key in target; } }; var target = { _prop: 'foo', prop: 'foo' }; var proxy = new Proxy(target, handler); '_prop' in proxy // false
-
-
construct()
-
用于拦截
new命令 -
接受三个参数,目标对象、构造函数的参数数组、创造实例对象时
new命令作用的构造函数、 -
方法中的
this指向的是handlerconst p = new Proxy(function () {}, { construct: function(target, args) { console.log('called: ' + args.join(', ')); return { value: args[0] * 10 }; } }); (new p(1)).value // "called: 1" // 10
-
-
deleteProperty()
-
用于拦截
delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除var handler = { deleteProperty (target, key) { invariant(key, 'delete'); delete target[key]; return true; } };
-
-
defineProperty()
-
拦截
Object.defineProperty()操作var handler = { defineProperty (target, key, descriptor) { return false; } }; var target = {}; var proxy = new Proxy(target, handler); proxy.foo = 'bar' // 不会生效
-
-
getOwnPropertyDescriptor()
- 拦截
Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined
- 拦截
-
getPrototypeOf()
- 拦截获取对象原型,拦截以下操作
Object.prototype.__proto__Object.prototype.isPrototypeOf()Object.getPrototypeOf()Reflect.getPrototypeOf()instanceof
- 拦截获取对象原型,拦截以下操作
-
isExtensible()
- 拦截
Object.isExtensible()操作
- 拦截
-
ownKeys()
- 拦截对象自身属性的读取操作,拦截以下操作
Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()for...in循环
- 拦截对象自身属性的读取操作,拦截以下操作
-
preventExtensions()
-
拦截
Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值 -
限制
-
只有目标对象不可扩展时(即
Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错 -
解决方法(在
proxy.preventExtensions()方法里面,调用一次Object.preventExtensions())var proxy = new Proxy({}, { preventExtensions: function(target) { console.log('called'); Object.preventExtensions(target); return true; } }); Object.preventExtensions(proxy) // "called" // Proxy {}
-
-
-
setPrototypeOf()
- 拦截
Object.setPrototypeOf()方法
- 拦截
-
-
Proxy.revocable()
- 返回一个可取消的 Proxy 实例
- 执行
revoke函数之后,再访问Proxy实例,就会抛出一个错误 - 使用场景
- 目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问
-
this 问题
-
在 Proxy 代理的情况下,目标对象内部的
this关键字会指向 Proxy 代理const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true -
Proxy 拦截函数内部的
this,指向的是handler对象
-
-
实例:Web 服务的客户端
function createWebService(baseUrl) { return new Proxy({}, { get(target, propKey, receiver) { return () => httpGet(baseUrl + '/' + propKey); } }); }
Reflect
-
概述
-
设计目的
-
将
Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上,现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上 -
修改某些
Object方法的返回结果,让其变得更合理// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure } -
让
Object操作都变成函数行为// 老写法 'assign' in Object // true // 新写法 Reflect.has(Object, 'assign') // true -
Reflect对象的方法与Proxy对象的方法一一对应
-
-
-
静态方法
-
Reflect.apply(target, thisArg, args)
-
用于绑定
this对象后执行给定函数const ages = [11, 33, 12, 54, 18, 96]; // 旧写法 const youngest = Math.min.apply(Math, ages); const oldest = Math.max.apply(Math, ages); const type = Object.prototype.toString.call(youngest); // 新写法 const youngest = Reflect.apply(Math.min, Math, ages); const oldest = Reflect.apply(Math.max, Math, ages); const type = Reflect.apply(Object.prototype.toString, youngest, []);
-
-
Reflect.construct(target, args)
-
调用构造函数的方法
function Greeting(name) { this.name = name; } // new 的写法 const instance = new Greeting('张三'); // Reflect.construct 的写法 const instance = Reflect.construct(Greeting, ['张三']);
-
-
Reflect.get(target, name, receiver)
-
查找并返回
target对象的name属性,如果没有该属性,则返回undefinedvar myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, } Reflect.get(myObject, 'foo') // 1 Reflect.get(myObject, 'bar') // 2 Reflect.get(myObject, 'baz') // 3
-
-
Reflect.set(target, name, value, receiver)
-
设置
target对象的name属性等于valuevar myObject = { foo: 1, set bar(value) { return this.foo = value; }, } myObject.foo // 1 Reflect.set(myObject, 'foo', 2); myObject.foo // 2
-
-
Reflect.defineProperty(target, name, desc)
-
用来为对象定义属性
function MyDate() { /*…*/ } // 旧写法 Object.defineProperty(MyDate, 'now', { value: () => Date.now() }); // 新写法 Reflect.defineProperty(MyDate, 'now', { value: () => Date.now() });
-
-
Reflect.deleteProperty(target, name)
-
用于删除对象的属性
-
如果删除成功,或者被删除的属性不存在,返回
true;删除失败,被删除的属性依然存在,返回falseconst myObj = { foo: 'bar' }; // 旧写法 delete myObj.foo; // 新写法 Reflect.deleteProperty(myObj, 'foo');
-
-
Reflect.has(target, name)
-
对应
name in obj里面的in运算符var myObject = { foo: 1, }; // 旧写法 'foo' in myObject // true // 新写法 Reflect.has(myObject, 'foo') // true
-
-
Reflect.ownKeys(target)
-
用于返回对象的所有属性
var myObject = { foo: 1, bar: 2, [Symbol.for('baz')]: 3, [Symbol.for('bing')]: 4, }; // 旧写法 Object.getOwnPropertyNames(myObject) // ['foo', 'bar'] Object.getOwnPropertySymbols(myObject) //[Symbol(baz), Symbol(bing)] // 新写法 Reflect.ownKeys(myObject) // ['foo', 'bar', Symbol(baz), Symbol(bing)]
-
-
Reflect.isExtensible(target)
-
返回一个布尔值,表示当前对象是否可扩展
const myObject = {}; // 旧写法 Object.isExtensible(myObject) // true // 新写法 Reflect.isExtensible(myObject) // true
-
-
Reflect.preventExtensions(target)
-
用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功
var myObject = {}; // 旧写法 Object.preventExtensions(myObject) // Object {} // 新写法 Reflect.preventExtensions(myObject) // true
-
-
Reflect.getOwnPropertyDescriptor(target, name)
-
用于得到指定属性的描述对象
var myObject = {}; Object.defineProperty(myObject, 'hidden', { value: true, enumerable: false, }); // 旧写法 var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden'); // 新写法 var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
-
-
Reflect.getPrototypeOf(target)
-
用于读取对象的
__proto__属性const myObj = new FancyThing(); // 旧写法 Object.getPrototypeOf(myObj) === FancyThing.prototype; // 新写法 Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
-
-
Reflect.setPrototypeOf(target, prototype)
-
设置目标对象的原型,它返回一个布尔值,表示是否设置成功。
const myObj = {}; // 旧写法 Object.setPrototypeOf(myObj, Array.prototype); // 新写法 Reflect.setPrototypeOf(myObj, Array.prototype); myObj.length // 0
-
-
-
实例:使用 Proxy 实现观察者模式
-
函数自动观察数据对象,一旦对象有变化,函数就会自动执行
const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); queuedObservers.forEach(observer => observer()); return result; }
-
Promise对象
-
Promise 的含义
- 异步编程的解决方案
- 特点
- 对象状态不受外界影响,只有异步操作的结果可以决定当前的状态:pending(准备中)、fulfilled(已成功)、rejected(已失败)
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
-
基本用法
- 接受一个函数作为参数,该函数的两个参数分别是
resolve和reject resolve函数:将Promise对象的状态从“未完成”变为“成功”reject函数:将Promise对象的状态从“未完成”变为“失败”- 用
then方法分别指定resolved状态和rejected状态的回调函数- 接受两个回调函数作为参数。第一个回调函数是
Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的
- 接受两个回调函数作为参数。第一个回调函数是
- Promise 新建后就会立即执行
- 接受一个函数作为参数,该函数的两个参数分别是
-
Promise.prototype.then()
getJSON("/post/1.json").then( post => getJSON(post.commentURL) ).then( comments => console.log("resolved: ", comments), err => console.log("rejected: ", err) ); -
Promise.prototype.catch()
-
用于指定发生错误时的回调函数
promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
-
-
Promise.prototype.finally()
-
不管 Promise 对象最后状态如何,都会执行的操作
server.listen(port) .then(function () { // ... }) .finally(server.stop);
-
-
Promise.all()
-
用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]); -
接受一个数组作为参数,
p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例(可以不是数组,但必须具有 Iterator 接口) -
只有
p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,有一个被rejected,p的状态就变成rejected -
没有自己的
catch方法,才会调用Promise.all()的catch方法const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // ["hello", Error: 报错了]
-
-
Promise.race()
- 将多个 Promise 实例,包装成一个新的 Promise 实例
- 只要
p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
-
Promise.allSettled()
-
用来确定一组异步操作是否都结束了(不管成功或失败)
-
接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象
-
只有等到参数数组的所有 Promise 对象都发生状态变更,返回的 Promise 对象才会发生状态变更
const resolved = Promise.resolve(42); const rejected = Promise.reject(-1); const allSettledPromise = Promise.allSettled([resolved, rejected]); allSettledPromise.then(function (results) { console.log(results); }); // [ // { status: 'fulfilled', value: 42 }, // { status: 'rejected', reason: -1 } // ] // 过滤出成功的请求 const successfulPromises = results.filter(p => p.status === 'fulfilled'); // 过滤出失败的请求,并输出原因 const errors = results .filter(p => p.status === 'rejected') .map(p => p.reason);
-
-
Promise.any()
- 接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
- 只要参数实例有一个变成
fulfilled状态,实例就会变成fulfilled状态;所有参数实例都变成rejected状态,就会变成rejected状态
-
Promise.resolve()
-
将现有对象转为 Promise 对象
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
-
-
Promise.reject()
- 返回一个新的 Promise 实例,该实例的状态为
rejected
- 返回一个新的 Promise 实例,该实例的状态为
-
应用
-
加载图片
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); }; -
Generator函数与Promise的结合
function getFoo () { return new Promise(function (resolve, reject){ resolve('foo'); }); } const g = function* () { try { const foo = yield getFoo(); console.log(foo); } catch (e) { console.log(e); } }; function run (generator) { const it = generator(); function go(result) { if (result.done) return result.value; return result.value.then(function (value) { return go(it.next(value)); }, function (error) { return go(it.throw(error)); }); } go(it.next()); } run(g);
-
-
Promise.try()
-
所有操作提供了统一的处理机制(同步函数与异步操作)
const f = () => console.log('now'); Promise.try(f); console.log('next'); // now // next
-
Iterator和for...of循环
-
Iterator的概念
- 主要为数组、对象、
Map、Set - 遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用指针对象的
next方法,指向数据结构的第一个成员 - 不断调用指针对象的
next方法,直到它指向数据结构的结束位置 next方法返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束
- 主要为数组、对象、
-
默认 Iterator 接口
-
为所有数据结构,提供了一种统一的访问机制
-
一个数据结构只要具有
Symbol.iterator属性,就可以认为是“可遍历的” -
Symbol.iterator属性本身是一个函数,执行这个函数,就会返回一个遍历器let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true } -
为对象部署Interator接口
let obj = { data: [ 'hello', 'world' ], [Symbol.iterator]() { const self = this; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } return { value: undefined, done: true }; } }; } }; -
对于类似数组的对象(存在数值键名和
length属性)部署 Iterator 接口let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable) { console.log(item); // 'a', 'b', 'c' }
-
-
字符串的 Iterator 接口
var someString = "hi"; typeof someString[Symbol.iterator] // "function" var iterator = someString[Symbol.iterator](); iterator.next() // { value: "h", done: false } iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true } -
Iterator 接口与 Generator 函数
// Symbol.iterator()方法的最简单实现 var someString = "hi"; typeof someString[Symbol.iterator] // "function" var iterator = someString[Symbol.iterator](); iterator.next() // { value: "h", done: false } iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true } -
遍历器对象的 return(),throw()
-
return()方法必须返回一个对象function readLinesSync(file) { return { [Symbol.iterator]() { return { next() { return { done: false }; }, return() { file.close(); return { done: true }; } }; }, }; }
-
-
for...of 循环
-
for...of循环内部调用的是数据结构的Symbol.iterator方法 -
对象
-
for...of与Object.keys配合使用
for (var key of Object.keys(someObject)) { console.log(key + ': ' + someObject[key]); } -
使用generator函数重新包装
const obj = { a: 1, b: 2, c: 3 } function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; } } for (let [key, value] of entries(obj)) { console.log(key, '->', value); } // a -> 1 // b -> 2 // c -> 3
-
-
Generator函数
-
简介
-
一个状态机,封装了多个内部状态
-
特征
- function*函数名{}
- 内部使用yield表达式
-
分段执行的,
yield表达式是暂停执行的标记,而next方法可以恢复执行 -
next方法的运行逻辑- 遇到
yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 - 下一次调用
next方法时,再继续往下执行,直到遇到下一个yield表达式。 - 如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 - 如果该函数没有
return语句,则返回的对象的value属性值为undefined。
- 遇到
-
yield表达式如果用在另一个表达式之中,必须放在圆括号里面function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
-
-
next 方法的参数
-
可以带一个参数,该参数就会被当作上一个
yield表达式的返回值function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true } -
想要第一次调用就输入值可以在Generator函数外面再包一层
function wrapper(generatorFunction) { return function (...args) { let generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; } const wrapped = wrapper(function* () { console.log(`First input: ${yield}`); return 'DONE'; }); wrapped().next('hello!') // First input: hello!
-
-
for...of 循环
function* numbers () { yield 1 yield 2 return 3 yield 4 } // 扩展运算符 [...numbers()] // [1, 2] // Array.from 方法 Array.from(numbers()) // [1, 2] // 解构赋值 let [x, y] = numbers(); x // 1 y // 2 // for...of 循环 for (let n of numbers()) { console.log(n) } // 1 // 2 -
Generator.prototype.throw()
- 如果 Generator 函数内部没有部署
try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获 throw方法被内部捕获以后,会附带执行到下一条yield表达式,这种情况下等同于执行一次next方法
- 如果 Generator 函数内部没有部署
-
Generator.prototype.return()
-
可以返回给定的值,并且终结遍历 Generator 函数
-
return()方法调用时,不提供参数,则返回值的value属性为undefined
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true }
-
-
yield* 表达式
-
用来在一个 Generator 函数里面执行另一个 Generator 函数
function* inner() { yield 'hello!'; } function* outer1() { yield 'open'; yield inner(); yield 'close'; } var gen = outer1() gen.next().value // "open" gen.next().value // 返回一个遍历器对象 gen.next().value // "close" function* outer2() { yield 'open' yield* inner() yield 'close' } var gen = outer2() gen.next().value // "open" gen.next().value // "hello!" gen.next().value // "close"
-
-
作为对象属性的 Generator 函数
-
简写
let obj = { * myGeneratorMethod() { ··· } };
-
-
Generator 函数的this
gen返回的总是遍历器对象,而不是this对象,使用call方法可以绑定 Generator 函数内部的this
function* gen() { this.a = 1; yield this.b = 2; yield this.c = 3; } function F() { return gen.call(gen.prototype); } var f = new F(); f.next(); // Object {value: 2, done: false} f.next(); // Object {value: 3, done: false} f.next(); // Object {value: undefined, done: true} f.a // 1 f.b // 2 f.c // 3 -
含义
- Generator 函数被称为“半协程”,意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用
yield表达式交换控制权。
- Generator 函数被称为“半协程”,意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用
-
应用
-
异步操作的同步化表达
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); // 加载UI loader.next() // 卸载UI loader.next() -
控制流管理
let steps = [step1Func, step2Func, step3Func]; function* iterateSteps(steps){ for (var i=0; i< steps.length; i++){ var step = steps[i]; yield step(); } } -
部署Interator接口
function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); } // foo 3 // bar 7
-
Generator函数的异步应用
-
传统方法
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
-
Generator 函数
-
异步任务的封装
var fetch = require('node-fetch'); function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });
-
-
Thunk 函数(可以自动执行 Generator 函数)
-
任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。
const Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); } }; }; -
基于 Thunk 函数的 Generator 执行器
function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } function* g() { // ... } run(g);
-
-
co 模块
-
用于 Generator 函数的自动执行,
co函数返回一个Promise对象,因此可以用then方法添加回调函数var co = require('co'); co(gen); co(gen).then(function (){ console.log('Generator 函数执行完成'); });
-
async函数
-
含义
-
Generator函数的语法糖
const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; // 可以写成 const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; -
改进
- 内置执行器,可直接调用函数
- 返回值是Promise
-
-
基本用法
// 函数声明 async function foo() {} // 函数表达式 const foo = async function () {}; // 对象的方法 let obj = { async foo() {} }; obj.foo().then(...) // Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } } const storage = new Storage(); storage.getAvatar('jake').then(…); // 箭头函数 const foo = async () => {}; -
语法
-
函数内部
return语句返回的值,会成为then方法回调函数的参数async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world" -
错误处理
async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } } -
注意点
-
最好把
await命令放在try...catch代码块中 -
多个
await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发let foo = await getFoo(); let bar = await getBar(); let [foo, bar] = await Promise.all([getFoo(), getBar()]); -
async 函数可以保留运行堆栈
const a = async () => { await b(); c(); }; // b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包括a()
-
-
-
async 函数的实现原理:Generator函数和自动执行器包装在一个函数里
-
实例:按顺序完成异步操作
async function logInOrder(urls) { // 并发读取远程URL const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); }); // 按次序输出 for (const textPromise of textPromises) { console.log(await textPromise); } } -
顶层 await
-
允许在模块的顶层独立使用
await命令,主要目的是使用await解决模块异步加载的问题// import() 方法加载 const strings = await import(`/i18n/${navigator.language}`); // 数据库操作 const connection = await dbConnector(); // 依赖回滚 let jQuery; try { jQuery = await import('https://cdn-a.com/jQuery'); } catch { jQuery = await import('https://cdn-b.com/jQuery'); }
-
Class的基本语法
-
类的由来
-
类的方法都定义在
prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign()方法可以很方便地一次向类添加多个方法class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
-
-
constructor() 方法
- 一个类必须有
constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加
- 一个类必须有
-
类的实例
-
类的属性和方法,除非显式定义在其本身(即定义在
this对象上),否则都是定义在原型上(即定义在class上)class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); point.toString() // (2, 3) point.hasOwnProperty('x') // true point.hasOwnProperty('y') // true point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true
-
-
实例属性的新写法
-
实例属性现在除了可以定义在
constructor()方法里面的this上面,也可以定义在类内部的最顶层class IncreasingCounter { _count = 0; get value() { console.log('Getting the current value!'); return this._count; } increment() { this._count++; } }
-
-
取值函数(getter)和存值函数(setter)
-
对某个属性设置存值函数和取值函数,拦截该属性的存取行为
-
存值函数和取值函数是设置在属性的 Descriptor 对象上的
class CustomHTMLElement { constructor(element) { this.element = element; } get html() { return this.element.innerHTML; } set html(value) { this.element.innerHTML = value; } } var descriptor = Object.getOwnPropertyDescriptor( CustomHTMLElement.prototype, "html" ); "get" in descriptor // true "set" in descriptor // true
-
-
属性表达式
-
类的属性名,可以采用表达式
let methodName = 'getArea'; class Square { constructor(length) { // ... } [methodName]() { // ... } }
-
-
Class 表达式
-
类也可以使用表达式的形式定义
const MyClass = class Me { getClassName() { return Me.name; } }; // 注意点:这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用 let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not defined // 如果类的内部没用到的话,可以省略Me const MyClass = class { /* ... */ }; -
立即执行的 Class
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('张三'); person.sayName(); // "张三"
-
-
静态方法
-
在一个方法前,加上
static关键字,就表示该方法不会被实例继承,而是直接通过类来调用 -
静态方法包含
this关键字,这个this指的是类,而不是实例 -
父类的静态方法,可以被子类继承
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod() // 'hello' -
静态方法可以从
super对象上调用的class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ', too'; } } Bar.classMethod() // "hello, too"
-
-
静态属性
-
在实例属性的前面,加上
static关键字class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } }
-
-
私有方法和私有属性
-
方法一:在命名上加以区别
class Widget { // 公有方法 foo (baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } // ... } -
方法二:将私有方法移出类
lass Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; } -
方法三:利用Symbol值得唯一性
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... }; // Reflect.ownKeys()依然可以拿到 const inst = new myClass(); Reflect.ownKeys(myClass.prototype) // [ 'constructor', 'foo', Symbol(bar) ] -
私有属性是在属性名之前使用
#表示class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } } const counter = new IncreasingCounter(); counter.#count // 报错 counter.#count = 42 // 报错 -
私有属性的属性名必须包括
#,如果不带#,会被当作另一个属性 -
私有属性可以设置 getter 和 setter 方法
class Counter { #xValue = 0; constructor() { console.log(this.#x); } get #x() { return this.#xValue; } set #x(value) { this.#xValue = value; } } -
私有属性和私有方法前面,也可以加上
static关键字,表示这是一个静态的私有属性或私有方法class FakeMath { static PI = 22 / 7; static #totallyRandomNumber = 4; static #computeRandomNumber() { return FakeMath.#totallyRandomNumber; } static random() { console.log('I heard you like random numbers…') return FakeMath.#computeRandomNumber(); } } FakeMath.PI // 3.142857142857143 FakeMath.random() // I heard you like random numbers… // 4 FakeMath.#totallyRandomNumber // 报错 FakeMath.#computeRandomNumber() // 报错 -
使用in运算符判断私有属性
class C { #brand; static isC(obj) { if (#brand in obj) { // 私有属性 #brand 存在 return true; } else { // 私有属性 #foo 不存在 return false; } } } // 可以与this配合使用 class A { #foo = 0; m() { console.log(#foo in this); // true } }
-
-
静态块
-
允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化
-
每个类可以有多个静态块,每个静态块中只能访问之前声明的静态属性
-
静态块的内部不能有
return语句 -
内部可以使用类名或
this,指代当前类 -
可以将私有属性与类的外部代码分享
let getX; export class C { #x = 1; static { getX = obj => obj.#x; } } console.log(getX(new C())); // 1
-
-
类的注意点
-
类和模块的内部,默认就是严格模式
-
类不存在变量提升
-
类中的Generator方法
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world -
this的指向
-
方法内部如果含有
this,它默认指向类的实例 -
如果将这个方法提取出来单独使用,
this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined)class Logger { printName(name = 'there') { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property 'print' of undefined -
解决方法一:在构造方法中绑定this
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... } -
解决方法二:使用箭头函数
class Obj { constructor() { this.getThis = () => this; } } const myObj = new Obj(); myObj.getThis() === myObj // true -
解决方法三:使用Proxy
function selfish (target) { const cache = new WeakMap(); const handler = { get (target, key) { const value = Reflect.get(target, key); if (typeof value !== 'function') { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy; } const logger = selfish(new Logger());
-
-
-
new.target 属性
-
该属性一般用在构造函数之中,返回
new命令作用于的那个构造函数 -
如果构造函数不是通过
new命令或Reflect.construct()调用的,new.target会返回undefined -
Class 内部调用
new.target,返回当前 Class -
子类继承父类时,
new.target会返回子类 -
可以写出不能独立使用、必须继承后才能使用的类
class Shape { constructor() { if (new.target === Shape) { throw new Error('本类不能实例化'); } } } class Rectangle extends Shape { constructor(length, width) { super(); // ... } } var x = new Shape(); // 报错 var y = new Rectangle(3, 4); // 正确
-
Class的继承
-
简介
- 通过
extends关键字实现继承 - 子类必须在
constructor()方法中调用super(),这一步会生成一个继承父类的this对象,没有这一步就无法继承父类
- 通过
-
私有属性和私有方法的继承
- 类无法继承父类的私有属性和私有方法
- 如果父类定义了私有属性的读写方法,子类就可以通过这些方法,读写私有属性
-
静态属性和静态方法的继承
- 静态属性是通过浅拷贝实现继承的
- 如果父类的静态属性的值是一个对象,子类修改这个对象的属性值,会影响到父类
-
Object.getPrototypeOf()
-
用来从子类上获取父类,可以用来判断一个类是否继承了另一个类。
class Point { /*...*/ } class ColorPoint extends Point { /*...*/ } Object.getPrototypeOf(ColorPoint) === Point // true
-
-
super 关键字
-
情况一
-
super作为函数调用时,代表父类的构造函数,返回的是子类的this(即子类的实例对象) -
如果存在同名属性,此时拿到的是父类的属性
class A { name = 'A'; constructor() { console.log('My name is ' + this.name); } } class B extends A { name = 'B'; } const b = new B(); // My name is A
-
-
情况二
-
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类 -
注意点:当
super指向父类的原型对象,定义在父类实例上的方法或属性,是无法通过super调用的class A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m // undefined -
如果通过
super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined 读的是A.prototype.x console.log(this.x); // 3 } } let b = new B();
-
-
-
类的 prototype 属性和__proto__属性
-
子类的
__proto__属性,表示构造函数的继承,总是指向父类 -
子类
prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
-
-
原生构造函数的继承
-
例子
class MyArray extends Array { constructor(...args) { super(...args); } } var arr = new MyArray(); arr[0] = 12; arr.length // 1 arr.length = 0; arr[0] // undefined -
注意点:ES6 改变了
Object构造函数的行为,一旦发现Object方法不是通过new Object()这种形式调用,ES6 规定Object构造函数会忽略参数class NewObj extends Object{ constructor(){ super(...arguments); } } var o = new NewObj({attr: true}); o.attr === true // false
-
-
Mixin 模式的实现
-
多个对象合成一个新的对象,新对象具有各个组成成员的接口
-
简单实现
const a = { a: 'a' }; const b = { b: 'b' }; const c = {...a, ...b}; // {a: 'a', b: 'b'} -
完备实现
function mix(...mixins) { class Mix { constructor() { for (let mixin of mixins) { copyProperties(this, new mixin()); // 拷贝实例属性 } } } for (let mixin of mixins) { copyProperties(Mix, mixin); // 拷贝静态属性 copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性 } return Mix; } function copyProperties(target, source) { for (let key of Reflect.ownKeys(source)) { if ( key !== 'constructor' && key !== 'prototype' && key !== 'name' ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } } } // 使用时只要继承这个类 class DistributedEdit extends mix(Loggable, Serializable) { // ... }
-
-
Module语法
-
概述
- ES6 模块是编译时加载
-
自动采用严格模式
-
export 命令
-
export命令用于规定模块的对外接口 -
写法一
// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; -
写法二
// profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export { firstName, lastName, year }; -
可以使用
as关键字重命名function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
-
-
import 命令
-
用于输入其他模块提供的功能
-
可以使用
as关键字将输入的变量重命名import { lastName as surname } from './profile.js'; -
import命令输入的变量都是只读的,因为它的本质是输入接口 -
import命令具有提升效果,会提升到整个模块的头部,首先执行 -
import是静态执行,所以不能使用表达式和变量
-
-
export default 命令
-
为模块指定默认输出
-
import命令可以为该匿名函数指定任意名字 -
默认输出与正常输出对比
// 第一组 export default function crc32() { // 输出 // ... } import crc32 from 'crc32'; // 输入 // 第二组 export function crc32() { // 输出 // ... }; import {crc32} from 'crc32'; // 输入
-
-
export 与 import 的复合写法
-
先输入后输出同一个模块,
import语句可以与export语句写在一起export { foo, bar } from 'my_module'; // 可以简单理解为 import { foo, bar } from 'my_module'; export { foo, bar }; -
模块的接口改名和整体输出
// 接口改名 export { foo as myFoo } from 'my_module'; // 整体输出 export * from 'my_module'; export * as ns from "mod"; // 等同于 import * as ns from "mod"; export {ns}; -
默认接口
export { default } from 'foo';
-
-
跨模块常量
-
建一个专门的
constants目录,将各种常量写在不同的文件里面// constants/db.js export const db = { url: 'http://my.couchdbserver.local:5984', admin_username: 'admin', admin_password: 'admin password' }; // constants/user.js export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator']; -
将这些文件输出的常量,合并在
index.js里面// constants/index.js export {db} from './db'; export {users} from './users'; -
使用的时候,直接加载
index.js// script.js import {db, users} from './constants/index';
-
-
import()
-
引入
import()函数,支持动态加载模块 -
import()返回一个 Promise 对象async function renderWidget() { const container = document.getElementById('widget'); if (container !== null) { // 等同于 // import("./widget").then(widget => { // widget.render(container); // }); const widget = await import('./widget.js'); widget.render(container); } } renderWidget(); -
适用场景
-
按需加载
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) }); -
条件加载
if (condition) { import('moduleA').then(...); } else { import('moduleB').then(...); } -
动态的模块路径
import(f()) .then(...);
-
-
注意点(
import()加载模块成功以后,这个模块会作为一个对象)-
可以使用对象解构赋值的语法,获取输出接口
import('./myModule.js') .then(({export1, export2}) => { // ...· }); -
如果模块有
default输出接口,可以用参数直接获得import('./myModule.js') .then(myModule => { console.log(myModule.default); }); // 可以适用具名输入的形式 import('./myModule.js') .then(({default: theDefault}) => { console.log(theDefault); }); -
可以同时加载多个模块
Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]) .then(([module1, module2, module3]) => { ··· }); -
可以用在async函数之中
async function main() { const myModule = await import('./myModule.js'); const {export1, export2} = await import('./myModule.js'); const [module1, module2, module3] = await Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]); } main();
-
-
-
import.meta
-
返回当前模块的元信息
-
import.meta.url-
返回当前模块的 URL 路径
new URL('data.txt', import.meta.url)// https://foo.com/main.js/data.txt
-
-
import.meta.scriptElement-
返回加载模块的那个
<script>元素,相当于document.currentScript属性// HTML 代码为 // <script type="module" src="my-module.js" data-foo="abc"></script> // my-module.js 内部执行下面的代码 import.meta.scriptElement.dataset.foo // "abc"
-
-
import.meta.filename:当前模块文件的绝对路径。 -
import.meta.dirname:当前模块文件的目录的绝对路径。
-
Module的加载实现
-
浏览器加载
-
传统方法:通过
-
脚本异步加载
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script> -
defer是“渲染完再执行”,async是“下载完就执行”,如果有多个
defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的
-
-
记载规则
- 浏览器加载 ES6 模块,也使用
<script>标签,但是要加入type="module"属性。
<script type="module" src="./foo.js"></script> - 浏览器加载 ES6 模块,也使用
-
-
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块一旦输出一个值,模块内部的变化就影响不到这个值
- ES6 模块的JS 引擎对脚本静态分析的时候,遇到模块加载命令
import,会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- 因为 CommonJS 加载的是一个对象(即
module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
- 因为 CommonJS 加载的是一个对象(即
- CommonJS 模块的
require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
-
Node.js 的模块加载方法
-
.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置(ES6:"type": "module") -
package.json指定模块的入口文件的字段
-
main字段
-
exports字段(优先级高于main字段)
-
子目录别名
// ./node_modules/es-module-package/package.json { "exports": { // 指定src/features.js别名为features "./features/": "./src/features/" } } import feature from 'es-module-package/features/x.js'; // 加载 ./node_modules/es-module-package/src/features/x.js -
main的别名
{ "main": "./main-legacy.cjs",// 兼容旧版 "exports": { ".": "./main-modern.cjs" } } -
条件加载
// 为 ES6 模块和 CommonJS 指定不同的入口 { "type": "module", "exports": { ".": { "require": "./main.cjs", "default": "./main.js" } } }
-
-
-
CommonJS模块加载ES6模块
(async () => { await import('./my-app.mjs'); })(); -
ES6模块加载CommonJS
- 可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项
-
内部变量
- ES6 模块之中,顶层的
this指向undefined;CommonJS 模块的顶层this指向当前模块 - 以下顶层变量在ES6模块之中不存在
argumentsrequiremoduleexports__filename__dirname
- ES6 模块之中,顶层的
-
-
循环加载(
a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本)-
CommonJS模块的循环加载
- 一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出
-
ES6模块的循环加载
-
利用函数的变量提升
// a.mjs import {bar} from './b'; console.log('a.mjs'); console.log(bar()); function foo() { return 'foo' } export {foo}; // b.mjs import {foo} from './a'; console.log('b.mjs'); console.log(foo()); function bar() { return 'bar' } export {bar};
-
-