1. 90前端首页
  2. 前端开发
  3. JavaScript

ES6+知识点梳理及背后原理+工作中推荐用法

前言

本文将介绍ES2016以及之后版本中常用的知识点以及该知识点与之前版本用法的对比,更多的了解知识点背后的原理,从而更加深刻的理解ES6+到底好在哪里。

1、let和const


let,const都是用来声明变量,用来替代老语法的var关键字,与var不同的是,它们会创建一个块级作用域(一般一个花括号内是一个新的作用域)

特点:
  • 不存在变量提升
  • 不允许重复声明
  • 暂时性的死区——只要块级作用域内用let,const声明的变量,那这个变量就绑定到这个区域了,不受外部的影响
  • 块级作用域
  • let和var全局声明时,var可以通过window的属性访问而let不能。
  • const声明的变量必须赋值,即不能只声明不初始化
  • const声明的变量不能改变,如果声明的变量是引用类型(数组或对象),则不能改变它的内存地址
场景案例:

场景1:经典面试题

// 使用var
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    });
} // => 5 5 5 5 5

// 使用let
for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    });
} // => 0 1 2 3 4

知识点:

  1. for循环:设置循环变量的部分(即圆括号部分)是一个父作用域,而循环体内部(即中括号部分)是一个单独的子作用域,所以可以在循环体内部访问到i的值。
  2. 变量i:使用var声明的变量i,在预编译阶段会有变量提升,会提升到当前作用域的顶部并初始化赋值为undefined,var i=0在for循环中只执行一次;使用let声明的变量i,不存在变量提升,只有代码运行到let i语句的时候才开始初始化并赋值,在for循环中当前的i只在本轮循环中有用,可以理解为每次循环的i其实都是一个新的变量,JS内部引擎会记住上次循环结果赋值给下个循环。
  3. setTimeout:会将一个匿名的回调函数推入异步队列,并且回调函数还具有全局性,在非严格模式下this会指向window。这里就会有这样一个问题:setTimeout里的函数是何时进入任务队列?,请看下面的代码:
setTimeout(function () {
    console.log(\'ab\')
}, 5 * 1000) //这里改为0ms
for (let i = 0; i <= 5000000; i++) {
    if (i === 5000000) {
        console.log(i)
    }
}

说明:setTimeout里的函数在setTimeout执行的时候,就开始计时,计时完成(这里就是5s之后)才进入任务队列,当主执行栈执行完就开始执行任务队列。

场景2:const使用

const a; //Uncaught SyntaxError: Missing initializer in const declaration
const a=1;
a=2; //Uncaught TypeError: Assignment to constant variable.
const obj = {
    a: 1
}
obj.a = 2
obj = { b: 1 } //Uncaught TypeError: Assignment to constant variable.
let a = 1
console.log(window.a)//undefined
var b=1;
console.log(window.b)//1

2、解构赋值


解构赋值主要就是数组的解构赋值和对象的解构赋值,至于其他数据类型的解构赋值都与这两种有关系,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

注意点:

  1. 数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
  2. 只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
  3. 对象的解构赋值可以取到继承的属性。
对象的解构赋值

场景1:内部机制

let {
    name: nameOne,
    test: [{
        name: nameTwo
    }]
} = {
    name: \'wx\',
    test: [{
        name: \'test\'
    }]
}
//简化版
let { name, test } = {
    name: \'wx\',
    test: [{
        name: \'test\'
    }]
}

说明:这里等号左边真正声明的变量其实是nameOnenameTwo,然后根据它们所对应的位置找到等号右边对应的值,从而进行赋值。

场景2:对象的方法

// 例一
let { sin, cos } = Math;

// 例二
const { log } = console;
log(\'hello\') // hello

//vuex中action中的方法
//不使用对象解构:
const actions = {
    getMainTaskList(ctx) {
        ctx.commit(\'setMainTask\')
    },
}
//使用对象解构:
const actions = {
    getMainTaskList({ commit, state, dispatch }) {
        commit(\'setMainTask\', { mainTaskData: JSON.parse(res) })
    },
}

场景3:嵌套解构的对象

let obj = {
    p: [
        \'Hello\',
        { y: \'World\' }
    ]
};
// 以前的写法:
let x = obj.p[0]
let y = obj.p[1].y

// 解构写法:
let { p: [x, { y }] } = obj;
x // \"Hello\"
y // \"World\"

场景4:继承的原型属性

const obj1 = {}
const obj2 = { foo: \'bar\' }
Object.setPrototypeOf(obj1, obj2)//ES6推荐设置原型方法

const { foo } = obj1
foo // \"bar\"
数组的解构赋值
// 老式写法
const local = \'wx-18\'
const splitLocale = locale.split(\"-\");
const language = splitLocale[0];
const country = splitLocale[1];

// 解构赋值写法
const [language, country] = locale.split(\'-\');
其他类型的解构赋值
//字符串
const [a, b, c, d, e] = \'hello\';

//数值和布尔值
let {toString: s} = 123;
s === Number.prototype.toString // true
console.log(s);//function toString()
//[查看详细的解释](https://segmentfault.com/q/1010000005647566)

//undefined和null
注意:由于`undefined`和`null`无法转为对象,所以对它们进行解构赋值,都会报错。

3、扩展运算符


扩展运算符主要是两大类:数组和对象的扩展运算符。使用三个点点(...),后面跟含有iterator接口的数据结构

数组的扩展运算符

场景1:复制数组

// ES5的复制数组-复制了指针(浅)
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;

// ES5的复制数组-深拷贝
const a1 = [1, 2];
const a2 = a1.concat();//a1.slice()
a2[0] = 2;

// ES6复制数组-深拷贝
const a1 = [1,2]
const a2 = [...a1] //const [...a2] = a1
a2[0] = 2;

场景2:合并数组

//ES5合并数组
const arr1 = [\'a\', \'b\'];
const arr2 = [\'c\'];
const arr3  = arr1.concat(arr2) //[\"a\", \"b\", \"c\"]

// ES6合并数组
const arr1 = [\'a\', \'b\'];
const arr2 = [\'c\'];
const arr3  = [...arr1,...arr2] //[\"a\", \"b\", \"c\"]

场景3:与解构赋值结合使用

const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first) //1
console.log(rest) //[2, 3, 4, 5]

//将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...rest,last] = [1, 2, 3, 4, 5];//Uncaught SyntaxError: Rest element must be last element

场景4:类对象转数组

任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。

// 例1:
let nodeList = document.querySelectorAll(\'div\');//类数组对象,并且部署了iterator接口
let arr = [...nodeList];

// 例2:
let arrayLike = {
    \'0\': \'a\',
    \'1\': \'b\',
    length: 2
};
// Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
let arr = [...arrayLike];

//当然可以通过`Array.from(arrayLike)`将它转为真正的数组
let arr = Array.from(arrayLike)
对象的扩展运算符
let obj1 = {
    a:1,
    b:1,
}
let obj2 = {
    ...obj1,
    c:1
}
console.log(obj2);//{a: 1, b: 1, c: 1}

//vuex中
computed: {
    ...mapGetters(\'raplaceBattery\', {
      runningTask: \'runningMainTask\'
    })
},
methods:{
    ...mapActions(\'raplaceBattery\', [
      \'getMainTaskList\'
    ]),
}

4、数组和对象的遍历方式


数组和对象的遍历方式有很多种,常用的for循环,for...in循环以及ES6提出的for...of循环,这些方式有各自的使用场景以及优缺点。

数组的遍历方式

以下遍历的数组都为:let arr = [1,2,3,4]

方式1:for

//写法比较麻烦,但是常用
for (let i = 0; i < arr.length; i++) {
    console.log(\'for循环:\',arr[i]);
}
//使用break跳出循环,return会报错
for (let i = 0; i < arr.length; i++) {
    if(i === 2) break
    console.log(\'for循环:\',arr[i]);//1,2
}

方式2:forEach

arr.forEach(ele => {
    console.log(\'forEach循环:\',ele);
});
//缺点:无法跳出循环,break直接会报错,return只是跳出本次循环
arr.forEach(ele => {
    if(ele === 2) return;
    console.log(\'forEach循环:\',ele);//1,2,4
});

方式3:map

返回新的数组,不改变原数组

let mapArr = arr.map((item,index,arr)=>{
    if(index === 2) return;//只是跳出本次循环,
    return item+1
})
console.log(arr);//[1, 2, 3, 4]
console.log(mapArr);//[2, 3, undefined, 5]

//map有一个选填的返回函数里面的this的参数
let mapArr = arr.map(function (e) {
    return e + this; // 此处的 this为10
}, 10);
console.log(mapArr);//[11, 12, 13, 14]

这里为什么要用回调函数而不用箭头函数?
答:因为箭头函数里的this指向和函数里的this指向不同。请了解箭头函数的this指向(后续)

方式4:filter,find,findIndex

filter:返回新数组,不改变原数组,主要用于过滤
find:返回第一个判断条件的元素,不是数组
findIndex:返回第一个判断条件的元素的索引

let filterArr = arr.filter((item,index,arr)=>{
    return item > 1
})
console.log(filterArr);//[2, 3, 4]

//find
let findArr = arr.find((value,index,arr)=>{
    return value > 2
})
console.log(findArr);//3

//findIndex
let findIndexEle = arr.findIndex((val,i,arr)=>{
    return val > 2
})
console.log(findIndexEle);// 2

方式5:every和some(返回布尔值)

every:只要有一个不符合,返回false,全符合,返回true
some:只要有一个符合,返回true,全不符合,返回false

let everyBool = arr.every((val, i, arr) => {
    return val < 3
})
console.log(everyBool);//false

let someBool = arr.some((val,i,arr)=>{
    return val > 2
})
console.log(someBool);//true

方式6:keys(),values(),entries()

ES6提供的新方法用于遍历数组,都返回遍历器对象

let keysArr = arr.keys()
console.log(keysArr);//Array Iterator {}

//这种方式不可以哦
arr.keys().forEach(ele => {
    console.log(ele);//Uncaught TypeError: arr.keys(...).forEach is not a function
});

//for...of可以
for (const index of arr.keys()) {
    console.log(index);//0,1,2,3
}

for (const val of arr.values()) {
    console.log(val);//1,2,3,4
}

for (const [index,ele] of arr.entries()) {
    console.log(index,ele);
}
//0 1
//1 2
//2 3
//3 4

ES6+知识点梳理及背后原理+工作中推荐用法

方法7:for...of循环

遍历所有部署了iterator接口的数据结构的方法,如:数组,Set,Map,类数组对象,如 arguments 对象、DOM NodeList 对象,Generator 对象,字符串

场景1:用于set和map结构

遍历set结构返回的是一个值,遍历map结构返回的是一个数组
遍历的顺序是各个成员添加的顺序

let setArr = new Set([1,2,3,3])
let mapObj = new Map().set(\'b\',1).set(\'a\',2)
for (const item of setArr) {
    console.log(item);//1,2,3
}

for (const item of mapObj) {
    console.log(item);//item是数组,别搞错了
}
//[\"b\", 1]
//[\"a\", 2]

for (const [item, index] of mapObj) {
    console.log(item, index);
}
//b 1
//a 2

场景2:类数组对象(必须部署了Iterator接口)

//字符串
const str = \'wx\'
for (const item of str) {
    console.log(item);//w,x
}

//DOM NodeList对象
let ps = document.querySelectorAll(\'div\')
for (const p of ps) {
    console.log(p);
}

//arguments对象
function writeArgs(){
    for (const item of arguments) {
        console.log(item);
    }
}
writeArgs(\'1\',2)//1,2

//rest的方式
function writeArgs(...rest){
    for (const item of rest) {
        console.log(item);
    }
}
writeArgs(\'1\',2)//1,2

//没有部署的类数组对象,通过Array.from转为数组
let likeArr = {length:2,0:\'1\',1:\'2\'}
for (const item of Array.from(likeArr)) {
    console.log(item);//1,2
}

场景3:普通对象

普通对象不能使用for...of遍历,因为没有部署Iterator接口,一般使用for...in进行键名遍历

let obj = {
    a:1,
    b:2,
    c:3
}
for (const item of obj) {
    console.log(item);//Uncaught TypeError: obj is not iterable
    
//你非要使用for...of呢?你调皮了

// 方式1:
for (const item of Object.keys(obj)) {
    console.log(item);//a,b,c
}

// 方式2:使用Generator函数进行包装
function* packObj(obj){
    for (const key of Object.keys(obj)) {
        yield [key,obj[key]]
    }
}
for (const [key,value] of packObj(obj)) {
    console.log(key,value);
}
}
//a 1
//b 2
//c 3
对象的遍历方式

以下遍历的对象都为:

let obj = {
    a:1,
    b:2,
    c:3,
    [Symbol(\'mySymbol\')]: 4,
}
Object.setPrototypeOf(obj,{name:\'wx\'}) ////给原型加了name属性
//Object.getOwnPropertyDescriptor(obj,\'enumTemp\').enumerable = false //这种方式无效
Object.defineProperty(obj, \'enumTemp\', {
    value: 5,
    enumerable: false
})//添加一个不可枚举属性enumTemp

1. for...in

特点:遍历对象自身的和继承的可枚举属性(不含Symbol属性),不建议使用

for (const key in obj) {
    console.log(key);//a,b,c,name
}

2. Object.keys(obj),Object.entries(obj)

特点:只包括对象自身的可枚举属性--(不含Symbol属性,不含继承的),推荐使用

Object.keys(obj).forEach(item=>{
    console.log(item);//a,b,c
})

for (const [item,index] of Object.entries(obj)) {
    console.log(item,index);
}
//a 1
//b 2
//c 3

3. Object.getOwnPropertyNames(obj)

特点:包括对象自身所有的属性(包括不可枚举的)--(不含Symbol属性,不含继承的)

console.log(Object.getOwnPropertyNames(obj));
//[\"a\", \"b\", \"c\", \"enumTemp\"]

4. Object.getOwnPropertySymbols(obj)

特点:返回数组包括自身所有symbol属性的键名

console.log(Object.getOwnPropertySymbols(obj));//[Symbol(mySymbol)]
let sym = Object.getOwnPropertySymbols(obj).map(item=>obj[item])
console.log(sym);//[4]

5、Proxy、Reflect和Object.defineProperty


它们都是为了操作对象而设计的API,并且主要用于监听数据变化以及响应式能力

1. Object.defineProperty
具体更详细的了解请查看:Object.defineProperty

//基本形式
let obj = {};
Object.defineProperty(obj, \"num\", {
   value: 1,
   writable: true,
   enumerable: true,
   configurable: true
});
console.log(obj.num) //1

//get,set形式
let obj = {};
let value = 2;
Object.defineProperty(obj, \'num\', {
   get: function () {
       console.log(\'执行了 get 操作\')
       return value
   },
   set: function (newVal) {
       console.log(\'执行了 set操作\');
       value = newVal
   },
   enumerable: true,
   configurable: true
})
console.log(obj.num); // 执行了 get 操作 //2

2. Proxy

Tips:vue3.0使用Proxy代替Object.defineProperty实现数据响应式。

它是一个“拦截器”,访问一个对象之前,都必须先经过它,对比defineProperty只有get,set两种处理拦截操作,而Proxy有13种,极大增强了处理能力,并且也消除了Object.defineProperty存在的一些局限问题

它为何优秀呢?

  • 它有13种操作拦截对象的方法,如:ownKeys(target)apply(target, object, args)defineProperty(target, propKey, propDesc)。。。
  • 消除了Object.defineProperty存在的一些局限问题:对属性的添加、删除动作的监测,对数组基于下标的修改,对于 .length修改的监测,对 Map、Set、WeakMap 和 WeakSet 的支持。(尤大Vue3.0说的)
//1、基础用法
let proxy = new Proxy({}, {
    get(target, key, receiver) {
        console.log(\'get 操作\');
        console.log(receiver);//Proxy {name: \"wx\"}
        return target[key]
    },
    set(target, key, val, receiver) {
        console.log(\'set 操作\');
        console.log(receiver);//Proxy {}
        target[key] = val
    }
})
proxy.name = \'wx\' //set 操作
console.log(proxy.name); //get 操作 //wx

//2、has用法,拦截propKey in proxy的操作,返回一个布尔值
let handler = {
    has(target, key) {
        if (key[0] === \'_\') {
            return false
        }
        return key in target
    }
}
let target = { prop: \'foo\', _prop: \'foo\' };
let proxy = new Proxy(target, handler)
console.log(\'_prop\' in proxy)//false

//3、deleteProperty方法拦截delete操作,返回布尔值
let target = { prop: \'foo\', _prop: \'foo\' };
let proxy = new Proxy(target, {
    deleteProperty(target, key) {
        if (key[0] === \'_\') {
            return false
        }
        delete target[key]
        return true
    }
})
console.log(delete proxy[\'_prop\']) //false
console.log(target) //{prop: \"foo\", _prop: \"foo\"}//这里并没有删除‘_prop’属性

//4、apply方法拦截函数的调用,call和apply操作,它有三个参数,分别是:目标对象,目标对象的上下文(this)和目标对象的参数数组
let target = () => \'target function\'
let proxy = new Proxy(target, {
    apply(target, ctx, args) {
        return \'apply proxy\'
    }
})
console.log(proxy()) //apply proxy

3. Reflect

它和Proxy一样,也是用来处理对象,经常与proxy搭配使用,用来保证这些方法原生行为的正常执行,所以它是服务于proxy的。

//1、基本用法
    let obj = {
        \'a\': 1,
        \'b\': 2
    }
    let proxy = new Proxy(obj, {
        get(target, key) {
            console.log(\'get\', target, key);
            return Reflect.get(target, key);
        },
        deleteProperty(target, key) {
            console.log(\'delete\' + key);
            return Reflect.deleteProperty(target, key);
        },
        has(target, key) {
            console.log(\'has\' + key);
            return Reflect.has(target, key);
        }
    });
    console.log(proxy.b);
    //get {a: 1, b: 2} b
    //2 //原生行为

//2、has用法
    let myObject = {
        foo: 1,
    };
    // 旧写法
    console.log(\'foo\' in myObject);// true

    // 新写法
    console.log(Reflect.has(myObject, \'foo\'));// true

//3、deleteProperty用法
    let myObject = {
        foo: 1,
        b:2
    };
    // 旧写法
    console.log(delete myObject[\'foo\'])//true
    console.log(myObject);//{b: 2}

    // 新写法
    console.log(Reflect.deleteProperty(myObject,\'b\'));//true
    console.log(myObject);//{}

6、Promise 对象


Promise是解决异步编程提出的新的解决方案,它相对于之前的回调函数方案在很多方面做了改进,更加的合理和强大。
理解Promise之前建议先了解异步事件循环以及回调函数

  • 回调函数

回调函数是之前用来解决异步编程常用的方式,大概的流程是:前端发出一个请求,进入浏览器的http请求线程,等收到响应后,将该回调函数推入异步队列,等处理完主线程的任务后会逐个读取异步队列中的回调并开始执行。
1、第三方库信任问题及错误处理

ajax(\'http://localhost:3000\',()=>{
    console.log(\'执行回调\');
})

上面这个例子是最常见的一个回调例子,但是它有什么问题呢?试想一下,如果这个请求在网络环境不好的情况下,进行了超时重试的操作,那么这个回调函数就会执行多次,另外一种情况,要是这个请求失败了,那么它失败的错误信息怎么拿到,这些都是回调函数可能存在的问题,这明显不是我们希望看到的结果。

2、回调地狱

//引用《你不知道的JavaScript(中卷)》
listen(\"click\", function handler(evt) {
    setTimeout(function request() {
        ajax(\"http://some.url.1\", function response(text) {
            if (text == \"hello\") {
                handler();
            } else if (text == \"world\") {
                request();
            }
        });
    }, 500);
});

总结:

  1. 多重嵌套,导致回调地狱
  2. 代码逻辑复杂之后,很难理清代码结构
  3. 第三方库信任问题及错误处理不透明
  • Promise

它是一个构造函数,用来生成promise实例对象,它有两个参数(是函数),一般推荐命名为:resolve和reject

特点:
(1)、它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
(2)、它的状态改变只有两种可能:pendingfulfilled或者pendingrejected
(3)、promise新建后会立即执行

// 基础用法
let p = new Promise((resolve, reject) => {
    if (true) {
        return resolve(\'1\') //resolve(new Promise())
        // 后面写的代码没用了
    } else {
        reject(\'error\')
    }
})
p.then((val) => {
    console.log(\'fulfilled:\', val);
}).catch(e => { //推荐使用catch进行错误处理,可以检测到promise内部发生的错误
    console.log(\'catch\', e);
}).finally(()=>{...})
//对比两种方式
// bad
request(url, function (err, res, body) {
    if (err) handleError(err);
    fs.writeFile(\'1.txt\', body, function (err) {
        request(url2, function (err, res, body) {
            if (err) handleError(err)
        })
    })
});

// good
request(url)
    .then(function (result) {
        return writeFileAsynv(\'1.txt\', result)
    })
    .then(function (result) {
        return request(url2)
    })
    .catch(function (e) {
        handleError(e)
    });

1. Promise.all([...])

将多个Promise(p1,p2,p3)实例包装成一个新的promise实例,这个新实例的状态由p1,p2,p3共同决定。即所谓的一个老鼠坏一锅汤,只有没有老鼠,它才是一锅好汤。
何时用它?当多个任务之间没有必然联系,它们的顺序并不重要,但是必须都要完成才能执行后面的操作

const p1 = request(\"http://some.url.1/\");
const p2 = request(\"http://some.url.2/\");

Promise.all([p1, p2]).then(([p1Result, p2Result]) => {
    // 这里p1和p2全部响应之后
    return request(\"http://some.url.3/\")
}).then((result) => {
    console.log(result);
}).catch(e => {
    console.log(e);
})

2. Promise.allSettled([...])

它恰恰和all不一样,它会等到所有参数返回结果后,状态直接变为fulfilled(完成),不会有reject的情况。即就算是有一个老鼠,这个汤还是成功的(你品,你细品!)

const resolved = Promise.resolve(42)
const rejected = Promise.reject(-1)
const allSettledPromise = Promise.allSettled([resolved, rejected])

// all的情况
allSettledPromise.then(function(results) {
    console.log(\'results\',results)
}).catch(e=>{
    console.log(e) //-1
})

// allSettled的情况
allSettledPromise.then(function(results) {
    console.log(\'results\',results)
}).catch(e=>{
    console.log(e)
})
// [
//   {status:\'fulfilled\',value:42},
//   {status:\'rejected\',reason:-1},
// ]

3. Promise.race([...])

它与Promise.all([...])类似,只是它只关心谁先第一个完成Promise协议,一旦有一个完成,它就完成。即各个promise之间(p1,p2,p3...)存在“竞争”关系

const p = Promise.race([
    request(\'/fetch-request\'),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error(\'request timeout\'))
        }), 5 * 1000
    })
]);

p.then(console.log)
 .catch(console.error);

上面的代码中,5秒之内无法从request中返回结果,p的状态就会变为reject,从而执行catch

4. Promise.resolve()和Promise.reject()

有时候需要将现有的对象转化为promise对象进行处理,如deferred或者thenable对象
注意:立即resolve()的promise对象,是在“本轮事件循环”结束时执行,而不是等到下一轮才执行

//deferred转为promise
const deferToPromise = Promise.resolve($.ajax(\'/a.json\'))

//thenable转promise
let thenable = {
    then(resolve, reject) {
        resolve(11)
    }
};
let p1 = Promise.resolve(thenable);
p1.then((val) => {
    console.log(val);//11
});

//纯对象转promise
let obj = {
    a:1,
    b:2
}
const p = Promise.resolve(obj)
p.then((v)=>{
    console.log(v);//{a: 1, b: 2}
}).finally(()=>{
    console.log(\'aaa\');
})

7、async...await函数


它被称为异步编程的终极解决方案,它进一步优化了promise的写法,用同步的方式书写异步代码,并且能够更优雅的实现异步代码的顺序执行。
它返回一个promise对象。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行吗?。

// 基本用法
async function request(){
    const data = await requestApi()
    // const data1 = await requestApiOther()
    return data
}
request().then((val)=>{
    console.log(val);
}).catch((e)=>{
    console.log(e);
})

重点关注:

async function f() {
    await Promise.reject(\'出错了\');
    await Promise.resolve(\'hello world\'); 
}

上面的这种情况,后面的await不会执行,但是你就想让后面的也执行呢?

async function f() {
    // 方式:1:
    try {
        await Promise.reject(\'出错了\');

    } catch (e) {
        console.log(e)
    }
    // 方式2:
    // await Promise.reject(\'出错了\').catch(e => {
    //     console.log(e);

    // })
    return await Promise.resolve(\'hello world\');
}
f().then((v) => {
    console.log(v);

}).catch(e => {
    console.log(e);

})

推荐使用:
1、将await使用try...catch包裹

async function f(){
    try {
        await someRequest()
    } catch (error) {
        console.log(error);
    }
}

2、多个await后面的异步操作如果不存在依赖关系,最好同时触发

async function f(){
    try {
        await Promise.all([getFoo(), getBar()]);
    } catch (error) {
    }
}
f().then(([foo,bar])=>{
// handle
}).catch(e=>{
    console.log(e);

})

3、多个await后面的异步操作如果存在依赖关系,请参照以下写法

function fetchData() {
    return Promise.resolve(\'1\')
  }

function fetchMoreData(val) {
    return new Promise((resolve, reject) => {
      resolve(val * 2)
    })
  }

function fetchMoreData2(val) {
    return new Promise((resolve, reject) => {
      resolve(val * 3)
    })
  }

// good
  function fetch() {
    return fetchData()
      .then(val1 => {
        return fetchMoreData(val1)
      })
      .then(val2 => {
        return fetchMoreData2(val2)
      })
  }
  fetch().then(val => {
    console.log(val) //6
  })

// better
async function fetch() {
    const value1 = await fetchData()
    const value2 = await fetchMoreData(value1)
    return fetchMoreData2(value2)
};

建议在项目中多使用ES7的async...await写法,它简洁,优雅,可维护性高。

8、Class

Class(类)的概念早就产生,它开启了面向对象语法的潮流,后台开发语言里面(如JAVA)都有这个概念,前端出现类的概念,预示着JS语言已经开始朝着更复杂或者更高级的语言方向走来。

//基本语法
class Foo { //类名
    name = \'wx\'// 定义实例属性
    static name = \'帅哥\' //定义静态属性
    constructor(a, b) { //相当于初始化构造函数,默认返回实例对象(this),必须要有,不显示声明就会默认添加一个空的方法
      this.a = a
      this.b = b
    }
    toString() { //类方法
      return `(${this.a},${this.b})`
    }
}
let f = new Foo(1, 2) //实例化类对象
console.log(f.toString())// (1,2)

知识点1:类的方法都定义在class.prototype上,并且都是不可枚举的

console.log(Object.getOwnPropertyDescriptor(Foo.prototype,\'toString\'))
// value: ƒ toString()
// writable: true
// enumerable: false //不可枚举
// configurable: true

console.log(Object.keys(Foo.prototype)) //[]
 console.log(Object.getOwnPropertyNames(Foo.prototype)) //[\"constructor\", \"toString\"]

知识点2:定义类的其他方式

//方式1:
let Myclass = class Me {
    getClassName() {
      return Me.name //Me只在class内部使用,Myclass可以在内部也可以在外部 // Myclass.name
    }
}
let class1 = new Myclass()
class1.getClassName() //Me
Me.name //Uncaught ReferenceError: Me is not defined

//方式2:
let Myclass = class { //直接去掉
    getClassName() {
      return Myclass.name 
    }
}
let class1 = new Myclass()
console.log(class1.getClassName()) //Myclass

知识点3:this指向

//类的方法内部的this默认指向类的实例
class Foo{
    printName(name = \'wx\'){
      this.print(`Hello ${name}`)
    }
    print(txt){
      console.log(txt);
    }
  }
let foo = new Foo()
let {printName} = foo
printName() //单独使用,内部的this为undefined,从而报错

//有解决方法吗?有的
class Foo{
    constructor(){
      this.printName = this.printName.bind(this) //绑定this
    }
    printName(name = \'wx\'){
      this.print(`Hello ${name}`)
    }
    print(txt){
      console.log(txt);
    }
  }
let foo = new Foo()
let {printName} = foo
printName() //Hello wx

知识点4:静态方法不会被实例继承,只能通过类来调用

class Foo {
    static staticMethod() {
      return \'static method\'
    }
  }
let foo = new Foo()
// foo.staticMethod() // Uncaught TypeError: foo.staticMethod is not a function
Foo.staticMethod() // static method

知识点5:类的继承

class superFoo extends Foo { // extents关键字
    constructor(a, b) {
      super(a, b) //super代表父类,子类的构造函数必须执行一次super函数
      this.superProp = \'prop\'
    }
}

class A {
    constructor() {
      this.propOfA = 123
    }
    getName() {
      return \'wx\'
    }
  }
  class B extends A {
    constructor() {
      super()
    }
    getName() {
      return super.propOfA // undefined
    }
  }
let b = new B()
console.log(b.getName())

注意点:super可以作为函数,也可以作为对象使用,那它在不同的形式里代表什么?
ES6+知识点梳理及背后原理+工作中推荐用法

未完待续。。。

参考资料链接:

  1. ES6 入门教程--阮一峰
  2. 近一万字的ES6语法知识点补充
  3. 一文快速掌握 es6+新特性及核心语法
  4. ES6 完全使用手册
  5. ECMAScript 2016、2017和2018中所有新特性

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000021897302

展开阅读全文

发表评论

登录后才能评论