前序 ES6的新语法

2020-03-01  本文已影响0人  yangsg

学习vue的语法之前,首先要掌握一些ES6的新语法,以便更容易理解vue中的一些编程风格。

1. 变量与常量的声明

1.1 let关键字
1.1.1 let基本语法规则

ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部)
let和var声明的区别可以理解为类似全局变量与局部变量的区别。

var x = '全局变量';
{
  let x = '局部变量';
  console.log(x); // 局部变量
}
console.log(x); // 全局变量

使用let声明的变量绑定其所在的代码块,在代码块之外的地方无法访问和使用

{
  var x = 1;
  let y = 2;
}
console.log(x); // 正常显示1
console.log(y); //控制台报错:y is not defined
1.1.2 变量提升

如果使用var关键字声明变量,如下代码执行不会报错,但会得到undefined结果。

console.log(x);  //显示undefined
var x = 5;

此种情况被称为变量提升,可以理解为代码在执行时实际上是

var x;
console.log(x); 
x = 5;

这样做虽然方便但不符合一般编程语言的习惯,变量提升允许一个变量未声明即可使用。
另一个变量提升的示例

var tmp = new Date();
function f() {
  console.log(tmp);  //显示为undefined
  if (false) {
    var tmp = "hello world";  
  }
}
f(); 

在调用f()函数时,函数内部在下方声明了tmp,因此产生了变量提升的情况,先打印的tmp变为undefined
使用let声明变量时,不支持变量提升
上述代码如果更改为let声明,控制台会报错,提示变量x没有定义(x is not defined

1.1.3 暂时性死区

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
如果同时使用var和let进行变量声明,在代码块内部let变量的优先级要高于var变量。
这与一般编程语言局部变量优先级高于全局变量的理解是一致的。

var x = 1;
if(true){
  x = 3;  //此处x指的是使用let声明的局部变量。
  let x;
}

上面代码中,存在全局变量x,但是块级作用域内let又声明了一个局部变量x,导致后者绑定这个块级作用域,所以在let声明变量前,对x赋值会报错(x is not defined)。

1.2 const关键字

const表示声明常量,与let命令一致,也是一个块级作用域命令。const的功能与Java编程语言中的final语法类似。
const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了

const x = 1
x = 0 //报错

如果const的是一个对象,对象所包含的值是可以被修改的,不可改变的是对象的内存地址。

const x = { name: 'cc' }
x.name = '赵四';// 不报错
x = { name: '赵四' };// 报错

x.name = '赵四';不报错的的原因是只更改了对象的内容,对象的内存地址没有改变
x = { name: '赵四' };报错的原因是该句代码相当于重新在内存新建了一个对象赋值给x。
一般情况下,遵守编码规范应将常量名设置为全部大写


总结


2. 模板字符串

在ES6之前,通过拼接字符串的方式来构建文本输出模板
比如:

let msg = {
  name: '赵四',
  age: 32,
  job: '亚洲舞王'
};
var div = document.getElementById("divx");
div.innerHTML = "姓名: "+msg.name+"<br>年龄: "+msg.name+"<br>职业: "+msg.name+"<br>";

ES6使用反引号:``,配合EL语法${变量名}完成字符串拼接
上述示例使用ES6语法模板字符串完成如下

let msg = {
  name: '赵四',
  age: 32,
  job: '亚洲舞王'
};
var div = document.getElementById("divx");
div.innerHTML =`姓名: ${msg.name}<br>年龄: ${msg.name}<br>职业:${msg.name}<br>`;

总结


3. 函数声明与传参

3.1 参数默认值

在ES6之前,如果函数的参数在未参入参数时提供默认值,需要如下方式实现

function printText(text) {
    text = text || 'default';  //如果text为传入数据,则默认设置为default
    console.log(text);
}

ES6支持在函数声明时直接指定参数默认值,上述示例修改如下

function printText(text = 'default') {
    console.log(text);
}
3.2 Spread / Rest 操作符

Spread / Rest 操作符指的是...,具体是 Spread 还是 Rest 需要看代码的上下文。可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造对象时, 将对象表达式按key-value的方式展开。

3.2.1 Spread

...被用于函数实参时,它是一个 Spread 操作符。

let a = [1,2,3];
function f(x,y,z) {
    console.log(x,y,z);
}
f(...a);
3.2.2 Rest

...被用于函数形参时,它是一个 Rest 操作符

function f(...x) {
    console.log(x);
}
f(1,2,3,4,5);
3.3 箭头函数

ES6中允许使用箭头=>定义函数
常规方式定义函数

var f1 = function (){
  return 5;
}

var f2 = function(a, b){
  return a+b;
}

var f3 = function(a, b){
  console.log(a);
  console.log(b);
  return a+b;
}

箭头函数

var f1 = () => 5;
var f2 = (a,b) => a+b;
var f3 = (a,b) => {
  console.log(a);
  console.log(b);
  return a+b;
}

由于{}会被解析为代码块,如果箭头函数返回的结果是一个对象,需要使用()将对象的{}括起来

var f = (a,b) => ({name : a, age : b}); 

除此之外,箭头函数在使用中需要注意

由此可见,箭头函数更适合一些逻辑简单,只有少量代码的函数编写。如果逻辑复杂代码量较大,相对常规函数,箭头函数并没有优势。


总结


4. 二进制与八进制

ES6 支持二进制和八进制的字面量。

4.1 二进制数据赋值

通过在数字前面添加0b或者0B表示二进制值

let bValue = 0b10; 
console.log(bValue); // 2
4.2 八进制数据赋值

通过在数字前面添加0o或者0O表示八进制值

let oValue = 0o10;
console.log(oValue); // 8

总结


5. 对象与数组的解构

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

5.1 数组的解构

数组的元素是按次序排列的,变量的取值由它的位置决定,因此数组的解构是位置相同才能控制。

5.1.1 数组的解构

比如常规声明变量可以用如下方式

let a = 1;
let b = 2;
let c = 3;

也可以利用解构方式声明

let[a,b,c] = [1,2,3]
5.1.2 函数默认参数中的数组解构

使用解构处理函数数组类型参数

function getRectDesc([width = 5, height = 5]) {
  return `矩形的大小是:${width} x ${height}`;
}

此时调用函数可以用如下方式

getRectDesc([]); // 矩形的大小是:5 x 5
getRectDesc([2]); // 矩形的大小是:2 x 5
getRectDesc([2, 3]); // 矩形的大小是:2 x 3
getRectDesc([undefined, 3]); // 矩形的大小是:5 x 3

getRectDesc函数预期传入的是数组。它通过解构将数组中的第一项设为 width,第二项设为 height。如果数组为空,或者只有一项,那么就会使用默认参数,并将缺失的参数设为默认值 5。
但此时有个问题,如果方法调用时没有提供数组类型的实参,执行时控制台会出现异常。

getRectDesc();  // 控制台抛出异常

因为 getRectDesc() 预期传入的是数组,然后对其进行解构。因为函数被调用时没有传入数组,所以出现问题。
此时解决办法是继续使用默认的函数参数值

function getRectDesc([width = 5, height = 5] = []) {
  return `矩形的大小是:${width} x ${height}`;
}
getRectDesc(); // 矩形的大小是:5 x 5

默认的数组没有提供任何值,此时解构数组使用默认的width和height,所有结果是5x5

5.1.3 箭头函数中的数组解构

上一小节中的示例也可以使用箭头函数来实现。

var getRectDesc = ([width = 5, height = 5] = []) => `矩形的大小是:${width} x ${height}`;
5.2 对象的解构

对象的解构与数组的解构不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性名相同,才能取到正确的值。

5.2.1 对象的解构

对象的解构取决于变量与属性名相同,与顺序无关。

let {job, name} = { name:"赵四", bar:"亚洲舞王"};
console.log(name); //赵四
console.log(job);  //亚洲舞王

如果使用的变量名在属性中不存在,则变量没有赋值,为undefined

let {age, name} = { name:"赵四", bar:"亚洲舞王"};
console.log(name); //赵四
console.log(age);  //undefined
5.2.2 函数默认参数中的对象解构

使用解构处理函数对象类型参数

function createArtist({sname = '赵四', job = '亚洲舞王'}){
    return `${sname}的职业是${job}`;
}

此时调用函数可以用如下方式

createArtist({}); //赵四的职业是亚洲舞王
createArtist({sname:'刘能'});  //刘能的职业是亚洲舞王
createArtist({job: '喜剧演员'}); //赵四的职业是喜剧演员
createArtist({sname:'刘能',job:'喜剧演员'});  //刘能的职业是喜剧演员

createArtist函数预期传入的是对象。它通过解构将对象中的属性sname和job。如果对象为空,或者只有其中一个属性赋值,那么就会使用默认参数,并将缺失的参数设为对应的默认值。
但此时有个问题,如果方法调用时没有提供对象类型的实参,执行时控制台会出现异常。

createArtist();  // 控制台抛出异常

因为 createArtist() 预期传入的是对象,然后对其进行解构。因为函数被调用时没有传入对象,所以出现问题。
此时解决办法是继续使用默认的函数参数值

function createArtist({sname = '赵四', job = '亚洲舞王'} = {}){
    return `${sname}的职业是${job}`;
}
createArtist(); //赵四的职业是亚洲舞王

默认的对象没有提供任何值,此时解构对象使用默认的sname和job,所有结果是“赵四的职业是亚洲舞王”

5.2.3 箭头函数中的对象解构

上一小节中的示例也可以使用箭头函数来实现。

var createArtist = ({sname = '赵四', job = '亚洲舞王'} = {}) => `${sname}的职业是${job}`;

总结


6. 循环与遍历

6.1 for...in

for...in可以用于遍历数组的下标

let a = ['a', '123', {x: 1, y: 2}];
for(let key in a){
    console.log(key);  //0,1,2
}

for...in可以用于遍历对象的属性名

let a = {name:'赵四',job:'亚洲舞王', age: 23};
for(let key in a){
    console.log(key);//name,job,age
}
6.2 for...of

for...of用于遍历数组的数值

let a = ['a', '123', {x: 1, y: 2}];
for(let value of a){
    console.log(value);  //a, 123, {x:1, y:2}
}

for...in不能用于遍历对象的属性名,控制台会报错a is not iterable

let a = {name:'赵四',job:'亚洲舞王', age: 23};
for(let value of a){
    console.log(value);//报错! a is not iterable
}

总结


7. 类的声明与继承

7.1 类的声明
7.1.1 ES5标准中的类声明

在ES5标准中没有明确的关键字支持类,需要利用原型链来完成类的实现。
如下示例是一个关于鸟类的ES5实现

function Bird(name){
    this.name = name;
}
Bird.prototype.fly = function (){
    console.log(this.name+"正在飞");
}
var b = new Bird("燕子");
b.fly();  //燕子正在飞

其中name表示类中的一个属性,fly为类中的一个常规方法。需要将fly方法挂载在prototype上完成类的实现。

7.1.2 ES6标准中的类声明

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链语法糖表现形式。
上一小节中的示例使用ES6实现

class Bird{
    constructor(name) {
        this.name = name;
    }
    fly(){
        console.log(`${this.name}正在飞`);
    }
}
var b = new Bird("燕子");
b.fly();  //燕子正在飞

此处使用了class关键字声明类,constructor关键字声明构造方法,直接定义了fly方法
需要强调的是:虽然写法更接近面向对象式编程,但这些仅仅是原型链方式的语法糖,本质上在底层实现上没有变化。

7.2 构造方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
Java语言的语法类似,一个类必须有constructor方法,如果没有显式定义,系统会提供默认的空构造方法。

constructor() {}

构造方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

class Bird{
  constructor() {
    return Object.create(null);
  }
}
new Bird() instanceof Bird// false

类的构造函数,必须通过创建对象的new关键字调用。这是它跟普通方法的一个主要区别,后者不用new也可以执行。

class Bird{
  constructor() {
    return Object.create(null);
  }
}
Bird();  //控制台抛出异常
7.3 对象

与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

class Bird{
  constructor(name) {
    this.name = name;
  }
  fly() {
    console.log(`${this.name}正在飞`);
  }
}
var b = new Bird("燕子");
b.fly() // 燕子正在飞
console.log(b.hasOwnProperty('name')); // true
console.log(b.hasOwnProperty('fly')); // false
console.log(b.__proto__.hasOwnProperty('fly')); // true

上面代码中,name是实例对象point自身的属性(因为定义在this变量上),
所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。
由此验证,类的所有实例共享一个原型对象,普通方法实际上是定义在原型上,被所有实例共享。

var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__ //true

进一步推导出,如果修改对象p1的原型,同时会影响到p2,因为两者共享同一个原型对象

p1.__proto__.printName = function () { return 'Oops' }; //为p1对象的原型新增一个printName函数
p1.printName() // "Oops"  
p2.printName() // "Oops"

var p3 = new Point(4,2);
p3.printName() // "Oops"

可以看到当为p1对象的原型增加一个printName方法时,其余对象均可以使用这个方法。
这意味着,使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变class的原始定义,影响到所有实例。

如果将方法绑定在构造方法中,可使方法绑定在this变量上而不是原型对象上

class Bird{
  constructor(name) {
    this.name = name;
    this.fly = function (){
      console.log(`${this.name}正在飞`);
    }
  }
}
var b = new Bird("燕子");
b.fly() // 燕子正在飞
console.log(b.hasOwnProperty('name')); // true
console.log(b.hasOwnProperty('fly')); // true
console.log(b.__proto__.hasOwnProperty('fly')); // false
7.4 继承

Java语法中关于继承的操作类似,ES6中允许使用extends关键字完成继承

class Bird{
  constructor(name) {
    this.name = name;
  }
  fly(){
    console.log(`${this.name}正在飞`);
  }
  eat(){
    console.log(`${this.name}正在吃`);
  }
}
class Duck extends Bird{
    constructor(name, color) {
        super(name);
        this.color = color;
    }
    swim(){
        console.log(`${this.color}的${this.name}正在游泳`);
    }
    fly(){
        super.fly();
        console.log(`但${this.name}飞的不远`);
    }
    
}
var b = new Bird("燕子");
var d = new Duck("大黄鸭","黄色");
b.eat(); //燕子正在吃
b.fly(); //燕子正在飞
d.eat(); //大黄鸭正在吃
d.fly();  //大黄鸭正在飞  但大黄鸭飞的不远
d.swim();  //黄色的大黄鸭正在游泳

继承的特点是:

可以看到很多语法规则与Java语言的继承语法几乎一致

7.5 静态

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

7.5.1 静态方法

ES6中使用static关键字描述静态方法

class Bird{
  constructor(name) {
    this.name = name;
  }
  fly(){
    console.log(`${this.name}正在飞`);
  }
  static show(){
    console.log('鸟类拥有吃的技能');
  }
}
var b = new Bird("燕子");
b.fly();  //燕子正在飞
Bird.show(); //鸟类拥有吃的技能
b.show();  //控制台报错   b.show is not a function

需要注意的是

7.5.2 静态属性

ES6中无法使用static关键字设置静态属性,只能通过类名.静态属性名方式声明

class Bird{
  ...
}
Bird.number = 100;
console.log(Bird.number);  //100

总结


8. 模块化

8.1 使用模块化的必要

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口

<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>

这种原始的加载方式暴露了一些显而易见的弊端

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 AMD 和 CMD 两种。前者用于浏览器,后者用于服务器。

8.2 模块系统的演进
8.2.1 AMD

对于依赖的模块,AMD 是提前执行。
AMD 的 API 默认是一个当多个用,require 分全局 require 和局部 require,都叫 require。

define("module", ["dep1", "dep2"], function(d1, d2) {// 依赖必须一开始就写好
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });

优点:

缺点:

8.2.2 CMD

对于依赖的模块,CMD 是延迟执行。CMD 推崇 as lazy as possible。
CMD 的 API 严格区分,推崇职责单一,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');// 依赖可以就近书写
  exports.doSomething = ...
  module.exports = ...
})

优点:

缺点:

8.3 ES6的模块指令

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CMD 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
其具有以下特点

模块功能主要由两个命令构成:exportimport

在chrome中可以通过输入chrome://flags/指令打开实验室,将Experimental JavaScript变为enabled
如果在html中引入模块化的js文件,需要设置

<script type="module" src="....">
8.3.1 export命令

export命令用于规定模块的对外接口
下面代码是dancer.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。

// dancer.js
export var name = '赵四';
export var job = '亚洲舞王';
export var age = 42;

另外一种写法,dancer.js文件,用export命令对外部输出了三个变量组成的对象。

// dancer.js
var name = '赵四';
var job = '亚洲舞王';
var age = 42;
export{name,job,age};

export命令可以输出函数,允许同时输出多个函数
下面示例输出了compute.js的multiply和addition函数

//compute.js
export function multiply(x, y) {
  return x * y;
};
export function addition(x, y) {
    return x + y;
}

对输出函数进行重命名,使用as关键字进行重命名

//compute.js
function multiply(x, y) {
  return x * y;
};
function addition(x, y) {
    return x + y;
}
export {
    multiply as cheng,
    addition as jia
}

export命令可以输出类
下方示例输出bird.js中的Bird类

//bird.js
export class Bird{
  constructor(name) {
    this.name = name;
  }
  fly() {
    console.log(`${this.name}正在飞`);
  }
}

export default 命令
很多时候,一个js文件中可能输出很多内容,为了方便使用者import,可以使用export default指定默认的输出内容
下方示例test.js中默认export的匿名函数

//test.js
export default function  () {
  console.log('foo');
}

也可以指定非匿名函数
下方示例test.js中默认export的foo函数

//test.js
export default function foo() {
  console.log('foo');
}

//test.js
function foo() {
  console.log('foo');
}
export default foo;
8.3.2 import命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
import对象或变量

// 加载dancer.js
import { name, job, age } from './dancer.js‘
console.log(`${name},${job},${age}`);

利用as创建别名

// 加载dancer.js
import { name, job, age as nl } from './dancer.js‘
console.log(`${name},${job},${nl}`);

import命令加载函数

import {multiply} from './compute.js'
console.log(multiply(3,4));

import命令加载类

//main.js
import {Bird} from './bird.js'
var b = new Bird('燕子');
b.eat();

import命令具有提升效果,会提升到整个模块的头部,首先执行
下面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

foo();
import { foo } from './test.js';

import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
下面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

// 报错
import { 'f' + 'oo' } from ‘./test.js';
// 报错
let module = ‘./test.js';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from './test1.js';
} else {
  import { foo } from './test2.js';
}

import语句是 Singleton 模式。次重复执行同一句import语句,那么只会执行一次,而不会执行多次

import { name} from './dancer.js';
import { age } from './dancer.js';

// 等同于
import { name, age } from './dancer.js';

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}
export function round(radius) {
  return 2 * Math.PI * radius;
}

逐一加载

// main.js逐一指定要加载的方法
import { area, round} from './circle.js';
console.log('圆面积:' + area(4));
console.log('圆周长:' + round(14));

整体加载

//main.js整体加载
import * as circle from './circle.js';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.round(14));

总结


9. Promise对象

Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单点说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。

9.1 使用Promise对象的优势

接触过Node的人都知道,Node是以异步(Async)回调著称的,其异步性提高了程序的执行效率,但同时也减少了程序的可读性。如果我们有几个异步操作,并且后一个操作需要前一个操作返回的数据才能执行,这样按照Node的一般执行规律,要实现有序的异步操作,通常是一层加一层的回调函数嵌套下去,这种情况被称为回调地狱

回调地狱

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

9.2 Promise三种操作的状态

对于Promise对象来说,它也有三种状态:

它只有两种状态可以转化,即

9.3 Promise的使用
const promise = new Promise((resolve, reject) => {
    // do something here ...
    if (success) {
        resolve(value); // fulfilled   可以理解为是成功的回调函数
    } else {
        reject(error); // rejected   可以理解为是失败的回调函数
    }
});

由上述代码我们可知:

  • 该构造函数接收两个函数作为参数,分别是resolve和reject。
  • 当异步操作执行成功后,会将异步操作结果作为参数传入resolve函数并执行,此时 Promise对象状态从pending变为fulfilled;
  • 失败则会将异步操作的错误作为参数传入reject函数并执行,此时 Promise对象状态从pending变为rejected;

接下来,
我们通过then方法,分别指定resolved状态和rejected状态的回调函数

promise.then(function(value) {
      // success
}, function(error) {
      // failure
});

then方法可以接收两个回调函数作为参数。

  • 第一个回调函数就是fulfilled状态时调用;
  • 第二个回调函数就是rejected时调用,可选
9.4 Promise的API
9.4.1 Promise.all(iterable)

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

let p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})

let p2 = new Promise((resolve, reject) => {
  resolve('success')
})

let p3 = Promse.reject('失败')

Promise.all([p1, p2]).then((result) => {
  console.log(result)               //['成功了', 'success']
}).catch((error) => {
  console.log(error)
})

Promise.all([p1,p3,p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)      // 失败了,打出 '失败'
})

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

let wake = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${time / 1000}秒后醒来`);
      console.log(`${time} is over`);
    }, time)
  })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
  console.log(error)
})

需要特别注意的是

9.4.2 Promise.race(iterable)

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})
9.4.3 Promise.resolve(value)

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
将jQuery生成的deferred对象,转为一个新的Promise对象。

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.resolve等价于下面的写法。

Promise.resolve('foo');
// 等价于
new Promise(resolve => resolve('foo'));

reslolve参数有以下四种情况

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。

const p = Promise.resolve();
p.then(function () {
  // ...
});
9.4.4 Promise.reject()

返回一个新的Promise实例,该实例的状态为rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
  console.log(s)
});
9.4.5 Promise.Promise.prototype.then()

Promise 的实例具有 then 方法,主要作用是为 Promise 实例发生状态改变时添加回调函数。
它接收两个回调函数作为参数,第一个参数是 fulfilled状态时的回调函数;第二个参数是rejected状态时的回调函数,可不传入。并且该方法返回一个新的Promise对象。

p.then(onResolve, onReject);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});
9.4.6 Promise.Promise.prototype.catch()

返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)相同。推荐使用catch方法,不要在then方法中定义rejected状态的回调函数;这是因为使用catch还可以捕获在then方法执行中存在的错误。

p.catch(onReject)

 p.catch(function(reason) {
     // 拒绝
 });
9.4.7 Promise.Promise.prototype.finally()

返回一个Promsie。是指,在上一轮 promise 运行结束后,无论fulfilled还是 rejected,都会执行指定的回调函数。该方法适合无论结果如何都要进行的操作,例如清除数据。finally不接收任何参数。

 p.finally(onFinally);

  p.finally(function() {
   
  })
9.5 Promise对象实例演示
9.5.1 加载图片
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script>
            let preloadImg = function (img, path) {
              return new Promise(function (resolve, reject) {
                img.onload  = resolve;
                img.onerror = reject;
                img.src = path;
              });
            };
            
            let img1 = new Image();
            let p1 = preloadImg(img1, 'img/1.jpg')
            p1.then(function (){
                console.log('图片加载成功');
                document.getElementById("div1").appendChild(img1);  
            }).catch(function (){
                document.getElementById("div1").innerHTML = "图片加载失败";   
            });
            
            let img2 = new Image();
            let p2 = preloadImg(img2, 'img/1.txt');
            p2.then(function (){
                console.log('图片加载成功');
                document.getElementById("div2").appendChild(img2);  
            }).catch(function (){
                document.getElementById("div2").innerHTML = "图片加载失败";   
            });
        </script>
    </head>
    <body>
        <div id="div1"></div>
        <div id="div2"></div>
    </body>
</html>
9.5.2 链式加载
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script>
            function runAsync1(){
                var p = new Promise(function(resolve, reject){
                    //做一些异步操作
                    setTimeout(function(){
                        console.log('异步任务执行完成1');
                        resolve('数据1');
                    }, 1000);
                });
                return p;            
            }
            function runAsync2(){
                var p = new Promise(function(resolve, reject){
                    //做一些异步操作
                    setTimeout(function(){
                        console.log('异步任务执行完成2');
                        resolve('数据2');
                    }, 5000);
                });
                return p;            
            }
            function runAsync3(){
                var p = new Promise(function(resolve, reject){
                    //做一些异步操作
                    setTimeout(function(){
                        console.log('异步任务执行完成3');
                        resolve('数据3');
                    }, 3000);
                });
                return p;            
            }
             
            runAsync1()
            .then(function(data){
                console.log(data);
                return runAsync2();
            })
            .then(function(data){
                console.log(data);
                return runAsync3();
            })
            .then(function(data){
                console.log(data);
            }); 
        </script>
    </head>
    <body>
    </body>
</html>
上一篇下一篇

猜你喜欢

热点阅读