一、概念
ECMAScript(ES):是一个国际通用性的标准化脚本语言。
JavaScript(JS):ECMAScript、DOM和BOM组成。
简单地理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展。
2011 年,ECMAScript 5.1 版发布。也就是ES5('use strict', forEach, map,filter.some...[Array.prototype], JSON.parse, JSON.stringfiy, object.defineProperty)。
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。
目前已经发展至ES12。
二、为什么要学习ES?
- 精简代码,且主流浏览器已支持ES6(如果需要适配IE9-,不建议使用)。
- 行业内较新的前端框架(vue、react、原生微信小程序、uniapp、taro)都已经使用ES6+语法。
- 便于阅读框架源码。
三、常用ES6语法
-
let、const
限制在块级作用域({})的变量定义方式。
let是新的var,但是声明的变量只能在当前块级作用域有效。
const声明常量,常量只能赋值一次,一旦声明就不能改变。
与var区别:
1)var声明的变量为全局变量,默认绑定到windows上,全局作用域有效;而let仅在当前声明的块级作用域内有效。
-
在同一个块级作用域里,变量只能被let声明一次;在不同的块级作用域里,变量可以被let声明多次。var可以在全局作用域内声明多次。
-
let严格遵守代码顺序,不存在变量提升。而var声明的变量,会忽略声明顺序,可以提升变量。
-
-
模板字符串
模板字符串为构造字符串提供了语法糖。符号 ``
let person = 'xiaoming'; console.log(person + '是个好学生'); // 等价于 console.log(`${person}是个好学生`);
-
扩展(延展)运算符、rest运算符
都是三个点 "..."。
扩展(延展)运算符:用于把数组或者类数组对象展开形成一系列用逗号隔开的值。
let getFunc = (x, y) => { console.log(x); console.log(y); } let arr = [1, 2]; // 普通做法 getFunc(arr[0], arr[1]); // 打印 1, 2 // 使用扩展运算符 getFunc(...arr);// 打印1, 2
使用场景
1、给数组做深拷贝
let arr1 = [1, 2]; let arr2 = [...arr1]; console.log(arr1 === arr2); // false
2、合并数组,相当于ES5中的concat
let arr1 = [1, 2]; let arr2 = [3, 4]; console.log(arr1.concat(arr2)); // 打印 [1, 2, 3, 4] console.log([...arr1, ...arr2]); // 打印 [1, 2, 3, 4]
3、字符串转数组
let str = 'hello'; console.log([...str]); // 打印 ['h', 'e', 'e', 'l', 'o']
rest运算符:把逗号隔开的值组合成一个数组 or 对象。
使用场景
1、不定参数传值
// 不定参数传值 - 数组 let getArrItem = ([...args]) => { console.log(args[0]); console.log(args[1]); console.log(args[2]); console.log(args[3]); } getArrItem([1, 2, 3]) // 不定参数传值 - 对象 let getObjItem = ({ ...args }) => { console.log(args.id); console.log(args.name); } getObjItem({ id: '1', name: 'xiaoming', address: '济南市' });
2、与解构赋值关联使用,具体见解构赋值。
-
解构赋值
允许基于模式匹配的方式进行赋值,这种模式匹配能够支持 数组和对象。
对于数组:
1、数组中的数据会自动解析到对应接受该值的变量中,数组的解构赋值要一一对应,否则就是undefined。
2、可以直接省略元素,使用','做占位符。
3、rest运算符 '...', 可以用于获取数组部分数据。
// 数组 let [ a, b, c, , d ] = [1, 2, 3, 4, 5] console.log('a:' + a) console.log('b:' +b) console.log('c:' +c) console.log(d)
对于对象:
1、对象与数组不同,对象不要求一一对应,但是要求变量必须与属性同名(不使用别名的情况),这样才能获取到正确的值。
2、使用别名:let { local:address } = { local: '山东' } 可以访问address
3、rest运算符 '...',可以用于获取对象部分数据。
4、嵌套对象时与上述相同。
// 对象 let { id, name, address: local, ...otherinfo } = { id: 1 , name: 'xiaoming',address: '济南市', sex: '男', note: '' }; console.log(id) console.log(name) console.log(local) console.log(otherinfo) let obj = { id: 2, ...otherinfo } console.log(obj) // 嵌套对象 let { ids, otherinfos: { company } } = { ids: 2, otherinfos: { company: 'jxd' } }; console.log(company)
-
箭头函数
类似于匿名函数,将原函数的"function"关键字和函数名都去掉,并且用"=>"连接参数列表和函数体。
function add(x, y) { return x + y } // 等价于 let add = (x, y) => { return x + y } // 等价于 let add = (x, y) => x + y console.log(add(1,2));
注意点:
- 函数的参数只有一个时,可以省略 ()。
函数体内只有一条return语句,且返回非对象时,可以省略 return关键字和{};当返回对象时,需要加(),避免和函数体的{...}有语法冲突。
// 返回非对象 let getNum = (x)=> { return x }; // 等价于 let getNum = x => x; // 返回对象 let getObj = name => ({ name: name }); console.log(getObj('xiaoming'));
2)箭头函数的this指向就是上下文里对象this的指向,如果没有上下文对象,this就指向window。不受call、apply、bind等方法的影响。
// 关于this指向 let obj = { id: '1', age: this, // 指向window name: () => { console.log(this); // 指向window setTimeout(() => { console.log(this);// 指向window })}, getFunc: function() { console.log(this); // 指向obj setTimeout(() => { console.log(this); // 指向obj }) } } obj.age; obj.name(); obj.getFunc();
-
类(class)
类(class)是在基于原型的面向对象模式上简单包装的语法糖。拥有一个 单一且方便的声明形式将更易于使用,并且 鼓励混合使用。类(class)支持基于原型的继承、super 调用、 实例和静态方法以及构造函数。
等价于构造函数。
1、基本格式
1) class后面紧跟方法名,方法名需要大写。
2) 创建实例时,使用new Xxx()的格式。
3) 都存在constructor,即构造函数。
// 声明类 class Person { constructor(name) { this.name = name; } getPersonInfo() { console.log(this.name) } } // 创建实例 let person = new Person('xiaoming'); person.getPersonInfo();
2、继承
1) 使用关键字 extends。
2) constructor中必须使用super(),用于继承父元素的属性和方法。
// 声明类 class BoyPerson extends Person { constructor(name, sex) { super(name); this.sex = sex; } getBoyPersonInfo() { console.log(`姓名: ${this.name}, 性别: ${this.sex}`); } } // 创建实例 let boy = new BoyPerson('xiaoli', '男') boy.getBoyPersonInfo();
3、实例属性
1) 定义在constructor()方法中的属性都是对象的属性,可以用this获取。
2) 实例属性也可以定义在类顶层的,定义格式 _xxx, 通过this获取。
4、静态属性和方法
所有在类中定义的属性和方法,都会被实例继承。如果在属性和方法前面添加static,则该属性和方法不会被实例继承,而是直接通过类来调用,这就是静态方法。
1) 使用关键字 static。
2) 不能通过实例调用,只能通过类调用,即构造函数.方法名。
5、私有属性和方法
只能在类的内部访问的属性和方法,外部不能调用。
写法: 在属性或者方法前面加 #。
内部访问:需要 this.#属性名|方法名。
class BoyPerson extends Person { _local = '济南市'; #age = 10; constructor(name, sex) { super(name); this.sex = sex; } getBoyPersonInfo() { console.log(`姓名: ${this.name}, 性别: ${this.sex}, 地址: ${this._local}, 年龄${this.#age}`); } } let boy = new BoyPerson('xiaoli', '男') boy.getBoyPersonInfo(); console.log(boy._local); // 打印 济南市 console.log(boy.#age); // 报错
PS:
1)类声明和函数声明一个重要的区别,类声明不会在作用域内导致变量提升。所以在使用类的时候先声明后创建实例。
2) constructor中定义的内容都会绑定到对象的属性上,类的所有方法会定义prototype上,例如constructor()、getBoyPersonInfo() 会绑定到构造函数的prototype上。
3) 添加方法确实可以通过 Person.prototype.add = () { return 1 } || person.proto.add = () => { return 1 }。但是不推荐,会影响其他实例。
class Person { static comeFrom = 'china' constructor(name) { this.name = name; } getPersonInfo() { console.log(this.name) } } let person = new Person('xiaoming'); let person2 = new Person('xiaoli'); person.__proto__.getName = () => { console.log(this.name) };
-
Promise
构造函数,异步操作容器,里面存在一个异步操作。通过Promise可以获取该异步操作可能发生的结果,比如pending, fulfilled, rejected。
1、Promise对象特点
1)提供三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个操作。
2)状态变化有两种,从pending -> fulfilled || pending -> rejected。
2、作用
1) 异步操作以同步的写法表现出来,避免回调函数层层嵌套 .then().catch().finally()。
2) Promise对象提供了许多API,使异步函数处理更加容易。
3、基本用法
1) 创建promise实例。Promise构造函数接收一个函数作为参数,该函数中有两个参数,分别是resolve,reject。
2) 调用Promise提供方法。Promise对象生成后,可以用then() 指定resolve(必选)和reject(可选)状态的回调函数。
3) 如果resolve()带有参数,该参数会被传递给回调函数。如果reject() 带有参数,该参数一般是Error对象的实例,也会被传递给回调函数。
const promise = new Promise((resolve, reject) => { // todo 这里处理逻辑 if(success) { // 成功时执行 resolve(value) } else { // 失败时执行 reject(value) } }) promise.then(value => { }, err => { })
4、Promise实例提供的方法
1) Promise.all(iterator),其中iterator必须是个可迭代的对象,比如Array 或者 String,返回一个Promise实例
传入参数中任意一个primise返回失败,则整体返回失败,返回的错误信息为第一个失败的promise结果。
2) Promise.race(iterator)。返回最先发生改变的实例。
3) Promise.resolve(value): 返回一个以给定的值解析后的Promise对象。
4) Promise.reject(reason): 返回一个含有reason的状态为rejected的Promise对象。
5) Promise.prototype.then(): 为Promise实例状态发生变化时添加回调函数,接受两个参数,fulfilled状态时的回调函数,rejected状态时的回到函数。并且该方法返回一个新的Promise对象。
6) Promise.prototype.finally(): 返回一个Promise, 在上一轮promise运行后,无论状态是fullfilled还是rejected,都会执行finally中指定的回调函数。
7) Promise.prototype.catch(): 返回一个Promise,并且处理拒绝情况,与then中的err相同。这里推荐使用catch方法,不要在then() 中定义rejected状态的回调函数,这是因为catch还可以捕捉在then()方法执行中存在的错误。
getPreApprovalList(params) { return new Promise((resolve, reject) => { OtherContractService.getPreApprovalList(params).then(data => { (data.list || []).forEach(i => this.$set(i, 'deleteLoading', false)) resolve(data) }).catch(e => { reject(e) }) }) }
promise具有then(),返回一个新的promise实例,因此可以采用链式写法,即then方法后面再调用then。
5、关于Promise执行顺序
// 执行顺序 function timeOut(ms) { return new Promise((resolve, reject) => { console.log(4) return setTimeout(resolve, ms, '5' ) }) } timeOut(100).then(val => { console.log(val) }, err => { console.log('失败') }) let promise = new Promise((resolve, reject) => { resolve(2); // 这里比较下加return的情况 console.log(1); }) promise.then(val => { console.log(val) }) console.log(3)
解析:
1、创建Promise实例时,优先执行Promise实例中的console。
2、执行同步console。
3、从上往下执行不含延时的Promise的resolve or reject。
4、从上往下执行含有延时的Promise的resolve or reject。
timeOut是个含有返回Promise对象的方法,当执行timeOut(100)时,会生成Promise实例,这里面的4优先打印,then()暂缓;
let promise = new Promise({...}),这时候Promise对象已经创建,所以接下来打印1,then()暂缓;
打印同步的3;
从上往下数,第二个promise没有延时,优先打印2。
最后打印5。
6、实现异步加载图片
let url = "图片地址"; function loadImageAsync(url) { return new Promise((resolve, reject) => { var image = new Image(); image.src = url; image.onload = function() { resolve(image); } image.onerror = function() { reject('加载失败') } }) } loadImageAsync(url).then(val => {})
7、总结
1) 什么是Promise?
Promise() 其实是个构造函数,用户可以根据操作状态给与处理。Promise()的prototype提供了多个api,比如all()、race()、then()、catch()、finally(),给实例化的promise对象提供了操作、执行、异常捕捉、后续执行这些方法。
2)与普通回调函数的区别。
在执行多项异步操作时,普通的回调函数需要在回调中嵌套回调,代码比较臃肿,容易出现回调地狱。通过promise,可以在then()中resolve返回下一个异步操作,then()返回的是一个新的promise对象,原型对象上存在then(),可以链式调用。
-
数据结构 Set
构造函数,类似于数组,但是成员的值都是唯一的,可以用于去重(仅适用于普通数组,不适用于对象数组)。
数组去重方法:[ ...new Set(arr) ]
1)Set的属性及方法
constructor: 构造函数,返回Set();
size: 返回实例的成员总数
add(val): 添加某个值,返回Set结构本身
delete(val): 删除某个值,返回boolean;
has(val): 查找是否含有某个值,返回boolean;
clear(): 清除所有指,没有返回值。
2) Set遍历操作
keys(): 返回键名的遍历器
values(): 返回键值的遍历器
entries(): 返回键值对的遍历器
forEach((value, keys) => {})
-
数据结构Map
构造函数,类似于Object,{ key: value }的形式,但是和Object有区别
Object:只能用字符串当key, "字符串 -> 值"
Map: 键的范围不限于字符串,各种类型的值(包括对象)都可以当做key,"值 -> 值";
const map = new Map([['name', 'xiaoming'], ['title', 'author']])
1)Map的属性和方法
size: 返回Map结构的成员总数
set(key, value): 设置键名为key的对应键值为value,然后返回整个Map。
get(key): 读取键值为key的键值
has(key): 判断某个键是否在当前的Map对象中,返回Boolean。
delete(key): 删除某个值,返回boolean
clear(): 清空Map对象
四、ES7~ES12部分常用语法
-
async 、await
异步终极解决方案。以同步的写法完成异步操作流程,避免异步嵌套。
async: 表示函数里有异步操作。
await: 表示跟随在后面的表达式需要等待结果, await 后面跟着Promise对象 。
当函数执行时一旦遇到await就返回,等到触发的异步操作完成,再接着执行函数体后面的语句。
let timeOut = ms => { return new Promise((resolve, reject) => { return setTimeout(resolve, ms, 'timeOut') }) } // 打印顺序 timeOut, 1 let asyncMethod = async () => { try { let timeConsole = await timeOut(2000); console.log(timeConsole); console.log(1) } catch(e) { console.log(e) } } asyncMethod();
注意点:
await 后面的Promise对象,运行有可能返回reject,最好把await放在try...catch里,这样能够捕捉异常。
await只能用在async函数中,如果是普通函数就会报错。
可以使用 promise.all 方法发起多个请求。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者使用下面的写法 ES9 异步迭代 await可以和for...of循环一起使用,以串行的方式运行异步操作
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
当出现多个Promise
```javascript
let timeOut1 = ms => {
return new Promise((resolve, reject) => {
return setTimeout(resolve, ms, 'timeOut1')
})
}
let timeOut2 = ms => {
return new Promise((resolve, reject) => {
return setTimeout(resolve, ms, 'timeOut2')
})
}
let timeOut3 = ms => {
return new Promise((resolve, reject) => {
return setTimeout(resolve, ms, 'timeOut3')
})
}
let funcArr = [ timeOut1(1000), timeOut2(2000), timeOut3(3000) ]
async function promiseFunc() {
console.time();
let result = await Promise.all(funcArr);
console.log(result);
console.timeEnd();
console.log('结束')
}
promiseFunc();
-
Array.prototype.includes
返回Boolean,表示某个数组是否包含指定值。
let arr = [1, 2, 3]; if(arr.includes(1)) { console.log('包含') }
-
String.trimStart()和String.trimEnd()
去除字符串首尾空白字符。
-
空值处理 ??
表达式在 ?? 的左侧 运算符求值为undefined或null,返回其右侧。
-
Optional chaining(可选链)
?.用户检测不确定的中间节点
let person = {}; // let childName = person.children.name; // 报错 Cannot read property 'name' of undefined let childName = person.children ?. name; // undefined console.log(childName);
五、参考
Babel官网中文文档:https://www.babeljs.cn/docs/learn
Babel官网中文文档:https://www.liaoxuefeng.com/wiki/1022910821149312
阿西河ECMAScript 文档说明:https://www.axihe.com/api/js-es/api/api.html
六、补充
-
原型、原型链关系