ES6笔记
本文最后更新于:2022年9月21日 晚上
运算符的拓展
1.链式判断运算符 ?.
1 | |
eg:
1 | |
总结:
1 | |
注意:如果a?.b()里面的a.b有值,但不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是null或undefined,但也不是函数,那么a?.()会报错。
Symbol
1.为了解决的问题
对象的属性名称都是字符串,特别是自己定义的时候,特别容易冲突。
2.含义
Symbol()代表唯一的值
3.机制
如果Symbol()的参数是一个对象,就会调用改对象的toString()方法,将其转为字符串,然后生成一个Symbol值
eg:
1 | |
总结
1 | |
应用
1 | |
Promise与顶层await
1.什么是Promise
在Javascript中表示期约,说白了就是当想获取的数据需要花时间计算时候,“等”这个计算完成,再执行后面的代码,避免后面拿值拿不到。
2.await
- await用于等待一个
Promise对象,只能在异步函数async function中使用- 首先为什么是一个
Promise对象,因为await我理解为一个标志,标志后面的函数需要返回Promise对象 - 如果表达式的值不是
Promise对象,await会把该值转换为已经正常处理的Promise结果并返回 - 通过
Promise对象实现异步
- 首先为什么是一个
3.顶层await
ES6之后,可以单独使用
await? ==存疑==旧版本在一个async函数外单独使用
await会引起错误,常用立即执行函数表达式(IIFE)1
2
3
4
5
6
7await Promise.resolve('ok') //错误用法
(async()=>{
await Promise.resolve(
console.log('ok')
)
}) //正确用法
顶层
await想解决的问题: ES6模块化中导入导出,防止模块之间相互访问时异步访问无值的问题。看下面一个例子 来源为何要在Javascript中使用顶层await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module" src="./main.js"></script>
<!-- 注意这边的module 像浏览器解释js脚本的风格 -->
</body>
</html>1
2
3
4
5
6
7
8
9//------ library.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diagonal(x, y) {
return sqrt(square(x) + square(y));
}
//暴露具体的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//------ middleware.js ------ 中间件
import { square, diagonal } from './library.js';
console.log('From Middleware');
let squareOutput;
let diagonalOutput;
// IIFE
(async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
function delay(delayInms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('❤️'));
}, delayInms);
});
}
//中间件中计算并暴露值
export {squareOutput,diagonalOutput};1
2
3
4
5
6
7
8
9
10
11
12//------ main.js ------
import { squareOutput, diagonalOutput } from './middleware.js';
console.log(squareOutput); // undefined
console.log(diagonalOutput); // undefined
console.log('From Main');
setTimeout(() => console.log(squareOutput), 2000);
//169
setTimeout(() => console.log(diagonalOutput), 2000);
//13- 问题:
main.js中第一次打印,读取到了中间件中暴露出来的值,但中间件中线程并没有计算完成。
- 问题:
两种解决方案
计算值的时候用一个
Promise作为总的判断依据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//------ middleware.js ------ 中间件
import { square, diagonal } from './library.js';
console.log('From Middleware');
let squareOutput;
let diagonalOutput;
// IIFE 用export把IIFE返回的Promise暴露出去
// 用该Promise作为判断函数是否执行完的标准
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
function delay(delayInms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('❤️'));
}, delayInms);
});
}
//中间件中计算并暴露值
export {squareOutput,diagonalOutput};1
2
3
4
5
6
7
8
9
10//------ main.js ------
import promise,{squareOutput,diagonalOutput} from './middleware.js';
promise.then(()=>{
console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log('From Main');
setTimeout(() => console.log(squareOutput), 2000);// 169
setTimeout(() => console.log(diagonalOutput), 2000);// 13
})- 可行,但带来了新的问题
main.js调用中middleware.js中暴露出来的变量没有问题- 但是如果有另一个模块
B.js需要调用main.js,那么就需要再在main.js中使用一次和middleware.js中间件中一样的IIFE Promise才能让B.js正确访问变量。
- 可行,但带来了新的问题
用导出的变量去resolve
IIFE promise将变量作为
IIFE promise的返回值返回1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//------ middleware.js ------ 中间件
import { square, diagonal } from './library.js';
console.log('From Middleware');
let squareOutput;
let diagonalOutput;
// IIFE
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
return { squareOutput, diagonalOutput } //作为返回值返回
})();
function delay(delayInms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('❤️'));
}, delayInms);
});
}
export { squareOutput, diagonalOutput };1
2
3
4
5
6
7
8
9
10
11
12import promise, { squareOutput, diagonalOutput } from './middleware.js';
//解析返回值,返回值其实就包含Promise的状态(已完成)
promise.then(({squareOutput,diagonalOutput}) => {
console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log('From Main');
setTimeout(() => console.log(squareOutput), 2000);
//169
setTimeout(() => console.log(diagonalOutput), 2000);
//13
})- 问题:大部分函数模块都放在了
then()中,缺乏动态性和灵活性
- 问题:大部分函数模块都放在了
顶层
await允许我们让模块系统去处理
Promise之间的关系1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//------ middleware.js 中间件函数------
import { square, diagonal } from './library.js';
console.log('From Middleware');
let squareOutput;
let diagonalOutput;
//使用顶层 await
await delay(1000);
//await 标志 等待delay执行完之后,才会生成squareOutput
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
function delay(delayInms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('❤️'));
}, delayInms);
});
}
export {squareOutput,diagonalOutput};1
2
3
4
5
6
7
8
9
10//------ main.js 主函数------
import { squareOutput, diagonalOutput } from './middleware.js';
console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log('From Main');
setTimeout(() => console.log(squareOutput), 2000);// 169
setTimeout(() => console.log(diagonalOutput), 2000); // 13注意:顶层
await只在ES模块中生效,这些ES模块之间必须具备某种相互依赖关系总结:
- 顶层
await顾名思义,在某个代码块的顶层,下面的代码块全需要等它。 - 在
await函数的Promise被resolve之前,await下所有的代码都不会执行。 - 而如果仅仅在一个函数外面使用
await,函数外面export的东西,外部也会读到
- 顶层
Generator函数
1.遍历 Iterator
任何数据结构只要部署
Iterator接口,就可以完成遍历操作作用
- 为各种数据结构提供一个统一的、简便的数据访问接口
- 使得数据结构成员能够按照某种次序排列
- 供
for of使用
遍历过程:
- 创建一个指针对象,指向起始位置(遍历器对象的本质是一个指针对象)
- 第一次调用指针对象的
next方法,可以将指针指向数据结构的第一个成员。 - 第二次调用指针对象的
next方法,可以将指针指向数据结构的第二个成员。 - 不断调用指针对象的
next方法,直到指向数据结构的结束位置
返回值
{value: data , done: boolean}data是当前属性的值,done属性表示遍历是否结束一个函数只要部署了`
Symbol.iterator属性,就被视为具有iterator接口for of循环,内部调用的就是Symbol.iterator方法常见的具有
iterator接口的结构- 数组
- Set和Map结构
- 计算生成的数据结构(ES6数组、Set、Map)
entries()keys()values()
1
2
3
4let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}类似数组的对象
- 字符串
- DOM Nodelist对象
- arguments对象
注意:并不是所有的类数组对象都有Iterator接口,需要使用
Array.from方法将其转化为数组对象
普通对象,
for in可以遍历键名,for of循环会报错如果要用
for of用
Object.keys方法将对象的键名生成一个数组1
2
3for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}用
Generator函数将对象重新包装一下1
2
3
4
5
6
7
8
9
10
11
12
13
14const 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
2.Generator函数
写法
1
2
3
4
5
6function* generator(){ //函数名前,fucntion后有星号
yield 'hello'
return 'ending'
}
var gen = generator()机制
调用该函数后,返回一个指向内部状态的指针对象(
Iterator对象)调用遍历器的
next()方法,使指针指向下一个状态- 第一次调用,Generator
函数开始执行,直到遇到第一个
yield表达式为止 yield表达式- 是一种暂停标志,
next()方法遇到yield表达式就暂停后面的操作 - 将紧跟在
yield后面的表达式的值作为返回对象的value值
- 是一种暂停标志,
- 下一次调用
next方法时,内部指针从函数头部或者上一次停下来的地方再继续往下执行,直到遇到下一个yield表达式。 - 如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到return语句为止 - 并将
return语句后面的表达式的值,作为返回的对象的value属性值。
- 第一次调用,Generator
函数开始执行,直到遇到第一个
next()方法中的参数- 该参数会被当成上一个
yield表达式的返回值 - 通过该参数,就能在
Generator()函数运行后,继续向该函数中注入值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function* 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}
// 第一次运行,x值为5 返回var y = 2 * (yield (x + 1))中 yield后面的(x+1),返回值为6
a.next()
// Object{value:NaN, done:false}
// 第二次运行,next()中不带参数,undefined 返回var y = 2 * undefined,返回值为NaN
a.next()
// Object{value:NaN, done:true}
// 第三次运行,next()中不带参数,undefined 返回var z = undefinedw;,返回值为NaN
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }解释:
上面代码中,第二次运行
next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。如果向
next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。- 该参数会被当成上一个
ES6中的Module模块
1. 注意是ES6模块中的不是Nodejs模块中的CommonJS规范
module.exports、exports、export都是导出,有何区别?
2. ES6中export
ES6模块不是对象,而是通过
export命令显式指定输出的代码,再通过import命令输入。可以输出变量、函数和类
1
2
3
4
5
6
7
8
9
10
11
12//变量
var firstName = 'Lingyi';
var lastName = 'Zhu';
export {firstName,lastName}
export var firstName ='Lingyi';
export var lastName = 'Zhu';
//函数
export function multiply(x,y){
return x*y;
};通常情况下,
export输出的变量是原本的名字,但是可以用as关键字来重新命名1
2
3
4
5
6
7
8function v1(){...}
function v2(){...}
export{
v1 as streamV1;
v2 as streamV2;
v2 as streamLatestVersion; //v2可以使用不同的名字输出两次
}export模块暴露的接口必须与内部元素建立起一一对应的关系1
2
3
4
5
6
7
8
9export 1;//报错
export var m =1;//正确
var m =1;
export m;//报错
export {m};//正确
var n = 1;
export {n as m}; //正确,用m来接收关于整体暴露的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//temp.js
export function plus(x,y){
return x+y;
}
eport function multi(x,y){
return x*y;
}
//main.js
import {plus,multi} from 'temp.js'
let a = plus(1,2);
let b = multi(1,2);
//也可以写为
import * as Math from 'temp.js'
let a = Math.plus(1,2);
let b = Math.multi(1,2);关于动态绑定,
export接口与对应的元素是动态绑定关系,可以通过该接口获取该元素内部实时、后面变化的值1
2export var foo = '没变之前就先暴露出去';
settimeout(()=>{foo='暴露之后变化成现在这个值,5秒后别的文件读也会变成这个值'},5000);关于继承
1
2
3
4
5
6
7
8
9
10
11
12
13//circleplus.js
export * from 'circle';
export var e = 2.7;
export default function(x){
return Math.exp(x);
}
export {area as circleArea} from 'circle';
//main.js
import * as math from 'circleplus';
import exp from 'circleplus'; //将circleplus模块中的默认方法加载为exp方法
console.log(exp(math.e));关于
export default- 为了用户快速上手,可以不命名元素
- 本质是系统自动给
default后面的元素赋值为default - 用户可以
import时,设置元素的名称 - 用于指定模块的默认输出,一个模块只能有一个默认输出,因此
export default模块只能使用一次
关于
const变量跨模块1
2
3
4
5
6
7
8
9
10
11
12
13
14//constants.js
export const A = 1;
export const B = 2;
export const C = 3;
//test1.js 写法1
import * as constants from 'constants.js'
console.log(constants.A); //1
console.log(constants.B);//2
//test2.js 写法2 加载单一的输出项
import {A,C} from 'constants.js.'
console.log(A);//1
console.log(B);//3在使用的变量非常多的时候,常常专门建立一个
constants目录,将常量写在歌中不同的文件里,然后合并在index.js中1
2
3
4
5
6
7
8
9
10//本代码块包含两个文件的代码,分别是db.js和user.js
//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'];1
2
3//index.js 将上述两个js文件中的常量合并
export {db} from 'db.js'
export {users} from 'users.js' //跨模块常量的引入,带{}export可以出现在任何位置,只要处于模块顶层就行。在块级作用域中则会报错。
3.ES6中import
返回一个
Promise对象,需要用.then()方法指定处理函数,推荐使用await方法1
2
3
4
5
6
7
8
9
10
11
12
13
14async function renderWidget() {
//async封装Promise,下面await
const container = document.getElementById('widget');
if (container !== null) {
// 等同于
// import("./widget").then(widget => {
// widget.render(container);
// });
const widget = await import('./widget.js'); //await相当于Promise中的then(),所以前面说等同于
widget.render(container);
}
}
renderWidget();import按需加载,什么时候需要,什么时候加载
条件加载,可以放在
if模块中允许加载模块的路径是动态生成的
import(function(var))加载成功后,该模块会当作一个对象,作为
.then()的参数1
2
3
4
5//如果有default模块的话
import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});加载方式
- ```html
1
2
3
4
5
6
+ ```html
<script type='module'>
import foo from 'foo.js'
...
</script>
- ```html
4. ES6模块和Common JS模块的差异
- ES6模块是对外接口的一种静态定义,在代码静态解析阶段就会完成
- ES6模块输出的是值的引用,模块中变化了输出也变化了。
- Common JS模块加载的是一个对象,该对象在脚本运行时才会生成
- Common JS模块输出是一个值的拷贝,内部变化,不会影响已经输出的值。
- 其他区别Module 的加载实现
5.Common JS加载ES6模块
CommonJS模块的
require()不能加载ES6模块,只能用import()1
2
3(async () => {
await import('./my-app.mjs');
})();require()不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await命令,导致无法被同步加载。
6.ES6加载CommonJS模块
ES6需要静态代码分析,而CommonJS输出的是一个对象,无法被静态解析
1
2
3
4
5
6
7
8
9// 正确 只能整体加载
import packageMain from 'commonjs-package';
// 报错 不能只加载单一的输出项
import { method } from 'commonjs-package';
//加载输出单一项的写法(折中)
import packageMain from 'commonjs-package';
const { method } = packageMain;