抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文主要学习了ECMAScript的基本知识。

1 简介

1.1 定义

ECMAScript是一种由ECMA组织(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范。

JavaScript是ECMAScript的实现,一般情况下,ECMAScript和JavaScript表达的是同一个意思。

严格来说,JavaScript包含三个部分:

  • ECMAScript:核心,通用标准,简写为ES
  • DOM:文档对象模型,操作网页
  • BOM:浏览器对象模型,操作浏览器

1.2 历史版本

ES5:2009年发布
ES6:2015年发布,也称为ECMA2015
ES7:2016年发布,也称为ECMA2016
ES8:2017年发布,也称为ECMA2017
ES9:2018年发布,也称为ECMA2018
ES10:2019年发布,也称为ECMA2019

2 ES5

2.1 严格模式

在ES5中新增了严格模式,其作用是:

  • 使得JS在更严格的条件下运行
  • 消除JS语法的一些不合理、不严谨之处,减少一些怪异行为
  • 消除JS代码运行的一些不安全之处,保证代码运行的安全

使用方式是在JS的第一行:

js
1
'use strict';

需要记住的几个变化:

  • 声明定义变量必须用var关键字:
    js
    1
    2
    'use strict';
    age = 18;// 页面报错
  • 禁止自定义的函数中的this关键字指向全局对象:
    js
    1
    2
    3
    4
    5
    'use strict';
    function Person(age) {
    this.age = age
    }
    Person(18);// 页面报错
  • 创建eval作用域:
    js
    1
    2
    3
    4
    'use strict';
    var age = 16;
    eval('var age = 18;');// 使用eval()方法可以执行传入的命令,有风险,不建议使用
    console.log(age);// 16 只在eval作用域有效,不会污染全局变量

2.2 JSON支持

JSON是一种轻量级的数据交换模式,用于传输数据。

ES5提供了对JSON的支持:

js
1
2
3
4
5
6
7
8
9
10
11
var obj = {
age: 18
}
console.log(obj);// {age: 18}
console.log(typeof obj);// object
var jsonStr = JSON.stringify(obj);
console.log(jsonStr);// {"age":18}
console.log(typeof jsonStr);// string 将对象或数组转为JSON字符串
var jsonObj = JSON.parse(jsonStr);
console.log(jsonObj);// {age: 18}
console.log(typeof jsonObj);// object 将JSON字符串转为对象或数组

2.3 Object扩展

增加了一种创建对象的方式,支持通过将指定对象作为原型的方式创建对象:

js
1
2
3
4
5
6
7
var obj = {
age: 18
}
var newObj = {}
console.log(newObj.__proto__);// {__proto__: null}
newObj = Object.create(obj);
console.log(newObj.__proto__);// {age: 18}

增加操作属性的方法:

js
1
2
3
4
5
var obj = {
age: 18
}
Object.defineProperty(obj, "age", {value : 16})
console.log(obj.age);// 16

2.4 Array扩展

查询元素在数组中的下标:

js
1
2
3
var arr = [1, 2, 3, 2, 1];
console.log(arr.indexOf(2));// 1 指定元素在数组中首次出现的下标
console.log(arr.lastIndexOf(2));// 3 指定元素在数组中最后出现的下标

遍历数组:

js
1
2
3
4
var arr = [1, 2, 3, 2, 1];
arr.forEach(function (item, index) {
console.log("[" + index + "]", item);
});

对数组进行处理:

js
1
2
3
4
5
var arr = [1, 2, 3, 2, 1];
var newArr = arr.map(function (item, index) {
return item + 10
});
console.log(newArr);

对数组进行过滤:

js
1
2
3
4
5
var arr = [1, 2, 3, 2, 1];
var newArr = arr.filter(function (item, index) {
return item > 1
});
console.log(newArr);

2.5 Function扩展

将函数的this对象修改为指定的对象:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var age = 18;
function fun(age) {
this.age = age;
console.log(this + ' age = ' + this.age);
}
fun(20);// [object Window] age = 20
console.log(age);// 20
var obj = {};
// 修改方法的this对象,返回新方法
var newFun = fun.bind(obj, 12);
console.log(obj.age);// undefined
console.log(obj);// {}
// 执行新方法
newFun();// [object Object] age = 12
console.log(obj.age);// 12
console.log(obj);// {age: 12}

也可以直接执行新方法,需要在方法后面增加(),相当于call()方法:

js
1
2
3
4
5
6
7
8
9
10
11
var age = 18;
function fun(age) {
this.age = age;
console.log(this + ' age = ' + this.age);
}
fun(20);// [object Window] age = 20
console.log(age);// 20
var obj = {};
// 修改方法的this对象,并执行新方法,相当于fun.call(obj, 16)
fun.bind(obj, 16)();// [object Object] age = 16
console.log(obj);// {age: 16}

3 ES6

3.1 声明变量

使用let关键字声明变量:

方式 作用域 重复声明 声明提升
使用var关键字声明变量 属于全局变量 可以重复声明 存在变量提升
使用let关键字声明变量 属于局部变量,会创建一个块级作用域 不能重复声明 不会预处理,不存在提升

使用const关键字声明的变量被称为常量,和let关键字类似,唯一的区别是不支持修改变量的值。

为了更好的区分常量,其标识符一般使用大写。

3.2 解构赋值

按照一定模式,从数组或者对象中提取值,对变量进行赋值,这被称为解构赋值。

对象解构可以同时获取对象多个属性,但是要求属性名一致,使用{}包裹,按属性赋值:

js
1
2
3
4
5
6
7
8
let obj = {
age:18,
sex:'男'
};
let {age, sex} = obj;// 获取多个属性
console.log(age, sex);// 18 '男'
let {age: objAge, sex: objSex} = obj;// 解构同时使用别名
console.log(objAge, objSex);// 18 '男'

数组解构可以同时获取多个元素,对变量名没有要求,使用[]包裹,按下标顺序赋值:

js
1
2
3
let arr = [1, 2, 3, 2, 1];
let [a, b, , , c, d] = arr;
console.log(a, b, c, d);// 1 2 1 undefined

3.3 模板字符串

使用模板字符串可以简化字符串的拼接,支持在字符串中使用变量、对象、表达式、方法的返回值。

对于模板字符串需要使用``包裹,在字符串中的模板需要使用${}包裹:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
age:18,
sex:'男'
};
console.log(`个人信息:
年龄:${obj.age}
性别:${obj.sex}
以上数据真实有效`);// 支持换行
function fun() {
return "test";
}
console.log(`获取返回值:${fun()}`)// 支持获取返回值
let arr = [1, 2, 3, 2, 1];
console.log(`数组内容:${arr}`);// 支持变量和数组

3.4 简化对象

在定义对象时,可以直接写入变量和函数作为对象的属性方法,这样的书写更加简洁:

js
1
2
3
4
5
6
7
8
9
let age = 18;
let sex = "男";
let test = function() {console.log("test")}
let obj = {
age,
sex,
test
};
console.log(obj)// {age: 18, sex: '男', test: ƒ}

3.5 箭头函数

使用() => {}箭头函数简化了函数的定义和调用,特点:

js
1
2
3
4
5
6
7
8
9
10
// 没有参数,不能省略箭头左侧的()符号,一条语句,可以省略箭头右侧的`{}`符号
let fun1 = () => console.log("test");
fun1();// test
// 一个参数,可以省略箭头左侧的`()`符号,一条语句,可以省略return关键字,默认将结果作为返回值
let fun2 = str => "str = " + str;
console.log(fun2("test"));
fun2('hello');// str = test
// 多个参数,不能省略箭头左侧的()符号,多条语句,不能省略箭头右侧的`{}`符号,不能省略return关键字
let fun3 = (age,sex) => {let obj = {age, sex}; return obj};
console.log(fun3(18,"男"));// {age: 18, sex: '男'}

3.6 扩展运算符

扩展运算符就是使用...变量名作为变量,在特定场景下具有收集和扩展的功能。

使用扩展运算符的收集功能可以实现可变参数,必须将可变参数放在参数列表最后的位置:

js
1
2
3
4
function fun(a,b,...c) {
console.log(a,b,c);
}
fun(1,2,3,4,5);// 1 2 [3, 4, 5]

使用扩展运算符的扩展功能可以更加方便的操作数组:

js
1
2
3
4
let arr1 = [1,2,3];
let arr2 = [4,5];
let arr3 = [...arr1,...arr2];
console.log(arr3);// [1, 2, 3, 4, 5]

还可以将伪数组转为真数组:

js
1
2
3
4
let home = document.getElementsByTagName('div');
let arr = [...home];
console.log(home);// HTMLCollection [div]
console.log(arr);// [div]

3.7 参数默认值

在定义函数时,支持设置参数的默认值,当没有传入参数时,会使用定义的默认值:

js
1
2
3
4
function fun(str = "test") {
console.log("hello", str)
}
fun();// hello test

3.8 Symbol

新增Symbol原始数据类型,用于解决命名冲突的问题,主要用于需要保证属性名唯一的场景。

Symbol属于第七种数据类型,其他六种是:Object,String,Number,Boolean,Null,Undefined。

创建Symbol类型的数据,因为不是对象,所以不能使用new关键字:

js
1
2
3
let s = Symbol();
console.log(s);// Symbol()
console.log(typeof s);// symbol

支持在创建时传入参数作为标识符,支持其他类型数据,包括对象:

js
1
2
3
4
let s1 = Symbol("test");
console.log(s1);// Symbol(test)
let s2 = Symbol({age:18,sex:"男"});
console.log(s2);// Symbol([object Object])

在ES10中,增加了description属性,用于获取传入的参数:

js
1
2
let s = Symbol("test");
console.log(s.description);// test

即使创建了相同的Symbol数据,两个数据也是不同的,这就是Symbol的唯一性:

js
1
2
console.log(Symbol() == Symbol());// false
console.log(Symbol("test") == Symbol("test"));// false

如果想要创建相同的数据,需要使用Symbol.for()创建:

js
1
2
3
4
console.log(Symbol === Symbol.for());// false
console.log(Symbol.for() === Symbol.for());// true
console.log(Symbol === Symbol.for("test"));// false
console.log(Symbol.for("test") === Symbol.for("test"));// true

通过Symbol.for()创建数据时会先从全局按照参数获取,如果获取到了就返回数据,如果没有获取到就会创建数据并注册到全局。

使用Symbol.keyFor()获取全局中的参数:

js
1
2
let s = Symbol.for("test");
console.log(Symbol.keyFor(s));// test

将Symbol作为对象的属性:

js
1
2
3
4
5
6
7
8
9
let name = Symbol("name");
let obj = {
[name]: "test"// 在创建对象时创建Symbol数据作为属性
};
let sex = Symbol("sex");
obj[sex] = '男';// 给对象手动设置Symbol数据作为属性
let age = Symbol("age");
Object.defineProperty(obj, age, {value : 18});// 通过defineProperty方法设置Symbol数据作为属性
console.log(obj);// {Symbol(name): 'test', Symbol(sex): '男', Symbol(age): 18}

虽然Symbol属性是共有属性,但是在遍历对象时,不会出现在for循环中,也不会被Object的keys()方法和getOwnPropertyNames()方法获取,同样也不会被JSON.stringify()返回。

可以通过Object的getOwnPropertySymbols()方法获取,只能获取Symbol属性:

js
1
2
3
4
5
6
let obj = {
[Symbol("name")]: "test",
[Symbol("sex")]: "男",
age: 18
};
console.log(Object.getOwnPropertySymbols(obj));// [Symbol(name), Symbol(sex)]

也可以使用Reflect.ownKeys()方法获取所有类型的属性名:

js
1
2
3
4
5
6
let obj = {
[Symbol("name")]: "test",
[Symbol("sex")]: "男",
age: 18
};
console.log(Reflect.ownKeys(obj));// ['age', Symbol(name), Symbol(sex)]

3.9 Promise

引入Promise用于解决异步编程的问题,使用构造函数封装异步操作并可以获取其成功或失败的结果。

在执行完成后,Promise的状态会变成fulfilled(成功)或者rejected(失败),这种改变称为resolved(定型),并且一旦发生定型,结果就不会再次发生改变。

创建对象:

js
1
2
3
4
5
6
7
8
9
10
11
12
let promise = new Promise(function(resolve, reject) {
// 异步操作
let value;// 返回值
let error;// 错误
let result = true;// 异步操作结果

if (result){
resolve(value);// 异步操作成功
} else {
reject(error);// 异步操作失败
}
});

执行异步操作:

js
1
2
3
4
5
p.then(function (value){
console.log(value);// 异步操作成功执行的方法
}, function (error){
console.log(error);// 异步操作失败执行的方法
});

捕获异步操作中的异常:

js
1
2
3
p.catch(function (reason){
console.log(reason);// 异步操作出现异常的处理,包括then方法中出现的异常
});

3.10 Iterator

迭代器Iterator是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署了Iterator接口就可以完成遍历操作。

可以使用新的遍历命令for...of循环,部署了Iterator接口的数据接口可以通过for...of循环进行遍历。

for...in循环相比,通过for...of循环的是value键值,而for...in循环的是index键名。

原生具备Iterator接口的数据,即不需要改动就可以使用for...of循环的有:Array、Arguments、Set、Map、String、TypedArray、NodeList。

非原生的数据可以通过自定义Iterator接口使用for...of循环。

原生Iterator接口:

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
let names = ['张三', '李四', '王五'];
// for...in
for (let index in names) {
// 0 张三
// 1 李四
// 2 王五
console.log(index + " " + names[index]);
}
// for...of
for (let value of names) {
// 张三
// 李四
// 王五
console.log(value);
}
// iterator.next()
let iterator = names[Symbol.iterator]();
for(var item = iterator.next(); item.done != true; item = iterator.next()) {
// {value: '张三', done: false}
// {value: '李四', done: false}
// {value: '王五', done: false}
console.log(item);
}
// {value: undefined, done: true}
console.log(item);

自定义Iterator接口:

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
27
28
29
30
31
32
33
34
35
36
37
38
let person = {
name: "张三",
home: ['北京市', '北京市', '东城区'],
[Symbol.iterator](){
// 索引变量
let index = 0;
// 保存this
let _this = this;
return {
next: function (){
if(index < Object.keys(_this).length){
let result = {
value: _this[Object.keys(_this)[index]],
done: false
}
// 下标自增
index ++;
// 返回结果
return result;
}else {
return {
value: undefined,
done: true
}
}
}
}
}
}
// iterator.next()
let iterator = person[Symbol.iterator]();
for(var item = iterator.next(); item.done != true; item = iterator.next()) {
// {value: '张三', done: false}
// {value: Array(3), done: false}
console.log(item);
}
// {value: undefined, done: true}
console.log(item);

3.11 Generator

生成器Generator是一种异步编程解决方案,语法行为与传统函数完全不同。

在创建时需要使用*符号创建,用于和普通函数区分:

js
1
2
3
function * gen(){
console.log("hello");
}

在调用时返回的是迭代器Iterator对象,所以需要使用next()方法调用:

js
1
2
let iterator = gen();
iterator.next();// hello

在创建时,可以使用yield对函数中的内容进行分割,在调用next()方法执行时,会按照yield分割执行,并返回yield后面的值:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function * gen(){
console.log("hello1");
yield 1
console.log("hello2");
yield 2
console.log("hello3");
}
// hello1
// {value: 1, done: false}
let iterator = gen();
console.log(iterator.next());
// hello2
// {value: 2, done: false}
console.log(iterator.next());
// hello3
// {value: undefined, done: true}
console.log(iterator.next());

在调用时,支持传入参数,需要在方法中接收yield的值:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function * gen(arg){
console.log(arg);
let arg1 = yield 1
console.log(arg1);
let arg2 = yield 2
console.log(arg2);
}
// arg1
// {value: 1, done: false}
let iterator = gen("arg1");
console.log(iterator.next());
// arg2
// {value: 2, done: false}
console.log(iterator.next("arg2"));
// arg3
// {value: undefined, done: true}
console.log(iterator.next("arg3"));

3.12 Set

新增Set对象用于存储不重复的数据列表,默认实现了Iterator接口,支持存储不同类型的数据。

使用:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let s = new Set();
// 使用add()方法添加
s.add(1);
s.add('2');
let arr = [3, 4, 5]
s.add(arr);
console.log(s);// Set(3) {1, '2', Array(3)}
// 使用has()方法判断是否存在
console.log(s.has(arr));// true
// 使用size查询数据个数
console.log(s.size);// 3
// 使用forEach()方法遍历
s.forEach(v => console.log(v));// 1 2 (3) [3, 4, 5]
// 使用delete()方法删除
s.delete(arr);
console.log(s);// Set(2) {1, '2'}
// 使用clear()方法删除所有数据
s.clear();
console.log(s);// Set(0) {size: 0}

3.13 Map

新增Map对象用于存储键值对,默认实现了Iterator接口,支持存储不同类型的键和值。

Map和Object一样,都可以存储对象的属性名和属性值,区别是Object的属性名只能是String类型或Symbol类型,而Map的键可以使任意类型。

使用:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let m = new Map();
// 使用set()方法添加
m.set(1, 1);
m.set('2', '2');
let arr = [3, 4, 5]
m.set(arr, arr);
console.log(m);// Map(3) {1 => 1, '2' => '2', Array(3) => Array(3)}
// 使用get()方法通过键获取值
console.log(m.get(arr));// (3) [3, 4, 5]
// 使用has()方法判断是否存在
console.log(m.has(arr));// true
// 使用size查询键值对个数
console.log(m.size);// 3
// 使用forEach()方法遍历
m.forEach((k, v) => console.log(k + ' = ' + v));// 1 = 1 2 = 2 3,4,5 = 3,4,5
// 使用delete()方法删除
m.delete(arr);
console.log(m);// Map(2) {1 => 1, '2' => '2'}
// 使用clear()方法删除所有数据
m.clear();
console.log(m);// Map(0) {size: 0}

3.14 Class

引入了类的概念,作为对象的模板,需要使用class关键字声明。

使用:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Human {
// 使用constructor关键字声明构造方法
constructor(name) {
this.name = name;
}
// 声明对象方法
hello() {
console.log("我是", this.name);
}
test() {
console.log("父类");
}
home() {
console.log("地球");
}
// 使用static关键字声明静态属性,不能通过对象调用,只能通过类调用
static eat = "食物";
// 通过set关键字定义设置属性的方法
set age(age) {
console.log("设置年龄", age);
}
// 通过get关键字定义获取属性的方法
get age() {
console.log("读取年龄");
return 100;
}
}
// 使用extends可以继承父类,自动拥有父类定义的属性和方法
class Worker extends Human {
// 创建子类的对象方法
work() {
console.log("工作");
}
// 重写父类的方法,默认调用的是子类重写的方法,使用super关键字调用父类的方法
test() {
console.log("子类");
super.home();
}
}
let h = new Human("人类");
h.hello();// 我是 人类
console.log(h.name);// 人类
console.log(h.eat);// undefined
console.log(Human.eat);// 食物
let w = new Worker("工人");
w.hello();// 我是 工人
w.work();// 工作
w.test();// 子类 地球
h.age = 100;// 设置年龄 100
h.age;// 读取年龄

3.15 数值扩展

引入了二进制和八进制的新写法:

js
1
2
console.log(0b100);// 4 二进制以0b开头,二进制100对应十进制4
console.log(0o100);// 64 八进制以0o开头,八进制100对应十进制64

定义最小精度,其值接近于2.22×10^(-16),即2.22乘以10的-16次方。

当无法准确的描述数值时,就会产生精度问题,比如无法通过二进制准确的表示0.1,再比如无法通过十进制表示1/3,等等。

当两个数的差值小于Number.EPSILON时,就认为两个数值是相等的:

js
1
2
3
4
5
console.log(0.1 + 0.2 === 0.3);// false
function equal(left, right) {
return Math.abs(left - right) < Number.EPSILON;
}
console.log(equal(0.1 + 0.2, 0.3));// true

检测数值是否是有限数:

js
1
2
console.log(Number.isFinite(1));// true
console.log(Number.isFinite(1/0));// false

检测数值是否为NaN:

js
1
2
3
console.log(Number.isFinite(1));// true
console.log(Number.isFinite(NaN));// false
console.log(Number.isFinite(null));// false

检测数值是否为整数:

js
1
2
console.log(Number.isInteger(1));// true
console.log(Number.isInteger(1.1));// false

将字符串转为整数和浮点数:

js
1
2
console.log(Number.parseInt("123test456"));// 123
console.log(Number.parseFloat("0.123test456"));// 0.123

检测数值是整数、负数还是零:

js
1
2
3
console.log(Math.sign(10));// 1
console.log(Math.sign(0));// 0
console.log(Math.sign(-10));// -1

将数值的小数部分抹掉:

js
1
console.log(Math.trunc(1.1));// 1

3.16 对象方法扩展

判断两个值是否完全相等:

js
1
2
3
console.log(Object.is(10, 10));// true
console.log(Object.is(NaN, NaN));// true
console.log(NaN === NaN);// false

对象的合并,当旧对象中有新对象中没有的时候,会进行合并,否则会直接覆盖:

js
1
2
3
4
5
6
7
8
9
let oldObj = {
name: '张三',
sex: '男'
}
let newObj = {
name: '李四',
age: 18
}
console.log(Object.assign(oldObj, newObj));// {name: '李四', sex: '男', age: 18}

操作原型对象:

js
1
2
3
4
5
6
7
8
9
10
let human = {
eat: '食物'
}
let worker = {
work: '打工'
}
console.log(worker.__proto__);// {...}
Object.setPrototypeOf(worker, human);
console.log(worker.__proto__);// {eat: '食物'}
console.log(worker.__proto__ == Object.getPrototypeOf(worker));// true

3.17 模块化

3.17.1 说明

模块化是指将一个大的程序文件拆成许多小的文件,然后进行组合。

优势:

  • 防止命名冲突:不同的模块之间可以使用相同名称的变量,彼此互不干扰。
  • 代码复用:模块可以对外提供接口,相同的功能可以使用同一接口。
  • 高维护性:模块之间独立运行,单个模块出现问题不会影响所有模块,模块支持单独升级。

模块化规范比较:

专用于服务器端,默认不支持在浏览器执行。

在服务器端使用,基于Node.js使用。
在浏览器端使用,使用Browserify编译打包JS文件,这个工具也是JS常用的打包工具。

专用于浏览器端,模块的加载时异步的。

在浏览器端使用,基于Require.js使用。

专用于浏览器端,模块的加载时异步的,并且在模块使用时才会加载和执行。

在浏览器端使用,基于Sea.js使用。

专用于浏览器端。

在浏览器端使用,使用Babel将ES6编译为兼容ES5的代码,使用Browserify编译打包JS文件。

3.17.2 导出方式

使用export关键字将变量或方法导出。

分别导出,在JS文件中分别指定导出的变量或方法:

test.js
1
2
3
4
export let hello = "hello";
export function test() {
console.log("test");
}

统一导出,使用export关键字要导出的变量和方法集中导出:

test.js
1
2
3
4
5
let hello = "hello";
function test() {
console.log("test");
}
export {hello, test}

统一导出提供的并不是对象,所以导出的不是对象的简写形式,而是导出的标记,可以认为是名称。

默认导出,将要导出的变量和方法封装为对象:

test.js
1
2
3
4
5
6
7
8
let hello = "hello";
function test() {
console.log("test");
}
export default {
hello,
test
}

默认导出提供的是对象,导出的内容是对象的简写形式,这一点和统一导出不同。

3.17.3 导入方式

使用import关键字将其他模块导出的变量或方法导入进来,同时设置script元素的type属性的值为module表示引入的是模块JS文件,并不是普通的JS文件。

全部导入,使用*将导出的全部变量和方法导入到页面:

test.html
1
2
3
4
5
<script type="module">
import * as test from "./test.js";
console.log(test.hello);
test.test();
</script>

命名导入,指定导入的变量和方法:

test.html
1
2
3
4
5
<script type="module">
import {hello, test} from "./test.js";
console.log(hello);
test();
</script>

使用命名导入可以使用分别导出和统一导出的变量和方法,不能直接使用默认导出的变量和方法。

默认导入,可以使用默认导出的变量和方法:

test.html
1
2
3
4
5
6
7
8
<script type="module">
// import * as test from "./test.js";
// console.log(test.default.hello);
// test.default.test();
import test from "./test.js";
console.log(test.hello);
test.test();
</script>

如果默认导出的只有一个变量或方法,在默认导入时导入的就是该变量或方法,不需要通过对象获取。

如果有大量模块需要引入,可以新建JS文件用于引入模块,然后将此文件引入到HTML文件:

app.js
1
import * as test from "./test.js";

在HTML文件中引入JS文件:

test.html
1
<script src="./app.js" type="module"></script>

4 ES7

4.1 数组增强

判断数组是否包含某个数据:

js
1
2
3
let nums = [1, 2, 3];
console.log(nums.includes(1));// true
console.log(nums.includes(4));// false

4.2 指数操作符

引入**指数运算符,用来实现幂运算,功能与Math.pow结果相同:

js
1
2
console.log(2 ** 4);// 16
console.log(Math.pow(2, 4));// 16

5 ES8

5.1 async和await

5.1.1 async函数

async函数会将返回值封装为Promise对象:

js
1
2
3
4
5
async function fun() {
console.log("fun");
}
let result = fun();
console.log(result);// Promise {<fulfilled>: undefined}

当函数执行成功时返回的是fulfilled状态的Promise对象,当函数执行失败时返回的是rejected状态的Promise对象。

当函数返回的是Promise对象时,不会再次封装,会将返回对象直接返回。

5.1.2 await表达式

await表达式必须写在async函数中,并且await右侧的表达式一般为Promise对象。

await返回的是Promise成功的值,需要使用手动捕获Promise失败的值。

使用:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let promise = new Promise(function(resolve, reject) {
console.log("promise");
if (true){
resolve("success");
} else {
reject("error");
}
});
async function fun() {
console.log("fun");
try {
let result = await promise;
console.log(result);
} catch (e) {
console.log(e);
}
}
let result = fun();
console.log(result);// Promise {<fulfilled>: undefined}

5.2 对象方法扩展

获取对象的属性名和属性值:

js
1
2
3
4
5
6
7
8
9
10
11
let person = {
name: "test",
sex: "男"
};
console.log(Object.keys(person));// ['name', 'sex']
console.log(Object.values(person));// ['test', '男']
console.log(Object.entries(person));// [Array(2), Array(2)]
let map = Object.entries(person);
// ['name', 'test']
// ['sex', '男']
map.forEach(obj => console.log(obj));

获取对象属性信息:

js
1
2
3
4
5
6
7
8
let person = {
name: "test",
sex: "男"
};
// name: {value: 'test', writable: true, enumerable: true, configurable: true}
// sex: {value: '男', writable: true, enumerable: true, configurable: true}
// [[Prototype]]: Object
console.log(Object.getOwnPropertyDescriptors(person));

6 ES9

6.1 Rest和Spread

rest参数与spread扩展运算符在ES6中已经引入,不过ES6中只针对于数组。

使用rest参数:

js
1
2
3
4
5
6
7
8
9
10
11
// function test({name, sex}) {
// console.log(name);
// console.log(sex);
// }
function test({...args}) {
console.log(args);
}
test({
name: "张三",
sex: "男",
});

使用spread扩展运算符:

js
1
2
3
4
5
6
7
8
9
10
11
let attr1 = {
name: "张三"
}
let attr2 = {
sex: "男"
}
let attr3 = {
age: "18"
}
let person = {...attr1, ...attr2, ...attr3}
console.log(person);// {name: '张三', sex: '男', age: '18'}

6.2 正则扩展

6.2.1 捕获分组

ES9允许使用符号?<name>获取捕获结果,可读性更强。

在ES9之前捕获结果,通过下标查看捕获分组,下标为0的元素是完整的匹配内容:

js
1
2
3
4
5
6
7
var reg = /(123)(a.a)/g;
var result;
while(result = reg.exec('123a4a123a5a')) {
// 123 a4a
// 123 a5a
console.log(result[1], result[2]);
}

在ES9之后捕获结果:

js
1
2
3
4
5
6
7
var reg = /(?<first>123)(?<last>a.a)/g;
var result;
while(result = reg.exec('123a4a123a5a')) {
// 123 a4a
// 123 a5a
console.log(result.groups.first, result.groups.last);
}

6.2.2 反向断言

通过正则表达式的?=符号进行正向断言,根据后面的判断是否返回前面的数据:

js
1
2
3
4
let test = '测试123正向456断言';
let reg = /\d+(?=正向)/;// 根据后面的内容是否正向,返回前面的数字
let result = reg.exec(test);
console.log(result[0]);// 123

通过正则表达式的?<=符号进行反向断言,根据前面的判断是否返回后面的数据:

js
1
2
3
4
let test = '测试123反向456断言';
let reg = /(?<=反向)\d+/;// 根据前面的内容是否反向,返回后面的数字
let result = reg.exec(test);
console.log(result[0]);// 456

6.2.3 dotAll模式

使用.匹配除回车外的任何单字符,如果使用了s就可以匹配包括回车在内的任何单字符:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let test = `
<ul>
<li>
<a>这是第1个a标签</a>
<p>这是第1个p标签</p>
</li>
<li>
<a>这是第2个a标签</a>
<p>这是第2个p标签</p>
</li>
</ul>
`;
var reg = /<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/g;// 使用之前的方式,通过\s+过滤空格和换行,匹配两个标签中的内容
var result;
while (result = reg.exec(test)) {
console.log(result[1] + ' >>> ' + result[2]);
}
var reg = /<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;// 使用dotAll模式,通过s让.包含任何单字符,匹配两个标签中的内容
while (result = reg.exec(test)) {
console.log(result[1] + ' >>> ' + result[2]);
}

7 ES10

7.1 创建对象

通过二维数组创建对象:

js
1
2
3
4
5
let person = Object.fromEntries([
['name', '张三'],
['sex', '男']
]);
console.log(person);// {name: '张三', sex: '男'}

通过Map创建对象:

js
1
2
3
4
5
let map = new Map();
map.set('name', '张三');
map.set('sex', '男');
let person = Object.fromEntries(map);
console.log(person);// {name: '张三', sex: '男'}

Object类的fromEntries()方法是entries()方法的反向操作。

7.2 字符串扩展

新增支持清除字符串空格的方法:

js
1
2
3
let str = ' hello ';
console.log(str.trimStart());// 清除左侧空格
console.log(str.trimEnd());// 清除右侧空格

7.3 数组扩展

将高维数组转为低维数组,支持设置向下转变的维度数目,默认向下转变1个维度:

js
1
2
3
4
5
var arr = [1,2,[3,4]];
console.log(arr.flat());// [1, 2, 3, 4]
var arr = [1,2,[3,4,[5,6]]];
console.log(arr.flat());// [1, 2, 3, 4, [5, 6]]
console.log(arr.flat(2));// [1, 2, 3, 4, 5, 6]

map()方法的基础上,增加了flatMap()方法,用于将结果向下维度转换处理:

js
1
2
3
var arr = [1,2];
var result = arr.flatMap(item => [item * 10]);
console.log(result);// [10, 20]

7.4 Symbol扩展

支持在创建Symbol对象的时候传入描述内容:

js
1
2
var s = Symbol('测试');
console.log(s.description);// 测试

8 ES11

8.1 私有属性

在属性前面添加#符号表示该属性是私有属性:

js
1
2
3
4
5
6
7
8
9
10
11
class Person {
name;
#age;
constructor(name, age) {
this.name = name;
this.#age = age;
}
}
let p = new Person('张三', 18);
console.log(p);// Person {name: '张三', #age: 18}
console.log(p.#age);// 报错

8.2 Promise扩展

新增Promise的allSettled()静态方法,接收Promise数组作为入参,将结果封装为Promise返回,状态为成功,值为数组的每个Promise执行的状态和结果:

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
27
28
29
30
31
32
33
34
35
36
37
let p1result = false;
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
if (p1result) {
resolve("p1成功");
} else {
reject("p1失败");
}
}, 100);
});
let p2result = true;
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
if (p2result) {
resolve("p2成功");
} else {
reject("p2失败");
}
}, 100);
});
// 方法永远返回成功
Promise.allSettled([p1, p2]).then(function (value){
console.log("allSettled执行成功");
// {status: 'rejected', reason: 'p1失败'}
// {status: 'fulfilled', value: 'p2成功'}
value.forEach((result) => console.log(result));
}, function (error){
console.log("allSettled执行失败 " + error);
});
// 只要有一个失败,方法就返回失败
Promise.all([p1, p2]).then(function (value){
console.log("all执行成功");
value.forEach((result) => console.log(result));
}, function (error){
// all执行失败 p1失败
console.log("all执行失败 " + error);
});

和Promise的all()静态方法不同,allSettled()方法不论数组里的结果是否成功,始终返回成功,all()会在数组里的结果存在失败时,返回失败。

8.3 字符串扩展

新增字符串matchAll()方法,用于正则表达式,其结果和正则表达式的exec()方法相同:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var reg = /(123)(a.a)/g;
// 使用match()方法
var matchResult = '123a4a123a5a'.match(reg);
console.log(matchResult);// ['123a4a', '123a5a']
// 使用matchAll()方法
var matchAllResult = '123a4a123a5a'.matchAll(reg);
for (let r of matchAllResult) {
// ['123a4a', '123', 'a4a', index: 0, input: '123a4a123a5a', groups: undefined]
// ['123a5a', '123', 'a5a', index: 6, input: '123a4a123a5a', groups: undefined]
console.log(r);
}
// 使用matchAll()方法
var execResult;
while(execResult = reg.exec('123a4a123a5a')) {
// ['123a4a', '123', 'a4a', index: 0, input: '123a4a123a5a', groups: undefined]
// ['123a5a', '123', 'a5a', index: 6, input: '123a4a123a5a', groups: undefined]
console.log(execResult);
}

8.4 可选链操作符

使用?.可选链操作符可以代替繁琐的层级判断是否为空的操作:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
function test(config) {
// 直接使用,如果不存在会报错
console.log(config.db.url);
// 层级判断,存在才获取
console.log(config && config.cache && config.cache.url)
// 简化操作,使用可选链操作符判断
console.log(config?.cache?.url)
}
test({
db: {
url: '192.168.1.10'
}
});

8.5 模块化扩展

在导入模块时支持动态导入,可以实现按需加载模块,即在用到某个模块时再导入,而不是在加载时全部导入,可以提高加载效率。

使用import()方法传入文件路径即可导入指定文件,返回Promise对象,该对象的值是文件对应的导出的对象:

test.html
1
2
3
4
5
6
7
8
9
10
11
12
<script type="module">
// 全部导入
// import * as test from "./test.js";
// 动态导入
let btn = document.getElementById('btn');
btn.onclick = function() {
import("./test.js").then(test => {
console.log(test.hello);
test.test();
});
}
</script>

8.6 BigInt

新增数据类型BigInt用于表示大整型数字:

js
1
2
3
4
5
6
7
8
9
10
11
12
// 大整型数字使用字母n作为数字结尾
let n = 1024n;
console.log(n);// 1024n
console.log(typeof n);// bigint
console.log(BigInt(0));// 0n 支持将整型数字转为大整型数字,并且只支持转换整型数字

// 使用大整型数字解决大数字运算,只支持大整型数字之间的运算,需要先将整型数字转为大整型数字
let max = Number.MAX_SAFE_INTEGER;
console.log(max + 1);// 9007199254740992
console.log(max + 2);// 9007199254740992
console.log(BigInt(max) + BigInt(1));// 9007199254740992n
console.log(BigInt(max) + BigInt(2));// 9007199254740993n

8.7 绝对全局对象

新增全局变量globalThis指向全局变量:

js
1
console.log(globalThis);// Window {...}

评论