ES6

版本

兼容性

IE7~11 基本不支持 ES6。

ES 6

  • let const
  • 箭头函数
  • 模块化
  • Promise
  • 面向对象
  • 数组解构赋值

ES 7

  • 幂运算符(**)
  • 数组扩展:Array.prototype.includes() 用来判断一个数组是否包含一个指定的值

ES 8

  • async / await
  • 字符串扩展
  • 共享内存和Atomics:允许您在多个 workers 和主线程之间共享 SharedArrayBuffer 对象的字节
  • Object.values / Object.entries
  • String padding
  • Object.getOwnPropertyDescriptors()
  • 函数参数列表和调用中的尾逗号

ES 9

  • 对象解构赋值
  • 异步迭代
  • Rest/Spread 属性
  • 新的正则表达式功能
  • Promise.prototype.finally()
  • 模板字符串修改

ES 10

  • 扩展对象
  • 数组方法
  • 行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配
  • 更加友好的 JSON.stringify
  • 新增了Array的flat()方法和flatMap()方法
  • 新增了String的trimStart()方法和trimEnd()方法
  • Object.fromEntries()
  • Symbol.prototype.description
  • String.prototype.matchAll
  • Function.prototype.toString()现在返回精确字符,包括空格和注释
  • 简化try {} catch {},修改 catch 绑定
  • 新的基本数据类型BigInt
  • globalThis
  • import()
  • Legacy RegEx
  • 私有的实例方法和访问器

Let

一般 var 没有块级作用域。

let不可重复声明,支持块级作用域,且不存在变量提升。

const

修改其元素和属性,不算对常量的修改。

解构赋值

解构赋值是对赋值运算符的扩展。

例如:

1
2
3
4
5
6
7
let [a, b, c] = [1, 2, 3];
let [a, [[b], c]] = [1, [[2], 3]];
let [a, , b] = [1, 2, 3]; // 忽略 2
let [a = 1, b] = []; // b undefined
let [a, ...b] = [1, 2, 3]; // b [2, 3]
let [a, b, c, d, e] = 'hello';
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };

Symbol

是一种新的数据类型,Symbol 表示独一无二的值,最大的用法是用来定义对象的唯一属性名。一个对象的键只能是字符串或Symbol。

1
2
3
4
let sy = Symbol("key1");
let syObject = {};
syObject[sy] = "kk"; // 只能是方括号形式,不可用点
console.log(syObject);

不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。

如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

Symbol 也可以定义常量。

1
2
3
const COLOR_RED = Symbol("red");
const COLOR_YELLOW = Symbol("yellow");
const COLOR_BLUE = Symbol("blue");

其他方法:

1
2
3
4
// 搜索全局symbol
Symbol.for()
// 返回一个已登记的symbol值的key
Symbol.keyFor()

迭代器

支持:

  • Array
  • Arguments
  • Set
  • Map
  • String
  • TypedArray
  • NodeList
1
2
3
for (let [key, val] of map){
// ...
}

适用于自定义遍历数据。

生成器

使用yield关键字实现的函数。

1
2
3
4
5
6
7
8
9
function * fn(arg) {  // 要有 * 号,表示是生成器
// 接收到的参数 = 返回的值
let param1 = yield "1";
let param2 = yield "2";
let param3 = yield "3";
}

let iterator = fn();
iterator.next(param) // 生成

Map Set

Map 是键值对:

  • 键可以是任何值。
  • 添加的键是有序的。
  • 可以获取键值对数。
1
2
3
var map = new Map();
map.set(key, "")
map.get(key)

遍历

1
2
3
4
5
6
for (var [key, val] of map){
// ...
}
map.forEach(function (val, key){
// ...
}, map)

Set 对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。

1
2
3
4
5
6
7
let set = new Set();

set.add(1)
// 交集
var ix = new Set([...a].filter(x => b.has(x)))
// 差集
var ix = new Set([...a].filter(x => !b.has(x)))

Reflect Proxy

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

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
27
28
29
30
31
32
33
34
35
36
37
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
},
// 用于拦截函数的调用、call 和 reply 操作。
// target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。
apply: function(target, ctx, args){
console.log('handle apply');
return Reflect.apply(...arguments);
},
// 用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。此方法不拦截 for ... in 循环。
has: function(target, propKey){
console.log("handle has");
return propKey in target;
},
// 用于拦截 new 命令。返回值必须为对象。
construct: function (target, args, newTarget) {
console.log('handle construct')
return Reflect.construct(target, args, newTarget)
}
// 用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。
deleteProperty: null,

}

let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set

其他操作

ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。

Reflect 对象对某些方法的返回结果进行了修改,使其更合理。

Reflect 对象使用函数的方式实现了 Object 的命令式操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 receiver
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
Reflect.set(target, name, value, receiver)
Reflect.has(obj, name)
Reflect.deleteProperty(obj, property)
Reflect.construct(obj, args)

实现观察者模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义 Set 集合
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
// observable 返回原始对象的代理,拦截赋值操作
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;
}

字符串

新加入反引号用法

1
2
3
4
5
6
7
8
9
10
const str = `
<ul>
<li>a</li>
<li>${user}</li>
</ul>
`

// 清除空格
str.trimStart()
str.trimEnd()

数组

常用方法

1
2
3
4
5
6
7
8
const arr = ['aa', 'bb', 'cc']
if (arr.includes('bb')) {
// ...
}

// 扁平化数组
arr.flat(2) // 参数为深度
arr.flatMap()

函数

箭头函数

1
2
3
const fn = () => {

}

默认参数

1
function add (a, b, c=10) {}

rest 参数,用于代替arguments

1
2
3
4
5
6
7
function fn() {
console.log(arguments); // 对象
}

function fn(...args) {
console.log(args); // 数组
}

可选链操作符

1
2
const name = person.stu.name // 如果stu 是undefined,那么会报错
const name = person?.stu?.name // 不会报错,得到undefined

对象

允许直接插入变量

1
2
3
4
const person = {
name, // ES6
age: 10
}

解对象操作

1
const p = ...person

方法扩展

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
27
28
29
30
31
// 判断是否相等
Object.is(NaN === NaN)
// 对象合并
Object.assign(obj1, obj2)
// 设置为属性
Object.setPrototypeOf(obj1, obj2)

// 获取键、值、键值对
Object.keys(stu)
Object.values(stu)
Object.entries(stu) // 得到 二维数组
// 创建对象
Object.create(null, {
name: {
value: '',
writable: true,
configurable: true,
enumerable: true
}
})
// 获取描述对象,上面的那个,用于对象克隆
Object.getOwnPropertyDescriptors(stu)

// 创建新对象,与entries互为逆运算
Object.fromEntries([
['name', '123'],
['age', 123]
])
Object.fromEntries(new Map())


ES 11 的私有属性

1
2
3
4
class Person {
// # 开头的属性是私有的
#age
}

全局this

1
globalThis

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
class Person {
static count = 10;
constructor(name, age) {
this.name = name
this.age = age
}
call() {
return this.name
}
get height() {
return '10m'
// person.height 被读取
}
set height(val) {
// person.height 被修改
}
}

class Student extends Person {
constructor(name, age) {
super(name, age)
}
}

const stu = new Student('jack', 10)

模块

当需要暴露数据时,只需要在前面写export

1
2
export const str = '123'
export function test() {}

或者

1
2
3
4
5
6
7
8
export {
str,
test
}

export default {
custom: function() {}
}

当引入时

1
2
3
4
<script type="module">
import * as m from './src/js/m.js'
import { str } as m from './src/js/m.js'
</script>

动态引入

1
import() // 得到 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
const str = '<a href="www.baidu.com">baidu</a>'
// 创建正则表达式,包含两个捕获分组
const reg = /<a href="(.*)">(.*)<\/a>/
// 执行
const result = reg.exec(str)
// result 为一个数组
// 0 - str
// 1 - 第一个捕获
// 2 - 第二个捕获

// 还可以为分组命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/
const result = reg.exec(str)
const text = result.groups.text
const url = result.groups.url

// 反向断言

// dotAll 模式 正则串末尾加 s
// 此时 . 表示任意字符,包括换行符
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/s

// matchAll 末尾加 g
// 得到可迭代对象

异步对象

Promise

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

创建:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});

// 对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then(onFulfilled, onRejected)

// 或
promise.then(onFulfilled).catch(onRejected)

// 链式操作
// 第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// proceed
});

// 创建多个,且必须都成功
// 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
// 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
var p = Promise.all([p1,p2,p3]);

// 创建多个,只要有一个成功
// 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
var p = Promise.race([p1,p2,p3]);

// 肯定返回成功,所有都完成了
var p = Promise.allSettled([p1, p2])

// 将现有对象转为Promise对象
var jsPromise = Promise.resolve($.ajax('/whatever.json'));

var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});

var p = Promise.reject('出错了');
p.then(null, function (s){
console.log(s)
});

Ajax使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ajax(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var URL = "/try/ajax/testpromise.php";
ajax(URL).then(function onFulfilled(value){
document.write('内容是:' + value);
}).catch(function onRejected(error){
document.write('错误:' + error);
});

Async / Await

async/await是基于Promise实现的,它不能用于普通的回调函数。

async可以修饰一个函数,这个函数里面可以使用await关键字。

await的语义是:必须等到await后面跟的Promise有了返回值,才能继续执行await的下一行代码。但是await是非阻塞的。

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
27
28
async function fn()
{
let str = await readFile("./a.txt")
if(str){
// 失败的话,要用 try catch 捕获
let fr2 = await readFile(str)
if(fr2){
console.log(fr2)
}
}
}

// 返回 成功的Promise
async function fn()
{
return '123'
}
// 返回失败的 Promise
async function fn()
{
throw new Error('error')
}
async function fn()
{
return new Promise((resolve, reject) => {
reject('error')
})
}

TypeScript

Webpack

ES 6 建议使用Webpack。webpack 是一个现代 JavaScript 应用程序的静态模块打包器 (module bundler) 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图 (dependency graph) ,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle 。

概念

webpack 主要有四个核心概念:

  • 入口 (entry)
  • 输出 (output)
  • loader
  • 插件 (plugins)

入口

入口会指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。入口的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 单个入口
const config = {
entry: "./src/main.js"
}

// 对象
const config = {
app: "./src/main.js",
vendors: "./src/vendors.js"
}

// 输出
const config = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
}
}

loader

loader 让 webpack 可以去处理那些非 JavaScript 文件( webpack 自身只理解 JavaScript )。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 通过 loader 将 ES6 的语法转为 ES5 
const config = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: [
presets: ["env"]
]
}
]
}
}

插件

loader 被用于转换某些类型的模块,而插件则可以做更多的事情。包括打包优化、压缩、定义环境变量等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 通过 npm 安装
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 用于访问内置插件
const webpack = require('webpack');

const config = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

搭建应用webpack.config.js

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
const path = require('path');

module.exports = {
mode: "development", // "production" | "development"
// 选择 development 为开发模式, production 为生产模式
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: [
presets: ["env"]
]
}
]
},
plugins: [
...
]
}

gulp

gulp 是一个基于流的自动化构建工具,具有易于使用、构建快速、插件高质和易于学习的特点,常用于轻量级的工程中。

在项目根目录下创建名为 gulpfile.js 的文件:

1
2
3
4
5
6
7
8
const gulp = require('gulp');
const uglify = require("gulp-uglify");
// default 表示一个任务名,为默认执行任务
gulp.task('default', function() {
gulp.src('./src/main.js')
.pipe(uglify())
.pipe(gulp.dest('./dist'));
})

使用

安装Webpack,首先要安装node.js。

node.js 官网

1
2
3
4
5
6
7
# 安装
cnpm install webpack -g
# 创建应用
mkdir app
# 编辑文件
touch app/app.js
touch app/index.html
1
document.write("It works.");
1
2
3
4
5
6
7
8
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
</body>
</html>

然后打包应用:

1
2
3
webpack app.js bundle.js
# 或编辑配置文件后
webpack

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var webpack=require('webpack');

module.exports = {
entry: "./app.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style-loader!css-loader" }
]
},
plugins:[
new webpack.BannerPlugin('菜鸟教程 webpack 实例')
]
};

JSX