0%

ES6新增标准

三大组成部分:ECMAScript;DOM(文档对象模型):对文档的操作;BOM(浏览器对象模型):主要包括对浏览器的相关操作;

let和const

let、const和var的区别

var的特性

1、var可以重复声明;

2、var作用域:全局作用域和函数作用域;

3、会进行预解析,但可能造成代码混乱;

let的特性

1、let在同一作用域下不能进行重复声明;

2、let作用域:全局作用域和块级作用域;if、while、for等有{}的语句,let会考虑在花括号之间的为块级作用域,仅在代码块内部起作用;(与C++类似)

3、不进行预解析;JS有预解析机制,在下面声明而在上面去调用并不会报错,然而使用let声明时不会进行预解析;

const的特性

1、const为常量,声明时必须赋值,而之后并不能对其进行修改;

2、其他情况与let一样,不能重复声明、不能预解析、为块级作用域。

块级作用域

之前在写代码时,为了避免与全局冲突,一般会先声明一个匿名函数,并马上对其调用;

1
2
3
(function(){

})()

有了块级作用域之后,可以直接用{}来生成代码块,便能直接在代码块里添加代码;

如果在循环之中添加了一个块级作用域,将要如何去处理,

1
2
3
4
5
6
7
8
9
{
let lis = document.querySelectorAll("li");
for (let i = 0; i < lis.length; i++){
lis[i].onclick = function(){
console.log(i);
}
}
}
//这么写相当于生成了lis.length个代码块,遍历i时每个i的值均声成代码块,循环几次便开辟几个块级作用域;

解构赋值

对象的解构赋值

对象可以有若干属性,且每个属性方法衷都可以存储数据;当你希望用其他的变量将原变量的属性存储起来,原本的JS写法如下:

1
2
3
4
5
6
let obj = {
a: 1,
b: 2
};
let a = obj.a;
let b = obj.b;

在ES6的标准下,可以把声明改成这样:

1
2
3
4
5
6
let obj = {
a: 1,
b: 2
};
let {a, b, c} = obj;
//语法中对象的名字必须与obj里面的属性名称一样,这样才能进行对象解构赋值,故c为undefined

数组的解构赋值

对象的解构赋值要求变量名与属性名一致,但数组的话,仅要求顺序必须一致;

1
2
3
4
5
6
7
8
let arr = ["a", "b", "c"];
let [e, f] = arr;

//面试题:如何快速交换a, b值
let a = 0;
let b = 1;
[a, b] = [b, a];
//使用数组解构赋值便能快速交换;

字符串的解构赋值

1
2
3
let str = "abc";
let [e, f] = str
//其实跟数组一样,根据顺序的索引来解构赋值

展开运算符

数组展开

1
2
3
4
5
let arr = [1, 2, 3, 4];
let arr2 = ["a", "b",...arr,"c", "d"];
//如何简单处理,将两个数组加一起
//剩余参数
let [a, b,...c] = arr;

对象展开

1
2
3
4
5
6
7
8
9
10
11
let obj = {
a:1,
b:2
};
let obj2 = {
...obj,
c:3,
d:4
};
//不建议对象直接赋给另一个对象,不然一个改变另一个也会改变,因为其本身传递的是一个地址,那么如何来处理呢,用obj解构来代替obj,其本质是所有内容而不是对象的地址
let obj2 = {...obj};

set对象

set本身是一个函数,用于构建对象,还有Data、Array,调用后返回构造对象,统称构造函数,用于构造某一类型的对象,即对象的实例化。

1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3, 4, 5]
let s = new Set(arr);
arr = [...s]
//arr返回之后便是去重之后的结果
s.size//size属性,储存保留之后值得个数
s.clear();//清空
s.delete("a");//删除某项值
s.add(6);//添加某项值
s.has();//查看是否包含某个值

Map对象

1
2
3
4
5
6
7
8
9
10
11
12
13
let arr = [
["a",1],
["b",2],
["c",3]
];
let s = new Map(arr);
//map结构会存成键值对得形式key-value
clear();//清空所有值
delete();//删除某一项
//参数:key,数据的key值;返回值,true、false是否删除成功
get();//获取某一项值
has();//是否包含某一项
set(key, val);//设置一个值

函数新增内容

箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//function(){return ;}
let fn = ()=>{
console.log(1);
};
fn();
//箭头函数,=>
//形参=>返回值
let fn = nub => nub *2;
//多个形参时,则加括号(形参,形参)=>返回值
//()=>返回值,没有参数时也要加上括号
// ()=>{
// 执行语句
// return 返回值
// }
//箭头函数没有arguments,即不能使用不定参,但可以使用扩展运算符加剩余参数来进行实现不定参数的功能,
let fn = (a,b...arg)=>
//rest参数存在数组中,即拿到该数组便能拿到剩余参数,其实跟前面的展开运算符一致

1
2
3
4
5
6
document,onclick = function(){
let fn = ()=>{
console.log(this);
};
fn();
}

使用正常函数时,this指针一般指向windows,但换成箭头函数时,this指针指向document;因为箭头函数本身没有this,调用箭头函数的this时,指向的是其声明时所在的作用域的this;

参数默认值问题:ES6中有简便方法设置参数默认值

1
2
3
let fn = (a = 2,b = 10)=>{

}

数组新增方法

日期对象

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
new Date().getTime();//方法属于Date返回的对象的方法,需要对对象进行调用
Date.now();//直接使用Date的方法;
new Array()
Array.from();
arr.forEach();//需要区分一下哪些方法是构造函数的本身方法,而哪些方法是需要利用对象来调用的方法

//新增方法如下
Array.from()//把一个类数组转换成真正的数组,类数组:有下标有length;返回值:转换之后的新数组;是Array构造函数的方法
{
let lis = document.querySelectorAll("#list li");
//map是数组方法,因此需要先将lis从类数组变成数组
let arr=[];
lis = Array.from(lis);
lis.map(item=>{
return item;
});
lis = Array.from(lis,(item,index)=>{
console.log(item, index);
return index;
});
}
//利用from组成新数组
//of方法将放入的元素组成新数组后返回
{
Array.of(1,2,3,4,"5");
}
//isArray方法:判断接收的数据是否是数组,类数组不是数组

数组的find、findIndex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
//find查找数组中满足要求的第一个元素的值
let arr = ["a", "b", "c", "d"];
arr.indexOf("a");
let arr = [1,2,3,4];
let val = arr.find((item,index)=>{
if(item > 3){
return true;
}
});
val = arr.find(item=> item >= 3);
console.log(val);
//fingIndex返回数组中满足要求的第一个元素的索引
}

数组扁平化处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = {
["小明""18"],
["小刚""18"],
}
arr.flat(n);
//flat(n)将多维数组转换成低维的数组,向下提取n层;后续传递的参数决定了提取几层
//当嵌套多层时,使用
flat(Infinity);//提取无限层,确保变为一维数组
//利用flatMap来扁平化处理后得到每一项
let newArr = arr.flatMap((item,index)=>{
cosole.log(item,index);
item = item.filter((item,index)=>{
return index == 0;
});
return item;
});
console.log(newArr);

flatMap参数:callback回调函数,可以生成一个新数组中元素的参数;可选参数:thisArg,执行callback函数时,使用的this值;返回值:一个包含将数组和子数组中所有元素的数组。

flatMap:只能处理一层的数组,要处理多层的话需要进行if判断是否里面还存在数组,并进行递归操作进行多次扁平化。

1
2
3
4
5
//fill语句,将数组用...数据填充满,从第几位开始填充,且本身是不修改原数组长度的
//includes判断数组中是否包含一个指定的值
let arr = ["a","b","c","d","e"];
arr.includes("c",2);
//后面参数代表从第几位开始检索

字符串新增方法

1
2
3
4
5
6
7
8
9
10
11
12
13
let str = "开课吧和面为课堂"
str.startsWith("开课吧");//返回一个bool值
str.endsWith("面为课堂");
str.repeat(30);
//模板字符串,如何快速地构造出想要的字符串
//${}插值表达式,可以用值,也可以用函数,适合该值有复杂的逻辑处理
let p = document.querySelector("p");
let name = "小明";
let age = 18;
let school = "大学";
p.innerHTML = `今年<strong>${name}</strong>就要<strong>${age}</strong>岁了,终于升入<strong>${school}</strong>了`
//先用``反引号将整个值包起来,在用插值表达式来一个个插入值
//模板字符串可以换行

对象新增方法

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
let a = 0;
let b = 1;
let name = "小明";
let obj = {
a,
b,
c(){
console.log("a");
},
[name]: 111;
};
//可以直接在obj里面起属性名表达式,可通过变量来给属性名赋值。
//对象合并,其实可以使用之前的对象展开来进行合并
let obj = {
a: 1,
b :2
};
let obj2 = {
c : 3,
d :4
};
Object.assign(obj2 ,obj);//第一个参数为合并传入的对象

//is方法 obj函数下的方法,接受两个值并判断是否一样

Babel

babel是一个JavaScript编译器,用于语法编译,把JS本身不识别、不兼容的语法糖编译成兼容的,babel最简单的使用:将babel引入到页面当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 0;
let b = 1;
let obj = {
a,
b,
c(){
console.log(1);
}
};
let obj2 = {
d: 4,
...obj,
e: 5
};

Promise

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点:

1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变;

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。缺点:无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

回调函数

一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

其实回调就是一种利用函数指针进行函数调用的过程.

为什么要用回调呢?比如我要写一个子模块给你用,来接收远程socket发来的命令.当我接收到命令后,需要调用你的主模块的函数, 来进行相应的处理.但是我不知道你要用哪个函数来处理这个命令,我也不知道你的主模块是什么.cpp或者.h,或者说,我根本不用关心你在主模块里怎么处理它,也不应该关心用什么函数处理它……怎么办呢?使用回调!

使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己写的一个函数(这个函数就是回调函数)的地址作为参数传递给那个函数。回调其实就是提供使用某模块的一种方法。回调函数就好比是一个中断处理函数。

在解释这种思想前我想先说明一下,回调函数固然能解决一部分系统架构问题但是绝不能再系统内到处都是,如果你发现你的系统内到处都是回调函数,那么你一定要重构你的系统。回调函数本身是一种破坏系统结构的设计思路,回调函数会绝对的变化系统的运行轨迹,执行顺序,调用顺序。回调函数的出现会让读到你的代码的人非常的懵头转向。

既然我们需要模块间的协作,同时我们又厌恶的摒弃模块间你中有我我中有你的暧昧关系那如何生成系统呢,答案是函数指针(不一定一定是函数指针)也就是使用回调的方式。如果一个对象关心另一个对象的状态变化那么给状态的变化注册回调函数让它通知你这类状态的改变,这样在封装了模块变化的同时实现了模块间的协作关系另辟独径的给对象解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var async=function(callback){
setTimeout(function(){ //1秒后回调
callback('data');
},1000);
};
async(function(data){
alert(data);
});


var friends = ["Mike", "Stacy", "Andy", "Rick"];
friends.forEach(function (eachName, index){
console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});

需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(就像它的名字一样)。因此,即使第一个jQuery的例子如下所示:

1
2
3
4
5
//匿名函数不会再参数中被执行
//这是一个回调函数
$("#btn_1").click(function(){
alert("Btn 1 Clicked");
});

这个回调函数在包含它的函数内的某一点执行,就好像这个回调函数是在包含它的函数中定义的一样。这意味着回调函数本质上是一个闭包。正如我们所知,闭包能够进入包含它的函数的作用域,因此回调函数能获取包含它的函数中的变量,以及全局作用域中的变量。

回调函数基本原理

命名或匿名函数作为回调

在前面的jQuery例子以及forEach的例子中,我们使用了再参数位置定义的匿名函数作为回调函数。这是在回调函数使用中的一种普遍的魔术。另一种常见的模式是定义一个命名函数并将函数名作为变量传递给函数。

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
//全局变量
var allUserData = [];

//普通的logStuff函数,将内容打印到控制台
function logStuff (userData){
if ( typeof userData === "string")
{
console.log(userData);
}
else if ( typeof userData === "object"){
for(var item in userData){
console.log(item + ": " + userData[item]);
}
}
}

//一个接收两个参数的函数,后面一个是回调函数
function getInput (options, callback){
allUserData.push(options);
callback(options);
}

//当我们调用getInput函数时,我们将logStuff作为一个参数传递给它
//因此logStuff将会在getInput函数内被回调(或者执行)
getInput({name:"Rich",speciality:"Javascript"}, logStuff);
//name:Rich
//speciality:Javascript

传递参数给回调函数

既然回调函数在执行时仅仅是一个普通函数,我们就能给它传递参数。我们能够传递任何包含它的函数的属性(或者全局书讯给)作为回调函数的参数。在前面的例子中,我们将options作为一个参数传递给了毁掉函数。现在我们传递一个全局变量和一个本地变量:

在执行之前确保回调函数是一个函数

在调用之前检查作为参数被传递的回调函数确实是一个函数,这样的做法是明智的。同时,这也是一个实现条件回调函数的最佳时间。我们来重构上面例子中的getInput函数来确保检查是恰当的。

1
2
3
4
5
6
7
8
9
function getInput(options, callback){
allUserData.push(options);

//确保callback是一个函数
if(typeof callback === "function"){
//调用它,既然我们已经确定了它是可调用的
callback(options);
}
}

使用this对象的方法作为回调函数时的问题

当回调函数是一个this对象的方法时,我们必须改变执行回调函数的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象(在浏览器中)。要么指向包含方法的对象。

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
var clientData = {
id: 094545,
fullName : "Not Set",
//setUsrName是一个在clientData对象中的方法
setUserName: fucntion (firstName, lastName){
//这指向了对象中的fullName属性
this.fullName = firstName + " " + lastName;
}
}

function getUserInput(firstName, lastName, callback){
//在这做些什么来确认firstName/lastName

//现在存储names
callback(firstName, lastName);
}
//在下面你的代码例子中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性。相反,它将设置window对象中的fullName属性,因为getUserInput是一个全局函数。这是因为全局函数中的this对象指向window对象

//全局函数中的thos都是指向window对象的
getUserInput("Barack","Obama",clientData.setUserName);

console.log(clientData,fullName); //Not Set

//fullName属性将在window对象中被初始化
console.log(window.fullName); //Barack Obama

使用Call和Apply来保存this

使用回调函数时一定要注意由于函数位置的改变,导致的this指针指向位置不同

我们知道了每个Javascript中的函数都有两个方法:Call 和 Apply。这些方法被用来设置函数内部的this对象以及给此函数传递变量。

call接收的第一个参数为被用来在函数内部当做this的对象,传递给函数的参数被挨个传递(当然使用逗号分开)。Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组。

1
2
3
4
5
6
7
8
9
10
11
12
//注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback. callbackObj){
//在这里做些什么来确认名字

callback.apply(callbackObj, [firstName, lastName]);
}
//我们将clientData.setUserName方法和clientData对象作为参数,clientData对象会被Apply方法使用来设置this对象
getUserName("Barack", "Obama", clientData.setUserName, clientData);

//clientData中的fullName属性被正确的设置
console.log(clientUser.fullName); //Barack Obama

多重回调函数

我们可以将不止一个的回调函数作为参数传递给一个函数,就像我们能够传递不止一个变量一样。

在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积

  1. 给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。
  2. 模块化L将你的代码分隔到模块中,这样你就可以到处一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块

Promise基本用法

Promise对象是一个构造函数,用来生成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
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

//resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
//Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

//Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});

//then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});

promise.then(function() {
console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved
//Promise 新建后就会立即执行,首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

下面我们写异步加载图片和实现Ajax操作的例子。

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();

image.onload = function() {
resolve(image);
};

image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};

image.src = url;
});
}
//上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();

});

return promise;
};

getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});

//上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
//如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,

const p1 = new Promise(function (resolve, reject) {
// ...
});

const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
//一个异步操作p2的结果是返回另一个异步操作p1,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})

p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
//上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

//调用resolve或reject并不会终结 Promise 的参数函数的执行。
//一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})

Promise的各类方法使用

then方法

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

1
2
3
4
5
6
7
8
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
//上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数

catch方法

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

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
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
//上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});

// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
//这两种写法是等价的,reject()方法的作用等同于抛出错误;
//如果promise状态已经变成resolved,再抛出错误是无效的Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了
//Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获

//一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});

// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
//因为第二种写法可以捕获前面then方法中执行的错误,也·更接近同步的写法(try、catch),因此建议总是使用catch()方法

跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

不过,Node.js 有一个unhandledRejection事件,专门监听未捕获的reject错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。

一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。

finally方法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

finally本质上是then方法的特例。如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

finally方法的实现:

1
2
3
4
5
6
7
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

all方法

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

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

上面代码中,Promise.all()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
const databasePromise = connectDatabase();

const booksPromise = databasePromise
.then(findAllBooks);

const userPromise = databasePromise
.then(getCurrentUser);

Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));

上面代码中,booksPromiseuserPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

race方法

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

1
const p = Promise.race([p1, p2, p3]);

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

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

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
function start() {  
return new Promise((resolve, reject) => {
resolve('start');
});
}

start()
.then(data => {
// promise start
console.log('result of start: ', data);
return Promise.resolve(1); // p1
})
.then(data => {
// promise p1
console.log('result of p1: ', data);
return Promise.reject(2); // p2
})
.then(data => {
// promise p2
console.log('result of p2: ', data);
return Promise.resolve(3); // p3
})
.catch(ex => {
// promise p3
console.log('ex: ', ex);
return Promise.resolve(4); // p4
})
.then(data => {
// promise p4
console.log('result of p4: ', data);
});

promise俗称链式调用,它是es6中最重要的特性之一
简单的说可以不停的then调用嵌套在调用(异步之后,链式调用方式执行回调),这种操作方式称为promise

⑴. resolved(全部置为完成状态)
①.初始化:比如说以国家,省份,县市(china ,jiangshu ,xian)三个方法来演示下链式调用关系(采用setTimeout模拟异步操作)

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
 function  china(){
console.log('china中国')
var p =new Promise(
function( resolve,reject ) {
setTimeout(function(){
console.log('中国 国家')
resolve('教育大省份')
},1000)
}
)
return p;
}

function jiangshu(data){
console.log('江苏'+data);
var p=new Promise(function(resolve,reject){
setTimeout(function (){
console.log('江苏 省份')
resolve('地级市');
},2000)
})
return p;
}

function xian(data){
console.log('盱眙县'+data)
var p=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('盱眙县');
resolve ('淮河镇')
},2000)
})
return p;
}
china ().then(jiangshu).then(xian).then(function(data){
console.log(data)
})
/*输出:china 中国
中国 国家
江苏教育大省份
江苏 省份
盱眙县地级市
盱眙县
淮河镇

\rejected(部分置为无效状态)**
①.初始化:同样的以上述的函数为例

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
function  china(){
console.log('china中国')
var p =new Promise(
function( resolve,reject ) {
setTimeout(function(){
console.log('中国 国家')
reject('教育大省份')
},1000)
}
)
return p;
}

function jiangshu(data){
console.log('江苏是'+data);
varp=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('江苏 省份')
resolve('地级市');
},2000)
})
returnp;
}
//函数写完之后,就开始结合then来链式调用了
china()
.then(jiangshu,function(data){ console.log(data)})

// 等同于(null不执行)
china()
.then(null,function(data){ console.log(data)})

//等同于(直接执行catch回调,抛出异常,页面也不会卡死,直接走catch)
china()
.then(jiangshu).catch(function(data){console.log(data)})
//(备注:为reject的时候,执行then的第二个参数回调,不会执行jiangshu)
//控制台输出:china 中国
//中国 国家
//教育大省份
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!