ES6 对象
对象字面量
属性的简洁表示法
ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。
1 | const age = 12; |
方法名也可以简写
1 | const person = { |
如果是Generator 函数,则要在前面加一个星号:
1 | const obj = { |
属性名表达式
ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
1 | const obj = { |
注意点:属性的简洁表示法和属性名表达式不能同时使用,否则会报错。
1 | const hello = "Hello"; |
对象拓展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
基本用法
1 | let person = {name: "Amy", age: 15}; |
可用于合并两个对象
1 | let age = {age: 15}; |
对象的新方法
一、Object.assign
1 | Object.assign(target, source_1, ···) |
用于将源对象的所有可枚举属性复制到目标对象中。
基本用法
1 | let target = {a: 1}; |
- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
1 | Object.assign(3); // Number {3} |
因为 null 和 undefined 不能转化为对象,所以会报错:
1 | Object.assign(null); // TypeError: Cannot convert undefined or null to object |
注意点
assign 的属性拷贝是浅拷贝:
1 | let sourceObj = { a: { b: 1}}; |
同名属性替换
1 | targetObj = { a: { b: 1, c:2}}; |
数组的处理
1 | Object.assign([2,3], [5]); // [5,3] |
会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
二、Object.is
1 | Object.is(value1, value2) |
用来比较两个值是否严格相等,与(===)基本类似。
基本用法
1 | Object.is("q","q"); // true |
与(===)的区别
1 | //一是+0不等于-0 |
ES6 数组
数组创建
Array.of
将参数中所有值作为元素形成数组。
1 | console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] |
Array.from
将类数组对象或可迭代对象转化为数组。
1 | // 参数为数组,返回与原数组一样的数组 |
array.from 接收的参数: Array.from(arrayLike[, mapFn[, thisArg]]) ; 返回值为转换后的数组。
arrayLike:
想要转换的类数组对象或可迭代对象。
1
console.log(Array.from([1, 2, 3])); // [1, 2, 3]
mapFn:
可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
1
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
thisArg:
可选,用于指定 map 函数执行时的 this 对象。
1
2
3
4
5
6
7
8
9let map = {
do: function(n) {
return n * 2;
}
}
let arrayLike = [1, 2, 3];
console.log(Array.from(arrayLike, function (n){
return this.do(n);
}, map)); // [2, 4, 6]
类数组对象
一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。
1 | let arr = Array.from({ |
转换可迭代对象
转换map
1 | let map = new Map(); |
转换set
1 | let arr = [1, 2, 3]; |
转换字符串
1 | let str = 'abc'; |
数组拓展方法
查找
find()
查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
1 | let arr = Array.of(1, 2, 3, 4); |
findIndex()
查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
1 | let arr = Array.of(1, 2, 1, 3); |
填充
fill()
将一定范围索引的数组元素内容填充为单个指定的值。
1 | let arr = Array.of(1, 2, 3, 4); |
copyWithin()
将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。
1 | // 参数1:被修改的起始索引 |
遍历
entries(): 遍历键值对。
keys(): 遍历键名;
values(): 遍历键值;
1 | for(let [key, value] of ['a', 'b'].entries()){ |
包含
includes()
数组是否包含指定值。
注意:与 Set 和 Map 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。
1 | // 参数1:包含的指定值 |
嵌套数组转一维数组
flat()
1 | console.log([1 ,[2, 3]].flat()); // [1, 2, 3] |
flatMap()
先对数组中每个元素进行了的处理,再对数组执行 flat() 方法。
1 | // 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组 |
数组缓冲区
数组缓冲区是内存中的一段地址。
定型数组的基础。
实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
创建数组缓冲区
通过构造函数创建:
1 | let buffer = new ArrayBuffer(10); |
视图
视图是用来操作内存的接口。
视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。
DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型。
1 | // 默认 DataView 可操作数组缓冲区全部内容 |
定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区。
创建
通过数组缓冲区生成
1 | let buffer = new ArrayBuffer(10), |
通过构造函数
1 | let view = new Int32Array(10); |
注意要点
length 属性不可写,如果尝试修改这个值,在非严格模式下会直接忽略该操作,在严格模式下会抛出错误。
1 | let view = new Int16Array([1, 2]); |
定型数组可使用 entries()、keys()、values()进行迭代。
1 | let view = new Int16Array([1, 2]); |
find() 等方法也可用于定型数组,但是定型数组中的方法会额外检查数值类型是否安全,也会通过 Symbol.species 确认方法的返回值是定型数组而非普通数组。concat() 方法由于两个定型数组合并结果不确定,故不能用于定型数组;另外,由于定型数组的尺寸不可更改,可以改变数组的尺寸的方法,例如 splice() ,不适用于定型数组。
1 | let view = new Int16Array([1, 2]); |
所有定型数组都含有静态 of() 方法和 from() 方法,运行效果分别与 Array.of() 方法和 Array.from() 方法相似,区别是定型数组的方法返回定型数组,而普通数组的方法返回普通数组。
1 | let view = Int16Array.of(1, 2); |
定型数组不是普通数组,不继承自 Array 。
1 | let view = new Int16Array([1, 2]); |
定型数组中增加了 set() 与 subarray() 方法。 set() 方法用于将其他数组复制到已有定型数组, subarray() 用于提取已有定型数组的一部分形成新的定型数组。
1 | // set 方法 |
拓展运算符
复制数组
1 | let arr = [1, 2], |
ES6 迭代器
Iterator
Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:
- 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。
- 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)。
迭代过程
迭代原理
- 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
- 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束
- 当 done 为 true 时则遍历结束
下面通过一个简单的例子进行说明:
1 | const items = ["zero", "one", "two"]; |
上面的例子,首先创建一个数组,然后通过 Symbol.iterator 方法创建一个迭代器,之后不断的调用 next 方法对数组内部项进行访问,当属性 done 为 true 时访问结束。
迭代器是协议(使用它们的规则)的一部分,用于迭代。该协议的一个关键特性就是它是顺序的:迭代器一次返回一个值。这意味着如果可迭代数据结构是非线性的(例如树),迭代将会使其线性化。
可迭代的数据结构
可迭代结构
- Array
- String
- Map
- Set
- Dom元素(正在进行中)
for … of 循环
for…of 是 ES6 新引入的循环,用于替代 for..in 和 forEach() ,并且支持新的迭代协议。它可用于迭代常规的数据类型,如 Array 、 String 、 Map 和 Set 等等。
循环方法
1 | let myMap = new Map(); |
可迭代数据结构
of 操作数必须是可迭代,这意味着如果是普通对象则无法进行迭代。如果数据结构类似于数组的形式,则可以借助 Array.from() 方法进行转换迭代。
1 | const arrayLink = {length: 2, 0: "zero", 1: "one"} |
let 、const 和 var 用于 for..of
如果使用 let 和 const ,每次迭代将会创建一个新的存储空间,这可以保证作用域在迭代的内部。
1 | const nums = ["zero", "one", "two"]; |
从上面的例子我们看到,最后一句会报异常,原因 num 的作用域只在循环体内部,外部无效,具体可查阅 let 与 const 章节。使用 var 则不会出现上述情况,因为 var 会作用于全局,迭代将不会每次都创建一个新的存储空间。
1 | const nums = ["zero", "one", "two"]; |
ES6 Class 类
概述
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
基础用法
类定义
类表达式可以为匿名或命名。
1 | // 匿名类 |
类声明
1 | class Example { |
注意要点:不可重复声明。
1 | class Example{} |
注意要点
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function 关键字。
方法间不能加分号。
类的主体
属性
prototype
ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法
1 | Example.prototype={ |
添加方法
1 | Object.assign(Example.prototype,{ |
静态属性
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
1 | class Example { |
公共属性
1 | class Example{} |
实例属性
实例属性:定义在实例对象( this )上的属性。
1 | class Example { |
name属性
返回跟在 class 后的类名(存在时)。
1 | let Example=class Exam { |
方法
constructor 方法
constructor 方法是类的默认方法,创建类的实例化对象时被调用。
1 | class Example{ |
返回对象
1 | class Test { |
静态方法
1 | class Example{ |
原型方法
1 | class Example { |
实例方法
1 | class Example { |
类的实例化
new
class 的实例化必须通过 new 关键字。
1 | class Example {} |
实例化对象
共享原型对象
1 | class Example { |
decorator
decorator 是一个函数,用来修改类的行为,在代码编译时产生作用。
类修饰
一个参数
第一个参数 target,指向类本身。
1 | function testable(target) { |
多个参数——嵌套实现
1 | function testable(isTestable) { |
实例属性
上面两个例子添加的是静态属性,若要添加实例属性,在类的 prototype 上操作即可。
方法修饰
3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)
1 | class Example { |
修饰器执行顺序
由外向内进入,由内向外执行。
1 | class Example { |
封装与继承
getter / setter
定义
1 | class Example{ |
getter 不可单独出现
1 | class Example { |
getter 与 setter 必须同级出现
1 | class Father { |
extends
通过 extends 实现类的继承。
1 | class Child extends Father { ... } |
super
子类 constructor 方法中必须有 super ,且必须出现在 this 之前。
1 | class Father { |
调用父类构造函数,只能出现在子类的构造函数。
1 | class Father { |
调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
1 | class Child2 extends Father { |
注意要点
不可继承常规对象
1 | var Father = { |
ES6 模块
概述
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
ES6 模块的特点
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
export 与 import
基本 用法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
1 | /*-----export [test.js]-----*/ |
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
as 的用法
export 命令导出的接口名称,须和模块内部的变量有一一对应关系。
导入的变量名,须和导出的接口名称相同,即顺序可以不一致。
1 | /*-----export [test.js]-----*/ |
不同模块导出接口名称命名重复, 使用 as 重新定义变量名。
import 命令特点
只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
1 | import {a} from "./xxx.js" |
单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。
1 | import { a } "./xxx.js"; |
静态执行特性:import 是静态执行,所以不能使用表达式和变量。
1 | import { "f" + "oo" } from "methods"; |
export default 命令
基本属性
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
1 | var a = "My name is Tom!"; |
复合使用
export 和 import 复合使用
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
ES6 Promise对象
概述
是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 状态
状态的特点
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
1 | const p1 = new Promise(function(resolve,reject){ |
状态的缺点
无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
then方法
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
1 | const p = new Promise(function(resolve,reject){ |
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
通过多次调用 .then 可以添加多个回调函数, 他们会按照插入顺序并且独立运行
1 | const p = new Promise(function(resolve,reject){ |
then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。
then 方法注意点
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise。
注意总是返回或终止 Promise 链。
1 | const p1 = new Promise(function(resolve,reject){ |
创建新 Promise 但忘记返回它时,对应链条被打破,导致 p4 会与 p2 和 p3 同时进行。
大多数浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error));
ES6 Generator 函数
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。 基本用法
Generator 函数组成
Generator 有两个区分于普通函数的部分:
- 一是在 function 后面,函数名之前有个 * ;
- 函数内部有 yield 表达式。
其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。
1 | function* func(){ |
执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
1 | f.next(); |
第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 ‘1’,作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。
第二次调用 next 方法时,同上步 。
第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。
第四次调用 next 方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。如果执行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。
函数返回的遍历器对象的方法
next 方法
一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步yield的返回值。
1 | function* sendParameter(){ |
next不传参
1 | var sendp1 = sendParameter(); |
除了使用 next ,还可以使用 for… of 循环遍历 Generator 函数生产的 Iterator 对象。
return 方法
return 方法返回给定值,并结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。
1 | function* foo(){ |
遍历器对象抛出了两个错误,第一个被 Generator 函数内部捕获,第二个因为函数体内部的catch 函数已经执行过了,不会再捕获这个错误,所以这个错误就抛出 Generator 函数体,被函数体外的 catch 捕获。
yield*表达式
yield* 表达式表示 yield 返回一个遍历器对象,用于在 Generator 函数内部,调用另一个 Generator 函数。
1 | function* callee() { |
使用场景
实现Iterator
为不具备 Iterator 接口的对象提供遍历方法。
1 | function* objectEntries(obj) { |
Reflect.ownKeys() 返回对象所有的属性,不管属性是否可枚举,包括 Symbol。
jane 原生是不具备 Iterator 接口无法通过 for… of遍历。这边用了 Generator 函数加上了 Iterator 接口,所以就可以遍历 jane 对象了。
ES6 async 函数
async
async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。
语法
1 | async function name([param[, param[, ... param]]]) { statements } |
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
返回值
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
1 | async function helloAsync(){ |
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。
1 | function testAwait(){ |
await
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
语法
1 | [return_value] = await expression; |
- expression: 一个 Promise 对象或者任何要等待的值。
返回值
返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
1 | function testAwait (x) { |
正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。
1 | function testAwait(){ |
await针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。