常用ES6-ES10知识点总结
在工作中我们会常用到的一些es6-es10的一些特性还记得多少,今天就让我们重新复习一遍
ES6语法
1.Let
1.let声明的变量具有块级作用域,
{ let a = 1 } console.log(a); //undefineda
变量是在代码块{}
中使用 let 定义的,它的作用域是这个代码块内部,外部无法访问。
2.let声明的全局变量不是全局对象的属性
var a = 1 console.log(window.a); //1
let a = 1 console.log(window.a); // undefined
3.用let重定义变量会抛出一个语法错误
var a = 1 var a = 2 console.log(a) //2 如果是 let ,则会报错 let a = 1 let a = 2 // VM131:1 Uncaught SyntaxError: Identifier 'a' has already been declared // at <anonymous>:1:1
2.Const
const
除了具有let
的块级作用域和不会变量提升外,还有就是它定义的是常量,在用const
定义变量后,我们就不能修改它了,对变量的修改会抛出异常。
const A= 123; console.log(A); A = 1; console.log(A); // Uncaught TypeError: Assignment to constant variable.
在 ES6 中新增了很多实用的原生 API,方便开发者对 Array 的操控性更强,如 for…of、from、of、fill、find、findIndex等。
3.for…of
在es5之前遍历数组
for (var i = 0; i < array.length; i++) { console.log(array[i]); }
在es6之前遍历数组
for (let val of [1,2,3]) { console.log(val); } // 1,2,3
4.Array.prototype.from()
数组是开发中经常用到的数据结构,它非常好用。在 JavaScript 的世界里有些对象被理解为数组,然而缺不能使用数组的原生 API,比如函数中的 arguments、DOM中的 NodeList等。当然,还有一些可遍历的对象,看上去都像数组却不能直接使用数组的 API,因为它们是伪数组(Array-Like)。要想对这些对象使用数组的 API 就要想办法把它们转化为数组,传统的做法是这样的:
let args = [].slice.call(arguments); let imgs = [].slice.call(document.querySelectorAll('img'));
基本原理是使用 call 将数组的 api 应用在新的对象上,换句话说是利用改变函数的上下文来间接使用数组的 api。在 ES6 中提供了新的 api 来解决这个问题,就是 Array.from,代码如下:
let args = Array.from(arguments); let imgs = Array.from(document.querySelectorAll('img'));
5.Array.prototype.of()
Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。
Array.of(7); // [7] Array.of(1, 2, 3); // [1, 2, 3] Array(7); // [ , , , , , , ] Array(1, 2, 3); // [1, 2, 3]
6.Array.prototype.fill()
fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
let array = [1, 2, 3, 4] array.fill(0, 1, 2) // [1,0,3,4]
7.Array.prototype.find()
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
let array = [5, 12, 8, 130, 44]; let found = array.find(function(element) { return element > 10; }); console.log(found); // 12
8.Array.prototype.findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。
let array = [5, 12, 8, 130, 44]; let found = array.findIndex(function(element) { return element > 10; }); console.log(found); // 1
9.Class
对于面向对象编程而言,更关注类的声明、属性、方法、静态方法、继承、多态、私有属性。
首先我们要先来说明在 JavaScript 世界里如何声明一个 “类”。在 ES6 之前大家都是这么做的:
let Animal = function (type) { this.type = type this.walk = function () { console.log(`I am walking`) } } let dog = new Animal('dog') let monkey = new Animal('monkey')
在 ES6 中把类的声明专业化了,不在用 function 的方式了,请看:
class Animal { constructor (type) { this.type = type } walk () { console.log(`I am walking`) } } let dog = new Animal('dog') let monkey = new Animal('monkey')
10.Setters & Getters
对于类中的属性,可以直接在 constructor 中通过 this 直接定义,还可以直接在类的顶层来定义:
class Animal { constructor (type, age) { this.type = type this._age = age } get age () { return this._age } set age (val) { this._age = val } }
11.静态方法
静态方法是面向对象最常用的功能,在 ES5 中利用 function 实现的类是这样实现一个静态方法的。
let Animal = function (type) { this.type = type this.walk = function () { console.log(`I am walking`) } } Animal.eat = function (food) { console.log(`I am eating`); }
在 ES6 中使用 static 的标记是不是静态方法,代码如下:
class Animal { constructor (type) { this.type = type } walk () { console.log(`I am walking`) } static eat () { console.log(`I am eating`) } }
12.继承
面向对象只所以可以应对复杂的项目实现,很大程度上要归功于继承。如果对继承概念不熟悉的同学,可以自行查询。在 ES5 中怎么实现继承呢?
// 定义父类 let Animal = function (type) { this.type = type } // 定义方法 Animal.prototype.walk = function () { console.log(`I am walking`) } // 定义静态方法 Animal.eat = function (food) { console.log(`I am eating`) } // 定义子类 let Dog = function () { // 初始化父类 Animal.call(this, 'dog') this.run = function () { console.log('I can run') } } // 继承 Dog.prototype = Animal.prototype
从代码上看,是不是很繁琐?而且阅读性也较差。再看看 ES6 是怎么解决这些问题的:
class Animal { constructor (type) { this.type = type } walk () { console.log(`I am walking`) } static eat () { console.log(`I am eating`) } } class Dog extends Animal { constructor () { super('dog') } run () { console.log('I can run') } }
13.默认参数
对于函数而言,经常会用到参数,关于参数的默认值通常都是写在函数体中,如在 ES5 的时候大家都会这么写:
function f (x, y, z) { if (y === undefined) y = 7; if (z === undefined) z = 42; return x + y + z; }; f(1) === 50;
当一个函数有很多参数涉及初始化的时候,这样写代码极其丑陋,所以在 ES6 中改变了对这种知识的写法:
function f (x, y = 7, z = 42) { return x + y + z } f(1) === 50
14.Rest Parameter
在写函数的时候,部分情况我们不是很确定参数有多少个,比如求和运算,之前都是这么做的:
function sum () { let num = 0 Array.prototype.forEach.call(arguments, function (item) { num += item * 1 }) return num } console.log(sum(1, 2, 3))// 6 console.log(sum(1, 2, 3, 4))// 10
其实在上面说过,这个代码在 ES5 中可以这么写,在 ES6 就不能这么写了,因为 arguments 的问题。现在需要这样写:
function sum (...nums) { let num = 0 nums.forEach(function (item) { num += item * 1 }) return num } console.log(sum(1, 2, 3))// 6 console.log(sum(1, 2, 3, 4))// 10
当然,Rest Parameter 也可以和其他参数一起来用,比如:
function sum (base, ...nums) { let num = base nums.forEach(function (item) { num += item * 1 }) return num } console.log(sum(30, 1, 2, 3))// 36 console.log(sum(30, 1, 2, 3, 4))// 40
15.箭头函数
箭头函数可以说是 ES6 很大的福利了,不管你是函数式爱好者还是面向对象开发者,函数是必须要用到的东西。之前声明函数需要使用 function,如下:
function hello () { console.log('say hello') } // 或 let hello = function () { console.log('say hello') }
现在可以这样做了:
let hello = () => { console.log('say hello') }
如果带参数该怎么做呢?
let hello = (name) => { console.log('say hello', name) } // 或者 let hello = name => { console.log('say hello', name) }
16.Enhanced Object Properties(增强对象属性)
在 ES6 之前 Object 的属性必须是 key-value 形式,如下:
var x = 0, y = 0; obj = { x: x, y: y };
在 ES6 之后是可以用简写的形式来表达:
var x = 0, y = 0 obj = { x, y }
17.Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。
const target = { a: 1, b: 2 } const source = { b: 4, c: 5 } const returnedTarget = Object.assign(target, source) console.log(target) // expected output: Object { a: 1, b: 4, c: 5 } console.log(returnedTarget) // expected output: Object { a: 1, b: 4, c: 5 }
从语法上可以看出源对象的个数是不限制的(零个或多个),如果是零个直接返回目的对象,如果是多个相同属性的会被后边的源对象的属相覆盖。
let s = Object.assign({ a: 1 }) // {a: 1}
18.RegExp
Sticky
除了u修饰符,ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
const s = 'aaa_aa_a' const r1 = /a+/g const r2 = /a+/y r1.exec(s) // ["aaa"] r2.exec(s) // ["aaa"] r1.exec(s) // ["aa"] r2.exec(s) // null
19.Unicode
ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于 \uFFFF
的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。
/^\uD83D/u.test('\uD83D\uDC2A') // false /^\uD83D/.test('\uD83D\uDC2A') // true
上面代码中,\uD83D\uDC2A
是一个四个字节的UTF-16编码,代表一个字符 “?”。但是,ES5不支持四个字节的UTF-16编码,会将其识别为两个字符,导致第二行代码结果为true。加了u修饰符以后,ES6就会识别其为一个字符,所以第一行代码结果为false。
(1)电字符
点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于 0xFFFF 的 Unicode 字符,点字符不能识别,必须加上u修饰符。
let s = '?'; // /^.$/.test(s) // false /^.$/u.test(s) // true
(2).Unicode字符表示法
ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。
/\u{61}/.test('a') // false /\u{61}/u.test('a') // true /\u{20BB7}/u.test('?') // true
(3)量词
使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的Unicode字符。
/a{2}/.test('aa') // true /a{2}/u.test('aa') // true /?{2}/.test('??') // false /?{2}/u.test('??') // true
(4) 预定义模式
u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的Unicode字符。
/^\S$/.test('?') // false /^\S$/u.test('?') // true
上面代码的\S是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。
利用这一点,可以写出一个正确返回字符串长度的函数。
function codePointLength(text) { const result = text.match(/[\s\S]/gu); return result ? result.length : 0; } const s = '??'; s.length // 4 codePointLength(s) // 2
(5) i修饰符
有些Unicode字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。
/[a-z]/i.test('\u212A') // false /[a-z]/iu.test('\u212A') // true
在 ES6 之前对字符串的处理是相当的麻烦,看如下场景:
1. 字符串很长要换行
字符串很长包括几种情形一个是开发时输入的文本内容,一个是接口数据返回的文本内容。如果对换行符处理不当,就会带来异常。
2. 字符串中有变量或者表达式
如果字符串不是静态内容,往往是需要加载变量或者表达式,这个也是很常见的需求。之前的做法是字符串拼接:
var a = 20 var b = 10 var c = 'JavaScript' var str = 'My age is ' + (a + b) + ' and I love ' + c console.log(str)
从 ES6 开始可以这样定义字符串了。
`string text` `string text line 1 string text line 2` `string text ${expression} string text` 在这里你可以任意插入变量或者表达式,只要用 ${} 包起来就好。
21解构赋值
在 ES6 中新增了变量赋值的方式:解构赋值。如果对这个概念不了解,我们可以快速展示一个小示例一睹风采:
let arr = ['Ilya', 'Kantor'] let firstName = arr[0] let surname = arr[1]
想从数组中找出有意义的项要单独赋值给变量(一一的写),在 ES6 中就可以这样写了:
let [firstName, surname] = ['Ilya', 'Kantor'] console.log(firstName) // Ilya console.log(surname) // Kantor
22.Promise
首先要说明 Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。如果我们使用 Promise 来解决上面那个问题该怎么做呢?
function loadScript (src) { return new Promise((resolve, reject) => { let script = document.createElement('script') script.src = src script.onload = () => resolve(script) script.onerror = (err) => reject(err) document.head.append(script) }) } loadScript('1.js') .then(loadScript('2.js'), (err) => { console.log(err) }) .then(loadScript('3.js'), (err) => { console.log(err) })
通过创建 Promise 对象开启一个异步操作的过程,一般用几步完成多次异步操作:
- new Promise(fn) 返回一个Promise 对象
- 在fn 中指定异步等处理
- 处理结果正常的话,调用resolve(处理结果值)
- 处理结果错误的话,调用reject(Error对象)
1.Promise.prototype.then()
var promise = new Promise(function (resolve, reject) { resolve('传递给then的值') }) promise.then(function (value) { console.log(value) }, function (error) { console.error(error) })
这段代码创建一个 Promise 对象,定义了处理 onFulfilled 和 onRejected 的函数(handler),然后返回这个 Promise 对象。
这个 Promise 对象会在变为 resolve 或者 reject 的时候分别调用相应注册的回调函数。
当 handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
定义的 handler 中产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法
2.Promise.resolve()
一般情况下我们都会使用 new Promise()
来创建 Promise 对象,但是除此之外我们也可以使用其他方法。
Promise.resolve(42).then(function (value) { console.log(value) })
在这段代码中的 resolve(42) 会让这个 Promise 对象立即进入确定(即resolved)状态,并将 42 传递给后面 then 里所指定的 onFulfilled 函数。
3.Promise.reject()
Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。
new Promise(function (resolve, reject) { reject(new Error('出错了')) })
4.Promise.prototype.catch()
捕获异常是程序质量保障最基本的要求,可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。
function test () { return new Promise((resolve, reject) => { reject(new Error('es')) }) } test().catch((e) => { console.log(e.message) // es })
5.Promise.all()
var p1 = Promise.resolve(1) var p2 = Promise.resolve(2) var p3 = Promise.resolve(3) Promise.all([p1, p2, p3]).then(function (results) { console.log(results) // [1, 2, 3] })
Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法。参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Paomise.all 可以处理不同类型的 promose对
6.Promise.race()
var p1 = Promise.resolve(1) var p2 = Promise.resolve(2) var p3 = Promise.resolve(3) Promise.race([p1, p2, p3]).then(function (value) { console.log(value) // 1 })
Promise.race 生成并返回一个新的 Promise 对象。
参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话, 该函数就会返回,并使用这个 Promise 对象的值进行 resolve 或者 reject。
23.Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法,这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
Reflect.apply(Math.floor, undefined, [1.75]) // 1; Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]) // "hello" Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index // 4 Reflect.apply(''.charAt, 'ponies', [3]) // "i"
该方法与ES5中Function.prototype.apply()方法类似:调用一个方法并且显式地指定this变量和参数列表(arguments) ,参数列表可以是数组,或类似数组的对象。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
2.Reflect.construct()
Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(…args).
var d = Reflect.construct(Date, [1776, 6, 4]) d instanceof Date // true d.getFullYear() // 1776
3.Reflect.defineProperty()
静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。
const student = {} Reflect.defineProperty(student, 'name', { value: 'Mike' }) // true student.name // "Mike"
4.Deflect.deleteProperty()
Reflect.deleteProperty 允许你删除一个对象上的属性。返回一个 Boolean 值表示该属性是否被成功删除。它几乎与非严格的 delete operator 相同。
var obj = { x: 1, y: 2 }; Reflect.deleteProperty(obj, "x"); // true obj; // { y: 2 } var arr = [1, 2, 3, 4, 5]; Reflect.deleteProperty(arr, "3"); // true arr; // [1, 2, 3, , 5] // 如果属性不存在,返回 true Reflect.deleteProperty({}, "foo"); // true // 如果属性不可配置,返回 false Reflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false
5.Reflect.get()
Reflect.get() 方法的工作方式,就像从 object (target[propertyKey]) 中获取属性,但它是作为一个函数执行的。
// Object var obj = { x: 1, y: 2 } Reflect.get(obj, 'x') // 1 // Array Reflect.get(['zero', 'one'], 1) // "one" // Proxy with a get handler var x = { p: 1 } var obj = new Proxy(x, { get (t, k, r) { return k + 'bar' } }) Reflect.get(obj, 'foo') // "foobar"
6.Reflect.getOwnPropertyDescriptor()
静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。
Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'x') // {value: "hello", writable: true, enumerable: true, configurable: true} Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'y') // undefined Reflect.getOwnPropertyDescriptor([], 'length') // {value: 0, writable: true, enumerable: false, configurable: false}
7.Reflect.getPrototypeOf()
静态方法 Reflect.getPrototypeOf() 与 Object.getPrototypeOf() 方法是一样的。都是返回指定对象的原型(即,内部的 [[Prototype]] 属性的值)。
8.Reflect.has()
Reflect.has 用于检查一个对象是否拥有某个属性, 相当于in 操作符
9.Reflect.set()
Reflect.set 方法允许你在对象上设置属性。它的作用是给属性赋值并且就像 property accessor 语法一样,但是它是以函数的方式。
// Object var obj = {}; Reflect.set(obj, "prop", "value"); // true obj.prop; // "value" // Array var arr = ["duck", "duck", "duck"]; Reflect.set(arr, 2, "goose"); // true arr[2]; // "goose" // It can truncate an array. Reflect.set(arr, "length", 1); // true arr; // ["duck"]; // With just one argument, propertyKey and value are "undefined". var obj = {}; Reflect.set(obj); // true Reflect.getOwnPropertyDescriptor(obj, "undefined"); // { value: undefined, writable: true, enumerable: true, configurable: true }
10.Reflect.setPrototypeOf()
Reflect.setPrototypeOf 方法改变指定对象的原型 (即,内部的 [[Prototype]] 属性值)
Reflect.setPrototypeOf({}, Object.prototype); // true // It can change an object's [[Prototype]] to null. Reflect.setPrototypeOf({}, null); // true // Returns false if target is not extensible. Reflect.setPrototypeOf(Object.freeze({}), null); // false // Returns false if it cause a prototype chain cycle. var target = {}; var proto = Object.create(target); Reflect.setPrototypeOf(target, proto); // false
24.Proxy
let p = new Proxy(target, handler)
从服务端获取的数据希望是只读,不允许在任何一个环节被修改。
// response.data 是 JSON 格式的数据,来自服务端的响应 // 在 ES5 中只能通过遍历把所有的属性设置为只读 for (let [key] of Object.entries(response.data)) { Object.defineProperty(response.data, key, { writable: false }) }
如果我们使用 Proxy 就简单很多了:
let data = new Proxy(response.data, { set(obj, key, value) { return false } })
25.Generator
什么是 JavaScript Generators 呢?通俗的讲 Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。如果这句话不好理解,可以看下接下来的示例。
ES5之前做法
for (let i = 0; i < 5; i += 1) { console.log(i) } // this will return immediately 0 -> 1 -> 2 -> 3 -> 4
用 Generator
function * generatorForLoop () { for (let i = 0; i < 5; i += 1) { yield console.log(i) } } const genForLoop = generatorForLoop() console.log(genForLoop.next()) // first console.log - 0 console.log(genForLoop.next()) // 1 console.log(genForLoop.next()) // 2 console.log(genForLoop.next()) // 3
对比下代码,常规的循环只能一次遍历完所有值,Generator 可以通过调用 next 方法拿到依次遍历的值,让遍历的执行变得“可控”。
语法
function * gen () { yield 1 yield 2 yield 3 } let g = gen() // "Generator { }"
这个是 Generator 的定义方法,有几个点值得注意:
- 比普通函数多一个 *
- 函数内部用 yield 来控制程序的执行的“暂停”
- 函数的返回值通过调用 next 来“恢复”程序执行
yield 表达式
yield 表达式的返回值是 undefined,但是遍历器对象的 next 方法可以修改这个默认值
function * gen () { let val val = yield 1 console.log(`1:${val}`) // 1:undefined val = yield 2 console.log(`2:${val}`) // 2:undefined val = yield 3 console.log(`3:${val}`) // 3:undefined } var g = gen() console.log(g.next()) // {value: 1, done: false} console.log(g.next()) // {value: 2, done: false} console.log(g.next()) // {value: 3, done: false} console.log(g.next()) // {value: undefined, done: true}
yeild * 是委托给另一个遍历器对象或者可遍历对象
function * gen () { let val val = yield 1 console.log(`1:${val}`) // 1:undefined val = yield 2 console.log(`2:${val}`) // 2:undefined val = yield [3, 4, 5] console.log(`3:${val}`) // 3:undefined }
enerator 对象的 next 方法,遇到 yield 就暂停,并返回一个对象,这个对象包括两个属性:value 和 done。
26.module
在 ES6 之前,JS 文件之间的导入、导出是需要借助 require.js、sea.js。现在,大家可以使用 import、export 来实现原生 JavaScript 的导入、导出了。
export
导出变量或者常量
export const name = 'hello' export let addr = 'BeiJing City' export var list = [1, 2 , 3]
import
const name = 'hello' let addr = 'BeiJing City' var list = [1, 2 , 3] export { name as cname, addr as caddr } export default list
ES7语法
1.Array.prototype.includes
Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
let array1 = [1, 2, 3] console.log(array1.includes(2)) // expected output: true var pets = ['cat', 'dog', 'bat'] console.log(pets.includes('cat')) // expected output: true console.log(pets.includes('at')) // expected output: false
在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find(function (item) { return item === 2 }))
2.Math.pow
我们一直都是这样用的:
console.log(Math.pow(2, 3)) // 8(2的三次方)
在 ES7 可以这样写了:
console.log(2 ** 3)
ES8语法
1.Async/Await
async 和 await 是 Promise 的拓展,如果对 Promise 还不了解的话,请移步 Promise 章节进行学习。
async function firstAsync () { return 27 } firstAsync().then(console.log) // 27
也就是说上面的代码等同于:
async function firstAsync () { return Promise.resolve(27) }
async 的作用我们清楚了,await呢?话不多说:
async function firstAsync () { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("Now it's done!"), 1000) }) // wait until the promise returns us a value let result = await promise // "Now it's done!" return result } firstAsync().then(console.log)
这段代码使用了 await,从这个字面的意思看就是“等待”,它等什么呢?很简单,它等 Promise 返回结果。上面代码的意思是 async 开启了一个 Promise 对象,这个函数内部嵌套了一个 Promise 操作,这个操作需要等 1 秒才返回 “Now it’s done!” 这个结果。也就是说 await 在拿到这个结果之前不会继续执行,一直等到结果才往后继续执行,也就是 async function 声明的 Promise 才会响应(console.log才执行)。
常规
async function firstAsync () { let result = await 27 return result } firstAsync().then(console.log) // 27
对象
async function firstAsync () { let result = await { a: 1 } return result } firstAsync().then(console.log) // { a: 1 }
非 Promise 对象将用 Promise.resolve 来包装,而 await 后边的值作为 resolve 的参数。
2. await 不可以脱离 async 单独使用
function firstAsync() { let promise = Promise.resolve(10); let result = await promise; // Syntax error }
2.Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for…in,但是for…in还会遍历原型上的属性值)。
let grade = { 'lilei': 98, 'hanmei': 87 } console.log(Object.values(grade)) // [98, 87]
3.Object.entries
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)
let grade = { 'lilei': 98, 'hanmei': 87 } for (let [key, value] of grade) { console.log(key, value) // Uncaught TypeError: grade is not iterable }
我们知道 Object 是不可直接遍历的,上述代码足以说明直接遍历触发了错误。如果使用 Object.entries() 则可以完成遍历任务。
let grade = { 'lilei': 98, 'hanmei': 87 } for (let [k, v] of Object.entries(grade)) { console.log(k, v) // lilei 98 // hanmei 87 }
4.String padding
在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
这两个方法有什么用武之地呢?回忆下我们处理日期的时候经常要格式化,比如 01、02、10等。
for (let i = 1; i < 31; i++) { if (i < 10) { console.log(`0${i}`) } else { console.log(i) } }
之前输出1号到31号,需要手动判断是否小于10号,小于的在前面增加 0。现在可以这样做了:
for (let i = 1; i < 31; i++) { console.log(i.toString().padStart(2, '0')) }
String.prototype.padStart 和 String.prototype.padEnd 用法是相同的,只是一个在开头补白一个在结尾。
5.Object.getOwnPropertyDescriptors()
想理解 Object.getOwnPropertyDescriptors 这个方法之前,首先要弄懂什么是描述符(descriptor)?
const data = { Portland: '78/50', Dublin: '88/52', Lima: '58/40' }
还是上述那个对象,这里有 key 和 value,上边的代码把所有的 key、value 遍历出来,如果我们不想让 Lima 这个属性和值被枚举怎么办?
Object.defineProperty(data, 'Lima', { enumerable: false }) Object.entries(data).map(([city, temp]) => { console.log(`City: ${city.padEnd(16)} Weather: ${temp}`) // City: Portland Weather: 78/50 // City: Dublin Weather: 88/52 })
很成功,Lima 没有被遍历出来,那么 defineProperty 的第三个参数就是描述符(descriptor)。这个描述符包括几个属性:
- value [属性的值]
- writable [属性的值是否可被改变]
- enumerable [属性的值是否可被枚举]
- configurable [描述符本身是否可被修改,属性是否可被删除]
ES9语法
1.for await of
我们知道 for…of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?
function Gen (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(time) }, time) }) } async function test () { let arr = [Gen(2000), Gen(100), Gen(3000)] for (let item of arr) { console.log(Date.now(), item.then(console.log)) } } test() // 1560090138232 Promise {<pending>} // 1560090138234 Promise {<pending>} // 1560090138235 Promise {<pending>} // 100 // 2000 // 3000
await 的作用,它可以中断程序的执行直到这个 Promise 对象的状态发生改变,
function Gen (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(time) }, time) }) } async function test () { let arr = [Gen(2000), Gen(100), Gen(3000)] for (let item of arr) { console.log(Date.now(), await item.then(console.log)) } } test() // 2000 // 1560091834772 undefined // 100 // 1560091836774 undefined // 3000 // 1560091836775 undefined
从返回值看确实是按照任务的先后顺序进行的,其中原理也有说明是利用了 await 中断程序的功能。不过,在 ES9 中也可以用 for…await…of 的语法来操作:
function Gen (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(time) }, time) }) } async function test () { let arr = [Gen(2000), Gen(100), Gen(3000)] for await (let item of arr) { console.log(Date.now(), item) } } test() // 1560092345730 2000 // 1560092345730 100 // 1560092346336 3000
2.Promise.prototype.finally()
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
let connection; db.open() .then(conn => { connection = conn; return connection.select({ name: 'Jane' }); }) .then(result => { // Process result // Use `connection` to make more queries }) ··· .catch(error => { // handle errors }) .finally(() => { connection.close(); });
3.Object Rest Spread
前面在讲 function 的 Rest & Spread 方法,忘却的同学可以去复习下。在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = { a: 1, b: 2 } const output = { ...input, c: 3 } console.log(output) // {a: 1, b: 2, c: 3}
ES10语法
1.JSON.stringify()
JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
JSON.stringify('\u{D800}') === '"\\ud800"' // true
2.Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const newArray = arr.flat(depth) 语法
const numbers = [1, 2, [3, 4, [5, 6]]] console.log(numbers.flat()) // [1, 2, 3, 4, [5, 6]]
此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
const numbers = [1, 2, [3, 4, [5, 6]]] console.log(numbers.flat(2)) // [1, 2, 3, 4, 5, 6]
3.Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const new_array = arr.flatMap(function callback(currentValue[, index[, array]]) { // 返回新数组的元素 }[, thisArg])
参数 | 含义 | 必选 |
callback | 可以生产一个新数组中的元素的函数,可以传三个参数,currentValue,index,array | y |
thisArg | 遍历函数this的指向 | N |
const numbers = [1, 2, 3] numbers.map(x => [x * 2]) // [[2], [4], [6]] numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个小示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下 MDN 的示例:
let arr = ['今天天气不错', '', '早上好'] arr.map(s => s.split('')) // [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]] arr.flatMap(s => s.split('')) // ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
4.String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
let str = ' foo ' console.log(str.length) // 8 str = str.trimStart() console.log(str.length) // 5
5.String.prototype.trimEnd()
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
let str = ' foo ' console.log(str.length) // 8 str = str.trimEnd() console.log(str.length) // 6
6.String.prototype.matchAll()
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器
在了解 matchAll 之前,我们回顾下 ES10 之前一共有多少种正则全部遍历的方法。
1. RegExp.prototype.exec() with /g
function collectGroup1 (regExp, str) { const matches = [] while (true) { const match = regExp.exec(str) if (match === null) break // Add capture of group 1 to `matches` matches.push(match[1]) } return matches } collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`) // [ 'foo', 'bar', 'baz' ]
2. String.prototype.match() with /g
如果用 .match 方法结合 /g 的正则模式,将会把所有的匹配打包成一个数组返回,换句话说所有的捕获被忽略。 'abeabd'.match(/(a)b(?=e)/g) // ["ab"]
不过如果没有使用 /g 的正则模式,.match 的效果和 RegExp.prototype.exec() 是一致的。
3. String.prototype.replace()
function collectGroup1 (regExp, str) { const matches = [] function replacementFunc (all, first) { matches.push(first) } str.replace(regExp, replacementFunc) return matches } collectGroup1(/"([^"]*)"/ug, `"foo" and "bar" and "baz"`) // ["foo", "bar", "baz"]
现在看下 .matchAll 方法,可以这样做:
function collectGroup1 (regExp, str) { let results = [] for (const match of str.matchAll(regExp)) { results.push(match[1]) } return results } collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`) // ["foo", "bar", "baz"]
7.Object.fromEntries()
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
Object.fromEntries([['foo', 1], ['bar', 2]]) // {foo: 1, bar: 2}
示例
let obj = { abc: 1, def: 2, ghij: 3 } let res = Object.fromEntries( Object.entries(obj) .filter(([ key, val ]) => key.length === 3) .map(([ key, val ]) => [ key, val * 2 ]) ) console.log(res) // res is { 'abc': 2, 'def': 4 }
8.Symbol.prototype.description
我们知道,Symbol 的描述只被存储在内部的 [[Description]],没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol('My name is axuebin') console.log(name.toString()) // Symbol(My name is axuebin) console.log(name) // Symbol(My name is axuebin) console.log(name === 'Symbol(My name is axuebin)') // false console.log(name.toString() === 'Symbol(My name is axuebin)') // true
9.Function.prototype.toString()
toString() 方法返回一个表示当前函数源代码的字符串
function hello (msg) { console.log('hello') } console.log(hello.toString()) // function hello (msg) { // console.log('hello') // }
10.try…catch
在 ES10 之前我们都是这样捕获异常的:
try { // tryCode } catch (err) { // catchCode }
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
try { console.log('Foobar') } catch { console.error('Bar') }
11.BigInt
在 ES10 增加了一个数据类型:BigInt,用于处理超过 2^53 的数字。
const aBigInt = 11n; const aNumber = 156; const aBigInt = BigInt(aNumber); aBigInt === 156n // true typeof aBigInt === bigint // true
以上就是在工作中比较常用的es6-es10语法知识点