0%

JavaScript-ES6(下)

迭代器

Iterator

iterator` 是 ES6 新增的一种遍历机制,类似于数据解构的代理,使用它可以让我们不用直接对数据解构对象本身进行操作。

核心

  • 迭代器是一个同一的接口,它的作用是使各种数据结构可以被便捷访问,它是通过一个键为Symbol.iterator 的方法来实现。
  • 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)

迭代过程

  • 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
  • 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束
  • 当 done 为 true 时则遍历结束
1
2
3
4
5
6
7
8
// 迭代器 Iterator 新的一种遍历机制
// 通过Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
const arr = ["red", "blue", "yellow"];
let iterator1 = arr[Symbol.iterator]();
// 相当于有一个指针每调用一次next 则返回一个数据
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());

上面的例子,首先创建一个数组,然后通过 Symbol.iterator 方法创建一个迭代器,之后不断的调用next方法对数组内部项进行访问,当属性done 为 true 时访问结束

迭代器是协议(使用它们的规则)的一部分,用于迭代。该协议的一个关键特性就是它是顺序的:迭代器一次返回一个值。这意味着如果可迭代数据结构是非线性的(例如树),迭代将会使其线性化。

可迭代的数据结构

  • Array
  • String
  • Map
  • Set
  • Dom元素(正在进行中)

我们使用for...of循环对数据结构进行迭代

for…of循环

for…of 是 ES6 新引入的循环,用于替代 for..inforEach() ,并且支持新的迭代协议。它可用于迭代常规的数据类型,如 Array 、 String 、 Map 和 Set 等等。

Array

数组 ( Array) 和类型数组 ( TypedArray) 他们是可迭代的。

1
2
3
4
// Array 
for (let item of [1, 2, 3, 4]) {
console.log(item); // 1 2 3 4
}

String

字符串是可迭代的,单他们遍历的是 Unicode 码,每个码可能包含一个到两个的 Javascript字符。

1
2
3
for (let item of 'z\uD83D\uDC0A') {
console.log(item); // z \uD83D\uDC0A
}

Map

Map 主要是迭代它们的 entries ,每个 entry都会被编码为 [key, value] 的项, entries 是以确定的形势进行迭代,其顺序是与添加的顺序相同。

1
2
3
4
5
6
let map = new Map();
map.set(0, "zhangsan");
map.set(1, "lisi");
for (let item of map) {
console.log(item); // [0, "zhangsan"] [1, "lisi"]
}

注意: WeakMaps不可迭代

Set

Set 是对其元素进行迭代,迭代的顺序与其添加的顺序相同

1
2
3
4
5
6
let set = new Set();
set.add('2');
set.add('1');
for (let item of set) {
console.log(item); // 2 1
}

注意: WeakSets不可迭代

arguments

arguments 目前在 ES6 中使用越来越少,但也是可遍历的

1
2
3
4
5
6
7
function fn() {
for (let item of arguments) {
console.log(item); // 1 2 false null
}
}

fn(1, "2", false, null);

普通对象不可迭代

普通对象是由 object 创建的,不可迭代

1
2
3
4
// TypeError
for (let item of {}) {
console.log(item);
}

Reflect 与 Proxy

ProxyReflect 是ES6 为了操作对象引入的API

Proxy可以对目标对象读取,函数调用等操作进行拦截,然后进行处理,它不直接操作对象,而是像代理模式

通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

Reflect 可以获取目标对象的行为,它与Object类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与Proxy是对应的

基本用法

proxy

一个Proxy对象由两个部分组成:targethandler.在通过Proxy构造函数生成实例对象是,需要提供这两个参数,

target:被代理对象,即目标对象

handler:是一个对象,声明了代理target的指定行为

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
// 1.Proxy 基本用法
// 创建目标对象
let target = {
name: 'tom',
age: 19
}
// 声明改目标对象具有的特殊行为
let handle = {
get: function (target, key) {
return target[key];
},
set: function (target, key, value) {
target[key] = value;
}
}
// let proxy = new Proxy(target, handle);
// // 自动调用 handle 中的get方法并获取name属性
// console.log(proxy.name);
// // 自动调用 handle 中的set方法并设对应属性值
// proxy.age = 25;

// 2.Proxy 用法 target 为空时
let targetEpt = {};
let proxyEpt = new Proxy(targetEpt, handle);
// 调用 get 方法,此时目标对象为空,没有 name 属性
// proxyEpt.name;
//
proxyEpt.name = "张三";
// 此时再调用 该对象已经存在name属性
console.log(proxyEpt.name);

// proxy代理是对目标对象的浅拷贝 ,因此目标对象与代理对象互相影响
console.log(proxyEpt);
console.log(targetEpt);

// proxy 用法3 不设置handle
let targetEmpty = {};
let proxyEmpty = new Proxy(targetEmpty, {});
proxyEmpty.name = "Tom"
console.log(targetEmpty); // {name: "Tom"}
实例方法

get()

1
get(target, propKey, receiver)

用于 target 对象上 propKey 的读取操作

  • target:目标对象
  • propKey:目标对象属性
  • receive receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let exam ={
name: "Tom",
age: 24
}
let proxy = new Proxy(exam, {
get(target, propKey, receiver) {
console.log('Getting ' + propKey);
console.log(receiver);
return target[propKey];
}
})
console.log(proxy.name);
// Getting name
// Proxy {name: "Tom", age: 24}
// "Tom"

get() 方法可以被继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = {
// 声明一个私有属性
_name: '张三',
age:19
}
let proxy = new Proxy({obj}, {
get(target, key, receiver) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to get private "${key}"`);
}
console.log('Getting' + key);
return target[key];
}
})
// proxy._name; // Invalid attempt to get private "${key}"
let obj2 = Object.create(proxy);
console.log(obj2.age);

set()

1
set(target, propKey, value, receiver)

用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。

  • target:目标对象
  • propKey:目标对象属性
  • value:将要修改的值
  • receive receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let proxy= new Proxy({}, validator)
proxy.age = 100;
proxy.age // 100
proxy.age = 'oppps' // 报错
proxy.age = 300 // 报错

第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身

1
2
3
4
5
6
7
8
9
10
11
12
13
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.name= 'Tom';
proxy.name=== proxy // true

const exam = {}
Object.setPrototypeOf(exam, proxy)
exam.name = "Tom"
exam.name === exam // true

注意:严格模式下,set代理如果没有返回true,就会报错

apply(target, ctx, args)

用于拦截函数的调用、call 和 reply 操作。

  • target:目标对象
  • ctx:表示目标对象的上下文
  • args:表示目标对象的参数数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sub(a, b) {

}

let handle = {
apply: function (target, ctx, args) {
console.log(target); // 函数本身
console.log(ctx); // undefined
console.log(args); // [2,1]

}
}
let proxy = new Proxy(obj, handle);
proxy(2, 1);

has(target, propKey)

用户拦截hasProperty操作,即在判断target对象是否村子propKey属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承属性。

  • target:目标对象
  • propKey:属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let  handler = {
has: function(target, propKey){
console.log("handle has");
return propKey in target;
}
}
let exam = {name: "Tom"}
let proxy = new Proxy(exam, handler)
console.log('name' in proxy);
// handle has
// true
// 此方法不拦截 for in
for (let key in proxy) { //name
console.log(key);
}

注意:此方法不拦截 for … in 循环。

deleteProperty(target, propKey)

  • target:目标对象
  • propKey:属性

用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。

defineProperty(target, propKey, propDesc)

  • target:目标对象
  • propKey:属性
  • proDesc

用于拦截 Object.definePro若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。

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
let handler = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
// 返回true 则可以在该对象上新增属性
return true;
}
}
let target = {}
let proxy = new Proxy(target, handler);
// proxy.name = "Tom" // handle defineProperty

console.log(target);
// {name: "Tom"}

// defineProperty 返回值为false,添加属性操作无效
let handler1 = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
// 返回false 则不可在该对象上新增属性
return false;
}
}
let target1 = {}
let proxy1 = new Proxy(target1, handler1)
proxy1.name = "Jerry"
console.log(target1); // {}

getPrototypeOf(target)

主要用于拦截获取对象原型的操作。包括以下操作:

1
2
3
4
5
- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
1
2
3
4
5
6
7
let exam = {}
let proxy = new Proxy({},{
getPrototypeOf: function(target){
return exam;
}
})
Object.getPrototypeOf(proxy) // {}

注意,返回值必须是对象或者 null ,否则报错。另外,如果目标对象不可扩展(non-extensible),getPrototypeOf 方法必须返回目标对象的原型对象。

getOwnPropertyDescriptor(target, propKey)

用于拦截 Object.getOwnPropertyD()返回值为属性描述对象或者 undefined 。

  • target:目标对象
  • propKey:属性
1
2
3
4
5
6
7
8
9
10
11
12
//  getOwnPropertyDescriptor() 用于拦截getOwnPropertyDescriptor()方法
// getOwnPropertyDescriptor返回目标对象目标属性的详细信息
let handler = {
getOwnPropertyDescriptor: function (target, propKey) {
return Object.getOwnPropertyDescriptor(target, propKey);
}
}
let target = {name: "Tom"}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'));
// {value: "Tom", writable: true, enumerable: true, configurable:
// true}

Reflect

ES6中将 Object 的一些明显属于语言内部的方法移植到了Reflect对象上(当某些方法同时存在于Object和Reflect对象上),未来的新方法只会部署在Reflect对象上

Reflect 对象对某些方法的返回结果进行了修改,使其更加合理

Reflect 对象使用函数的方式实现了 Object 的命令操作

简单理解:以前我们直接对对象进行操作的方法,改为通过Reflect对对象间接操作,且操作更加规范合理

静态方法

Reflect.get(target, name, receiver)

  • target:目标对象
  • name:属性
  • receiver:可选参数,传递一个对象,将会从目标对象方法移植到此对象上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"

// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
name: "Jerry",
age: 20
}
// 将目标对象的get info 绑定到 receiver对象上
console.log(Reflect.get(exam, 'info',receiver)); // Jerry20

// 当 name 为不存在于 target 对象的属性时,返回 undefined
console.log(Reflect.get(exam, 'birth'));; // undefined

// 当 target 不是对象时,会报错
// Reflect.get(1, 'name'); // TypeError

Reflect.set(target, name, value, receiver)

  • target:目标对象
  • name:属性
  • value:属性值
  • receiver:可选参数,传递一个对象,将会从目标对象方法移植到此对象上

将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。

Reflect.has(obj, name)

是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

1
2
3
4
5
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name'); // true

Reflect.deleteProperty(obj, property)

delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

1
2
3
4
5
6
7
8
9
10
11
12
    // deleteProperty(obj, property)
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
console.log(exam); // {age: 24}
// property 不存在时,也会返回 true
delete exam["name"]; // ES6之前的方法
// Reflect.deleteProperty(exam , 'name'); // true
console.log(exam);
</script>

Reflect.construct(obj, args)

等同于 new target(…args)。

1
2
3
4
function exam(name){
this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}

Reflect.getPrototypeOf(obj)

用于读取 obj 的_proto_ 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。

1
2
3
class Exam{}
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype // true

Reflect.setPrototypeOf(obj, newProto)

用于设置目标对象的 prototype。

1
2
let obj ={}
Reflect.setPrototypeOf(obj, Array.prototype); // true

Reflect.apply(func, thisArg, args)

等同于 Function.prototype.apply.call(func, thisArg, args)func表示目标函数;thisArg表示目标函数绑定的 this 对象;args表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError

1
2
3
4
// ES6 以前的写法
console.log(Math.max.apply(Math, [1, 2, 3, 4, 5]));
// Reflect 写法
console.log(Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1])); // 5

Reflect.defineProperty(target, propertyKey, attributes)

用于为目标对象定义属性。如果 target 不是对象,会抛出错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
let myDate= {}
// ES5
Object.defineProperty(myDate,'now',{
value: +Date.now();
})
//ES6
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
}); // true

const student = {};
console.log(Reflect.defineProperty(student, "name", {value: "Mike"})); // true
console.log(console.log(student.name));; // "Mike"

Reflect.getOwnPropertyDescriptor(target, propertyKey)

用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。

1
2
3
4
5
6
7
8
9
10
11
12
var exam = {}
Reflect.defineProperty(exam, 'name', {
value: true,
enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}


// propertyKey 属性在 target 对象中不存在时,返回 undefined
Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined

Reflect.isExtensible(target)

用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。

1
2
et exam = {}
Reflect.isExtensible(exam) // true

Reflect.preventExtensions(target)

用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。

1
2
let exam = {}
Reflect.preventExtensions(exam) // true

Reflect.ownKeys(target)

用于返回 target 对象的所有属性,等同于Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。

1
2
3
4
5
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]

组合使用

Reflect对象的方法与 Reflect对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let exam = {
name: "tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}

let proxy = new Proxy(exam, handler);
proxy.name = "张三"; // setting name to 张三
console.log(proxy.name); // getting name 张三

使用场景拓展

实现观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义 Set 集合 定义观察者队列
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
observe(function () {
console.log('观察到目标对象属性变化');
})
// observable 返回原始对象的代理,拦截赋值操作
const observable = function (obj) {
return new Proxy(obj, {set});
}

function set(target, key, value, receiver) {
console.log(`拦截到${target}${key}属性的赋值操作,赋值为${value}`);
// 获取对象的赋值操作
const result = Reflect.set(target, key, value, receiver);
// 一旦当前对象进行赋值操作就通知是所有的观察者 进行处理
queuedObservers.forEach(observer => observer());
// 执行赋值操作 并返回赋值结果
return result;
}

let observable1 = observable({name: "张三"});
console.log(observable1.name = "李四");

模块化

概述

在ES6前,实现模块化使用的是RequireJS或者seaJS(分别是基于AMD规范的模块化库,和基于CMD的模块化库)。

ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量

ES6 的模块化分为导出(export)@ 与导入(import)两个模块。

特点

ES6 的模块自动开启严格模式,不管你有没有在头部加上use strict;

模块化中可以导入和导出各种类型的变量,如函数、对象、字符串、数字、布尔值、类等

每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域

每一个模块之加载一次(是单例的),若再去加载同目录下的文件,直接从内存中读取

exprot 与 import

基本用法

模块导入导出各种类型的变量,如字符串、数值、函数、类。

  • 导出的函数声明类必须要有名称(export default 命令另外考虑)
  • 不仅能导出声明还能导出引用(列如函数)
  • export 命令可以出现在模块的任何位置,但必须处于模块顶层。
  • import 命令会提升到整个模块的头部,首先执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }

/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!

建议使用大括号指定所有要输出的一组变量写在文档尾部,明确导出的接口

函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。

export 命令导出的接口名称,须和模块内部的变量有一一对应关系。导入的变量名,须和导出的接口名称相同,即顺序可以不一致。

1
2
3
4
5
6
7
8
9
10
// module01.js
let myName = "module01";
let age = 19;
export {myName as exportName,age};

// module02.js
import {exportName,age as myAge} from "./module01.js";
// as 不仅可以在 exports 导出时使用还可以在 import 使用
console.log(exportName); // module01
console.log(myAge); // 19

不同模块导出接口命名重复,使用as重新定义变量名

import 命令特点

只读属性:不允许加载模块的脚本里面,改写接口的指向,既可以改写 import 变量类型为对象的属性值,不能改写 import 变量为基本类型的值

1
2
3
4
5
6
7
8
9
10
11
12
// modeule01.js
let a = 123;
let b = {name: "张三"}
export {a,b};

// module02.js
import {a,b} from "./module01.js";

// a = {}; // error
b.age = 19;

console.log(b); //{name: "张三", age: 19}

单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import

1
2
3
4
5
6
7
import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";

import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";

静态执行特性:import时静态执行,所以不能使用表达式和变量

1
2
3
4
5
6
7
8
9
10
11
import { "f" + "oo" } from "methods";
// error
let module = "methods";
import { foo } from module;
// error
if (true) {
import { foo } from "method1";
} else {
import { foo } from "method2";
}
// error

export default 命令

  • 在一个文件或模块中,export、import可以有多个,export default仅有一个
  • export default中的default是对应的导出接口变量
  • 通过export方式导出,在导出时要加{},export default则不需要
  • export default向外暴露的成员,可以使用任意变量来接收
1
2
3
4
5
6
7
8
const moduleName = "modeule01";
export default moduleName;
// 不可行 导出语句不能跟赋值语句
// export default const moduleName = "module01"

import moduleName from "./module01.js";

console.log(moduleName);

复合使用

exportimport可以在同一模块使用,使用特点:

  • 可以导出接口改名,包括 default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export { foo, bar } from "methods";

// 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar
import { foo, bar } from "methods";
export { foo, bar };

/* ------- 特点 1 --------*/
// 普通改名
export { foo as bar } from "methods";
// 将 foo 转导成 default
export { foo as default } from "methods";
// 将 default 转导成 foo
export { default as foo } from "methods";

/* ------- 特点 2 --------*/
export * from "methods";

说明

1.在import引入的html文件的script里必须标明 type=”module”

2.通过 importexport 实现导入导出功能(必须走 http 协议访问

as 的用法

Promise 对象

概述

是异步编程的一种解决方案

从语法上说,Promise是一个对象,从它可以获取异步操作的信息

Promise 状态

Promise 异步操作有三种状态:pending(进行中)fulfilled(成功)、和rejected(已失败)除了异步操作的结果,任何其他操作都无法改变这个状态。

Promise对象只有:从pening变为fulfiled和从pending变为rejected的状态改变。只要处于fulfilled和rejected,状态就不再变了即resolved(已定型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Promise 通过构造函数创建 参数1:成功回调 参数2:失败回调
// promise 对象共有三种状态:1.pending fulfilled rejected
// 注意:resolve 对应 .then() 方法回调
// reject 对应 .catch() 方法回调
// 且resolve与reject两者在一个函数中不能同时存在,
// 简单理解:就是事物的状态要么成功要么失败,不可能又成功又失败的状态

let flag = false;
const p1 = new Promise(function (resolve, reject) {
if (flag) {
resolve('success');
} else {
reject('error');
}

})
// 通过.then() 方法接收回调函数
p1.then(res => {
console.log(res);
}).catch(e => {
console.log(e);
})

缺点:

1.无法取消Promise,一旦新建它就会立即执行,无法中途取消

2.如果不设置回调函数,Promise内部抛出错误,不会反应到外部

3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

then方法

then 方法接收两个函数作为参数,第一参数时Promise执行成功时的回调函数,第二个参数时Promise执行的失败回调,两个函数中只有一个会调用(即事物处理的结果只有成功和失败)

特点

1.在 JavaScript事件队列的当前运行完成时,回调函数永远不会被调用

1
2
3
4
5
6
7
8
9
10
// then 方法 事件队列当运行完成之前,回调函数永远不会被调用
const p = new Promise(function (resolve, reject) {
resolve('success');
});

p.then(function (value) {
console.log(value);
});
// 这和js 异步任务队列的调用机制一致
console.log('first'); // first 再是 success

2.通过 .then 形式添加的回调函数,不论什么时候,都会被调用。,可以添加多个回调函数,它们会按照插入顺序并且独立运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 可以通过.then()链式调用的方式多次调用,
// 后一个then()拿到的值是前一个then()处理过的返回的值
// 注意:then() 必须显示 return 不然后面的then() 方法无法获取到值
const p = new Promise(function (resolve, reject) {
resolve(1);
}).then(function (value) { // 第一个then // 1
console.log(value);
return value * 2;
}).then(function (value) { // 第二个then // 2
console.log(value);
}).then(function (value) { // 第三个then // undefined
console.log(value);
return Promise.resolve('resolve');
}).then(function (value) { // 第四个then // resolve
console.log(value);
return Promise.reject('reject');
}).then(function (value) { // 第五个then //reject:reject
console.log('resolve:' + value);
}, function (err) {
console.log('reject:' + err);
});

then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。

注意点:

使用Promise链式编程时最好保持扁平化,不要嵌套Promise,注意总是返回或终止 Promise 链。

1
2
3
4
5
const p1 = new Promise(function(resolve,reject){
resolve(1);
}).then(function(result) {
p2(result).then(newResult => p3(newResult));
}).then(() => p4());

创建新 Promise 但忘记返回它时,对应链条被打破,导致 p4 会与 p2 和 p3 同时进行。

大多数浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error));

Promise.all和Promise.race

Promise.all

方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
var p = Promise.all([p1,p2,p3]);

上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)

p 的状态由 p1、p2、p3 决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race

方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

1
var p = Promise.race([p1,p2,p3]);
1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function(id){
return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function(posts) {
// ...
}).catch(function(reason){
// ...
});

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。

如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。

Promise Ajax

Promise在实际开发中常用于,包装网络请求,其常用格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ajax(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var URL = "/try/ajax/testpromise.php";
ajax(URL).then(function onFulfilled(value){
document.write('内容是:' + value);
}).catch(function onRejected(error){
document.write('错误:' + error);
});

Generator 函数

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。 基本用法

Generator 函数组成

有两个区分于普通函数的部分

  • function后面,函数名之间前有个*;
  • 函数内部有 yield表达式;

其中*用来表示函数为 Generator 函数,yield 用来定义函数内部的状态

1
2
3
4
5
6
7
8
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}

执行机制

Generator函数后,函数并不执行,而是返回一个执行内部状态的一个指针对象,即返回一个iterator遍历器对,

每次调用next()方法,内部执行就从函数头部或上一次停下的地方开始执行,直到遇到下一个yield语句为止

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
// Generator 函数组成
function* func() {
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}

let f = func();

f.next();
// one
// {value: "1", done: false}

f.next();
// two
// {value: "2", done: false}

f.next();
// three
// {value: "3", done: true}

f.next();
// {value: undefined, done: true}

函数返回的遍历器对象的方法

next()方法

一般情况下,next 方法不传入参数的适合,yeid表达式的返回值时undefined。当 next 传入参数的时候,该参数会作为上一步yield的返回值。

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
// next()方法
function* sendParameter(){
console.log("start");
var x = yield '2';
console.log("one:" + x);
var y = yield '3';
console.log("two:" + y);
console.log("total:" + (x + y));
}

var sendp1 = sendParameter();
// next() 不传参
// sendp1.next();
// // start
// // {value: "2", done: false}
// sendp1.next();
// // one:undefined
// // {value: "3", done: false}
// sendp1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}
// next传参
var sendp2 = sendParameter();
sendp2.next(10);
// start
// {value: "2", done: false}
sendp2.next(20);
// one:20
// {value: "3", done: false}
sendp2.next(30);
// two:30
// total:50
// {value: undefined, done: true}

除了使用 next ,还可以使用 for… of 循环遍历 Generator 函数生产的 Iterator 对象。

return 方法

return 方法返回给定值,并结束遍历 Generator 函数。

return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。

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
function* foo() {
yield 1;
yield 2;
yield 3;
}

var f = foo();
console.log(f.next());
// {value: 1, done: false}
console.log(f.return("foo"));
// 此时已经结束 后面再调用 next() 无法遍历
// {value: "foo", done: true}
console.log(f.next());
// {value: undefined, done: true}

// throw 方法
// throw 方法可以在 Generator 函数体外面抛出异常,再函数体内部捕获。
var g = function* () {
try {
yield 1 ;
} catch (e) {
console.log('catch inner', e);
}
};
var i = g();
console.log(i.next());
try {
console.log(i.throw('a'));
console.log(i.throw('b'));
} catch (e) {
console.log('catch outside', e);
}
// catch inner a
// catch outside b

遍历器对象抛出了两个错误,第一个被 Generator 函数内部捕获,第二个因为函数体内部的catch 函数已经执行过了,不会再捕获这个错误,所以这个错误就抛出 Generator 函数体,被函数体外的 catch 捕获。

yield * 表达式

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
function* callee() {
console.log('callee: ' + (yield));
}
function* caller() {
while (true) {
yield* callee();
}
}
const callerObj = caller();
callerObj.next();
// {value: undefined, done: false}
callerObj.next("a");
// callee: a
// {value: undefined, done: false}
callerObj.next("b");
// callee: b
// {value: undefined, done: false}

// 等同于
function* caller() {
while (true) {
for (var value of callee) {
yield value;
}
}
}

使用场景

实现 Iterator

为不具备iterator 接口的对象提供遍历方法

1
2
3
4
5
6
7
8
9
10
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj);
for (const propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
const obj = {first:'jane', last: 'Doe'}
for (const [key,value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}

async / await

异步回调

async

是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

语法:

1
async function name([param[, param[, ... param]]]) { statements }
  • name:函数名
  • param:要传递给函数的参数名称
  • statements:函数体语句

返回值

async 函数返回一个Promise 对象,可以使用 then 方法添加回调函数

1
2
3
4
5
6
7
8
async function helloAsync() {
return "helloAsync";
}

console.log(helloAsync()); // Promise
helloAsync().then(v => {
console.log(v); // helloAsync
})

await表达式

async函数中可能会有 await表达式,async 函数执行时,如果遇到 await就会先暂停执行 ,等到触发的异步操作完成后,恢复 async函数的执行并返回解析值。

await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function testAwait() {
return new Promise((resolve) => {
setTimeout(function () {
console.log("testAwait");
resolve(111);
}, 1000);
});
}

async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync

await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。

语法

1
return_value] = await expression;
  • expression: 一个Promise对象或者任何要等待的值

返回值

返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function testAwait (x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}

async function helloAsync() {
var x = await testAwait ("hello world");
console.log(x);
}
helloAsync ();
console.log(11);
// 11
// hello world

正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function testAwait() {
setTimeout(function () {
console.log("testAwait");
} , 2000)
}

async function helloAsync() {
await testAwait();
console.log("helloAsync");
}

helloAsync();
// testAwait
// helloAsync

await 针对所跟不同表达式的处理方式

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
  • 非 Promise 对象:直接返回对应的值。
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
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<script>
let getJson = (url, name) => {
const p = new Promise(function (resovle, reject) {
const request = new XMLHttpRequest();
const handler = function () {
if (this.readyState !== 4) return;
if (this.status === 200) {
resovle(name + this.responseText)
} else {

reject("请求失败");
}
}
request.onreadystatechange = handler;
request.open('get', url);
request.send(null);

});
return p;
}

// 解决纵向衍生太深,使得异步回调变成同步

async function test() {
// 内部实现实现同步
let data1 = await getJson('http://localhost:8080/', "p1");
console.log(111, data1);
let data2 = await getJson('http://localhost:8080/', "p2");
console.log(222, data2);
console.log(1111);
}
test()
console.log(222);
// const p1 = getJson('http://localhost:8080/', "p1")
// const p2 = getJson('http://localhost:8080/', "p2")



// p1.then(res => {
// console.log(res);
// return p2;
// }, err => {

// }).then(res => {
// console.log(res);
// })
// // 解决异步回调地狱
</script>
</body>

</html>
谢谢老板