1.ESMAScript6
ES6其实是一个泛指,泛指ES2015后续的版本
2.新增语法
声明变量
ES5之前因为if、for都没有块级作用域的概念,所以很多时候都需要借助 function的作用域 解决应用外部变量的问题
let 声明的变量
1.只在所处块级有效(大括号中有效,也就是说if和for都有它的块级作用域了),可以防止循环变量变成全局变量
2.不存在变量提升,只能先声明再使用,不可重复声明
3.暂时性死区(即在块级作用域中使用某变量,则会先在该块级中查找此变量)
var num = 10;
if(true) {
    console.log(num);        //报错,变量声明在后面
    let num = 20;
}利用let解决异步问题:(因为let仅在当前块级有效,每个迭代蓄奴含声明一个新的迭代变量,然后使用上一个迭代结束时的值来初始化这个变量。)
for (let i = 0; i < lis.length; i++) {
    lis[i].onclick = function () {
        console.log(i);
    }
}const声明常量,常量就是值(内存地址)不能变化的量(值可以改,内存地址不能变)
1.具有块级作用域
2.const声明常量时必须赋值
3.基本数据类型:常量赋值后,不可修改;复杂数据类型:对象赋值后(数组之类的)不可更改,但是可以更改数据结构内部的值
4.不存在变量提升,只能先声明再使用
const ary = [100, 200];
ary[0] = 'a';   //可以
//ary = ['a', 200];  //不可以ES6之前的块级作用域:
原来的代码
{
    let a = 2;
    console.log(a);
}
console.log(a);以前的代码来实现(丑的要死!!!)
try{ throw 2; } catch(a) {
    console.log(a);
}
console.log(a)解构赋值
ES6允许从数组或者对象(分别使用[]、{})中一一提取值,按照对应的位置,对变量赋值
//数组
let ary = [1, 2, 3];
let [a, b, c, d, e] = ary;  //1, 2, 3, undefined, undefined
//也可以 let [a, b, c] = [1, 2, 3];
//对象
let person = {name: 'zhangsan', age: 20};
let {name, age} = person;
let {name, age = 18} = person;  //解构赋值,如果原来person没有age属性,则定义默认值age = 18对象的另一解构写法(重命名)
let {name: myName, age: myAge} = person;如果name和age分别和person中的属性值匹配成功,则将左侧该属性值赋值给右边的myName,myAge变量
除此外,还有嵌套解构赋值写法
//4.对象的解构赋值
let obj = {a:{b:1}};
const {a:{b}} = obj;  //我们得到b的数据
console.log(b);       //1箭头函数
(形参) => {函数体} 箭头函数用来简化定义函数语法
const fn = () => {
    console.log('xx');
}
// 也可以,返回undefined
//const fn = () => console.log('xx');  如果函数体只有一句话,且代码执行结果就是返回值,则可以省略大括号
//传统
function sum(num1, num2) {
    return num1+num2;
}
//new
const sum1 = (num1, num2) => num1+num2; 如果形参只有一个,小括号可以省略
//传统
function sum(a) {
    return a;
}
//new
const sum1 = a => a; 如果返回的是一个对象,不能直接加大括号
//const sum1 = a => {};  错,返回undefined
const sum1 = a => ({}); //对箭头函数和传统函数不一样,箭头函数的this指向函数定义位置(使用了箭头函数的那个函数)的上下文this(定义函数地点最近作用域中的this)
也就是说,箭头函数的this使用的是词法作用域,而不是this原来的动态作用域
此时我们认为箭头函数将程序员们经常犯的一个错误标准化了,也就是混淆this绑定规则(动态作用域)和词法作用域规则
但是
- 箭头函数不适合事件回调
function fn() {
    console.log(this);
    return () => {
        console.log(this);
    }
}
const obj = { name: 'zhangsan' };
const resFn = fn.call(obj);  //这时this指向obj,所以箭头函数跟着指向obj 返回{ name: 'zhangsan' }
resFn();   //箭头函数中this指向指向上下文this,此时箭头函数this跟着上下文this发生改动,输出{ name: 'zhangsan' }2.不适合对象的方法
对象不能产生作用域,所以箭头函数实际被定义在全局作用域下,所以此处的this指向window,所以箭头函数处的this.age未定义
var obj = {
        age: 20,
        say: () => {
            alert(this.age);  //undefined
        },
        con: function () {
            console.log(this);  //obj
        }
    }
obj.say();
obj.con();const obj = {
    aaa() {
        setTimeout(function () {
            setTimeout(function () {
                console.log(1, this); //window
            })
            setTimeout(() => {
                console.log(2, this); //window
            })
            console.log(3, this);     //window
        })
        setTimeout(() => {
            setTimeout(function () {
                console.log(4, this); //window
            })
            setTimeout(() => {
                console.log(5, this); //obj  
            });
            console.log(6, this); //obj
        });
    }
}3.由于箭头函数必须以赋值声明的方式出现,所以没有变量提升
//会出错,使用var也会出错
console.log(sum(10, 10));
let sum = (num1, num2) => {
    return num1 + num2
}4.箭头函数是不存在原型的
const arrow = () => {
    console.log('a');
}
function fn() {
    console.log('a');
}
console.log(arrow.prototype); // undefined
console.log(fn.prototype); // {constructor: ƒ}arguments的使用
当我们不确定函数用多少个参数来传递的时候,arguments实际上是当前函数的一个内置对象(函数才拥有),arguments储存了传递的所有实参,它展示的方式是伪数组,因此可以进行遍历(使用for等)
注意:
- 箭头函数是用不了arguments(虽然箭头函数也用不了它,但是可以使用剩余参数补足)
- arguments对象的值不反映参数的默认值(当函数设置了默认参数值),它始终以调用函数时传入的值为准
function fn(){
  console.log(agruments);
  console.log(agruments.length);
}
fn(1,2,3);  
//则输出1,2,3
//3arguements 的值始终会与对应的命名参数同步(修改arguments[i],会对应修改第i个参数的值),但这并不意味着它们都访问同一个内存地址,这种同步是单向的,修改命名参数的值,不会影响argument[i] 对应的值
function a(a, b) {
  arguments[0] = 100;
  console.log(a, 'this is a');  //100
  a = 50;
  console.log(arguments[0], 'this is argument[0]');  //100
}
let aaa = 1, b = 2;
a(aaa, b)
console.log(aaa); //1伪数组:1.具有length属性 2.按索引凡是储存数据 3.不具有push,pop功能
注意:
对参数使用slice会阻止某些JavaScript引擎中的优化 (比如 V8 - 更多信息)。如果你关心性能,尝试通过遍历arguments对象来构造一个新的数组。另一种方法是使用被忽视的Array构造函数作为一个函数:
var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));剩余参数
剩余参数语法(展开运算符)允许我们将一个不定数量的参数表示为一个数组  ...args
潜规则: ...args要放到参数的最后,不然会报错
//1.作为传参(rest参数)
function sum(first, ...args) {
    console.log(first); //10
    console.log(args); //20, 30
}
sum(10, 20, 30);
//2.剩余参数配合解构
let students = ['allen', 'berry', 'david'];
let [s1, ...s2] = students;   //s1为allen,s2为[berry. david]的数组
//3.作为参数传入,将数组arr2里的数据划分成若干个,然后一个一个传入数组arr1:(扩展运算符)
arr1.push(...arr2);和arguments不一样的是,arguments得到的是一个对象,而…args中 args得到的是一个数组,可以使用数组方法(filter、some、map、every等)
function data1(){
    console.log(arguments);
}
function data2(...args){
     console.log(args);
}利用args手写new
应证了《你不知道的JavaScript》里
- 创建一个全新的对象
- 这个新对象会被执行Prototype连接
- 新对象会绑定到函数调用的this
- 如果函数没有其他返回值,那么new表达式中的函数调用会自动返回这个新对象
function _new(Constructor, ...args) {
    var obj = {};
    obj.__proto__ = Constructor.prototype;
    var ret = Constructor.apply(obj, args)
    return ret instanceof Object ? ret : obj;
}
function One(a) {
    this.a = a;
}
let a = new One('good');
let b = _new(One, 'good');
console.log(a);
console.log(b);展开运算符
剩余语法(Rest syntax) 看起来和展开语法完全相同,不同点在于, 剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。(来源于MDN)
运用于数组
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1];一般展开运算符只能展开可迭代对象,对于对象,但是可以 使用 let person1 = {...person2}进行展开
参数默认值
可以给函数附上默认的参数值,在调用时没有给到的形参会用默认值代替
一般具有默认值的参数都靠后(潜规则)
function add(a, b ,c=10){
    return a + b + c;
}
console.log(add(1, 2));  //133.ES6内置对象的扩展
array的扩展
  ...ary
扩展运算符是和剩余参数相反的原理,它可以将数组或者对象转为用逗号分隔的参数序列  ...ary
let ary = [1, 2, 3];
console.log(...ary); // 1, 2, 3
//相当于 console.log('1', '2', '3');扩展运算符应用:数组合并
//方法一:
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
//方法二
ary1.push(...ary2);...ary扩展运算符还能把伪数组转换成真正的数组,然后可以使用数组的方法
可计算属性名
ES6增加了可计算属性名,可以在文字形式中使用 [] 包裹一个表达式当作属性名
const prefix = "foo";
const obj = {
    [prefix + "bar"]: "hello"
}string的拓展
ES6新增的创建字符串的方式,使用反引号定义 let name = allen;(模板字符串)
模板字符串的特点:
1.可以解析变量,不用字符串拼接${变量名}
2.可以换行,撰写较为美观
3.可以调用函数,得到的结果为函数返回值${函数名()}
const saySomething = () => '我是函数';
let a = `allen`;
let = `hello, my name is ${name}`;
let html = `<div>
    <span>${saySomething()}</span>
</div>`;set数据结构
ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值(不会存储重复的值)、自动去重
集合实现了 iterator接口,所以可以使用 for of
Set本身是一个构造函数,用来生成Set数据结构
const s = new Set(["a", "a","b"]);
console.log(s.size);
const ary = [...s];    //数组去重- s.add(value)添加某个值,返回Set结构本身
- s.delete(value)删除某个值,返回布尔值表示删除成功与否
- s.has(value)返回布尔值,查看是否为Set成员
- s.clear()清空所有成员
- s.values()查看所有元素- s[Symbol.iterator]
 
- s.size()返回- Set实例的成员总数
Set结构实例与数组一样,也有forEach方法,用于对每个成员执行某种操作,没有返回值
map数据结构
 是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key),“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
(Object的键只能为字符串,其实还能为数值或符号,但它们都会转化为字符串)。
Map 的遍历顺序就是插入顺序。
使用对象作为键是 Map 最值得注意和重要的功能之一
let map = new Map();
let john = { name: "John" };
map.set('1', 'str1');   // 字符串键
map.set(1, 'num1');     // 数字键
map.set(true, 'bool1'); // 布尔值键
map.set(john, 123));    // 对象键
// 还记得普通的 Object 吗? 它会将键转化为字符串
// Map 则会保留键的类型,所以下面这两个结果不同:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3当然也可以接受一个数组作为参数
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);方法 + 属性
- new Map()—— 创建 map。
- map.set(key, value)—— 根据键存储值。
- map.get(key)—— 根据键来返回值,如果- map中不存在对应的- key,则返回- undefined。
- map.has(key)—— 如果- key存在则返回- true,否则返回- false。
- map.delete(key)—— 删除指定键的值。
- map.clear()—— 清空 map。
- map.size—— 返回当前元素个数。
- map.keys()—— 遍历并返回所有的键(returns an iterable for keys,返回一个引用的- Iterator对象。它包含按照顺序插入- Map对象中每个元素的key值。)
- map.values()—— 遍历并返回所有的值(returns an iterable for values)
- map.entries()—— 遍历并返回所有的键值对,entries实际上是引用了- [Symbol.iterator]这个属性- map.entries === map[Symbol.iterator] //true
 
只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefinedmap和Object对比:
1.内存占用:给固定大小的内存,Map大约可以比Object多存储50%键值对
2.插入性能:Object和Map插入新的键值对消耗大致相当,不过插入Map在所有浏览器中一般会稍微快一点儿
3.查找速度:与插入不同,从大型Object和Map中查找键值对的性能差异极小,如果涉及大量查找工作(对两个类型而言,查找速度不会随着键值对数量增加而增加),某些情况可能Object更好一点
4.删除性能:删除Objcet属性的性能一直以来饱受诟病,而对于大多数浏览器引擎来说,Map的delete()删除操作甚至比插入和查找更快,无疑时Map完胜
弱引用类型
(很多属性、迭代方法不能用 + 保存的元素有限制)
let s = new WeakSet()保存的元素必须得是引用类型(对象 / 数组)(DOM元素也是对象,所以也能存储)
let map = new WeakMap()的key 必须得是引用类型(对象 / 数组)(DOM元素也是对象,所以也能存储)
弱引用不支持遍历方法,只有四个方法可用,get()、set()、has()、delete()
正常引用类型的垃圾回收:
- 谁引用这个数据,就引用次数 + 1, 
- 原来引用这组数据,后面赋值为null,引用次数 -1 
- 当这组数据引用次数为0,则根据垃圾回收机制会被回收掉 
弱引用类型的垃圾回收:
- 当weak弱引用数据时,引用次数不会 + 1
这样的话优点就是,清除变量的时候,不用再去weak弱引用类型那里进行清除(不用赋值null)
obj的拓展
利用对象字面量创建对象即直接用{}创建对象而不是new出来,而ES6新增对象字面量的增强写法
const name = 'Allen';
const age = 18;
const height = 1.88;
// ES5对象字面量各类属性、函数写法
obj = {
    name: name,
    age: age,
    height: height,
    run: function () { }
};
// ES6对象字面量各类属性、函数写法
obj2 = {
    name,
    age,
    height,
    run() { }  //此写法仅支持在字面量/类中
};判断一个对象是否为空,可以使用
Object.key(对象名称).length === 0js 判断对象的属性是否存在
in运算符 (属性名 in 对象)
情况1:对象自身属性
var obj={a:1};
"a" in obj//true情况2:对象继承的属性
var objA = {a:1};
var objB = Object.create(A)
"a" in objB //true查找符合条件的第一个对象
find(function(currentValue, index, arr),thisValue)
| 参数 | 描述 | 
|---|---|
| currentValue | 必需。当前元素 | 
| index | 可选。当前元素的索引值 | 
| arr | 可选。当前元素所属的数组对象 | 
| thisValue | 可选。 传递给函数的值一般用 “this” 值。 如果这个参数为空, “undefined” 会传递给 “this” 值 | 
//实现一步查找符合条件的product
let product = state.cartList.find(item => item.id === payload.id);返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined。
对象使用变量名作为键名
let a = 'name';
let obj = {};
//obj.a = 'Allen'错误!
obj[a] = Allen  //正确又或者
let a = 'name';
let obj = {
    [a]:'Allen'
};4.Promise
callback hell
回调地狱:callback hell,异步里面套着另一个异步

无法保证异步任务执行顺序:
var fs = require('fs');
fs.readFile('./index.txt', function (err, data) {
    if (err) {
        // throw的作用:抛出异常
        //即1.阻止程序的执行, 2.把错误信息打印到控制台
        throw err;
    }
    console.log(data);
});
fs.readFile('./index2.txt', function (err, data) {
    if (err) throw err;
    console.log(data);
});
fs.readFile('./index3.txt', function (err, data) {
    if (err) throw err;
    console.log(data);
});通过回调嵌套的方式来保证顺序,但由此催生了回调地狱,语法十分丑陋,代码丑陋
fs.readFile('./index.txt', function (err, data) {
    if (err) {
        // throw的作用:抛出异常
        //即1.阻止程序的执行, 2.把错误信息打印到控制台
        throw err;
    }
    console.log(data);
    fs.readFile('./index2.txt', function (err, data) {
        if (err) throw err;
        console.log(data);
        fs.readFile('./index3.txt', function (err, data) {
            if (err) throw err;
            console.log(data);
        });
    });
});Promise
为了避免回调地狱嵌套,所以ES6中新增了API:Promise(生产微任务)
应用场景:数据来源于多个接口,出现回调嵌套
Promise本身不是异步的,只是里面的任务往往都是异步的
new Promise(resolve => {
    resolve();
    console.log("promise");
}).then(value => console.log("success!"));
console.log("end");
//执行顺序:
//promise
//end
//success!创建一个promise容器 => 而这里容器一旦创建,就开始执行里面的代码 => 容器中存放一个异步任务
默认pending状态,表示正准备去做,即将发生的

个人觉得:resolve和reject类似于两个callback,然后再外面进行回调罢了
finally()方法用于指定不管Promise对象最后状态如何(无论结果是fulfilled或者是rejected),都会执行的操作,该方法时ES2018引入的标准
var fs = require('fs');
//封装实例化Promise+读取数据API
function ProReadFile(Path) {
    return new Promise(function (resolve, reject) {
        fs.readFile(Path, 'utf8', function (err, data) {
            if (err) {
                // 失败了,承诺容器中的任务失败
                // 把容器的pending状态改为Rejected
                // 调用reject相当于调用了then方法第二个参数函数
                reject(err);
            } else {
                // 承诺容器中的任务成功
                // 把容器的pending状态改为成功Resolved
                // 调用resolve相当于调用了then方法第一个参数函数
                resolve(data);
            }
        })
    });
};
当返回结果成功后,then做指定操作
使用Promise过程中resolve或reject后,后面代码还会执行,除非你直接return
then的说明:
- then(resolve(), reject())
- 成功状态Fulfilled时(resolve,成功则进入下一个then),then方法接收两个参数:1.容器中的resolve函数, 2.容器中的reject函数,这里把then看成一个整体,then会默认返回一个 - fulfilled状态的- Promise
- 失败状态Rejected(自己设置判断失败的条件,然后reject函数),会回调catch 
- 在类里面定义 一个then方法,那么他会包装成一个 - Promise,但是注意这个- Promise默认没有状态,需要手动去- resolve或者- reject
ProReadFile('./index.txt')
    .then(function (data) {
        console.log(data);
        //当第一个读取成功时,这里返回后面想要继续执行的Promise异步任务,如果没有返回,则后面收到的是undefined
        //我们真正有用的是return 一个Promise对象
           //如果return 123,则接下来的then的function参数接受的data是123,而且并不是前面的异步任务执行完毕才进入下一个then
        return ProReadFile('./index2.txt');
    }, err => {
        console.log('读取文件失败', err);
        throw 'error message';           //要调用这个,不然返回undefined,会进入下一个then的resolve的回调
    })
    .then(function (data) {
        console.log(data);
       //第二个读取成功时,这里返回后面想要继续执行的Promise异步任务
        return ProReadFile('./index3.txt');
    }, err => {
        console.log('读取文件失败', err);
        throw 'error message';
    })
    .then(function (data) {
        console.log(data);
    }, err => {
        console.log('读取文件失败', err);
    })catch效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中(推荐把catch放在链式结构的最后,前面无论第几个出错,都会跑到最后执行这个catch(除非你事先在前面用reject的回调函数处理过错误结果了))。请看下面的代码:
ProReadFile('./index.txt')
    .then(function (data) {
        console.log(data);
        return ProReadFile('./index2.txt');
    }).catch(err => {console.log('文件读取失败', err)})特殊情况
状态传递
注意:如果resolve或reject中的参数是promise实例对象
var p1 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve('1');
  }, 3000);
})
var p2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(p1); // resolve的参数是一个promise对象
  }, 1000);
});
p2
  .then(function (data) {
    console.log('resolve执行')
    console.log(data) 
  }, function (err) {
    console.log(err)
  })3s后依次打印 ‘resolve执行’ ‘1’ (时间 = max (p1的定时时长, p2的定时时长))
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。(实际上有点像是promise.allSettle的原理?)
let p1 = new Promise((resolve, reject) => {
    reject("拒绝");
})
new Promise((resolve, reject) => {
    resolve(p1);
}).then(res => {
    console.log("res");
}, err => {
    console.log("err", err);
})
//err 拒绝上面说到p1的状态会传递,所以这里p1的状态是Rejected,传递给了下面这个Promise,执行的是Rejected的回调
返回值问题
注意 如果返回值为一个普通参数
then方法会返回一个新的promise,这个新promise的value由return的值决定
执行return语句后不是Promise实例,是123,则导致当前then方法返回的promise变为成功状态 pending->fulfilled(Resolved)
在这里它其实是 return new Promise.resolve(123)的简写
var p2 = p1.then(function (data) {
    console.log(data);
    return 123456
}, err => {
    console.log('读取文件失败', err);
})
.then(function (data) {
    console.log(data);      // 这个回调一定会被调用,打印123456
}, err => {
    console.log('读取文件失败', err);
})注意 甚至没有返回值时,它还会自动给你 pedding -> fulfilled,(因为会返回默认返回值undefined)然后进入下一个then的第一个成功的回调函数里
而反而如果你 return 了一个promise实例,在这个实例里面没有调用 resolve 或者 reject,就进入不了下一个then里面
所以!!!
- 因为异步操作我们才使用 - Promise,而返回值非- Promise的情况会导致未执行完异步操作则直接进入下一步的then里面,这样和原来未使用Promise语句地执行方式相同,和我们想要有序地进行异步操作的初衷背道而驰
- 所以我们推荐返回值返回一个 - new Promise,这样等到- Promise实例调用- resolve / reject后才会进入下一步- then,才符合我们的代码规范和初衷!!
直接抛出异常问题
注意 如果想要返回后直接跳转到下一个then的reject函数里,可以直接 throw ErrorMessage
执行throw语句后,导致当前then方法返回的promise变为失败状态 pending->Rejected
因为它其实是 return new Promise.reject('error message')的简写
 var p2 = p1.then(function (data) {
        console.log(data);
        throw 'error message';
    }, err => {
        console.log('读取文件失败', err);
    })
    .then(function (data) {
        console.log(data);     
    }, err => {
        console.log('读取文件失败', err);  // 这个回调一定会被调用
    })双重then问题
注意 then方法提供一个供自定义的回调函数,若传入非函数,则会忽略当前then方法。
以下的例子就是忽略了第一个then,因为它未传入函数,传入的是 ‘新的值’
let func = function() {
    return new Promise((resolve, reject) => {
        resolve('返回值');
    });
};
let cb = function() {
    return '新的值';
}
func().then(cb()).then(resp => {
    console.warn(resp);
    console.warn('=========');
});  
//输出:返回值 ============状态问题
注意 在执行promise后,return时都会包装成一个新的Promise实例,但如果then方法还未被调用,则这个实例它的状态还是pedding
let p1 = new Promise((resolve, reject) => {
    resolve("fulfilled");
})
let p2 = p1.then(
    val => {console.log(val);},
    err => {console.log(err)}
)
console.log(p1);
console.log(p2);
setTimeout(()=>{
    console.log(p1);
    console.log(p2);
})
//输出
//Promise<resolved>
//Promise<pending>
//fulfilled
//Promise<resolved>
//Promise<resolved>Promise的all方法使用
应用场景:处理多个相互依赖的异步请求
Promise.all([
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('hello');
        }, 1000);
    }),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('world');
        }, 2000);
    })
])
// 两个网络请求都完成后才会进入then
// 如果有一个失败,此回调直接失败,失败原因是那个第一个失败的promise
    .then(results => {
    // results是一个数组,它包含以上异步操作的结果
    console.log(results[0], results[1]);
})除了 all之外,还有allSettle、race方法,分别表示
- allSettle:只有等到所有这些参数实例都返回结果,不管是- fulfilled还是- rejected,包装实例才会结束,返回一个对象数组,每个对象表示对应的promise结果
- race:谁执行的快就取决于谁的状态
- any:只要参数实例有一个变成- fulfilled状态,包装实例就会变成- fulfilled状态;如果所有参数实例都变成- rejected状态,包装实例就会变成- rejected状态。
(感觉race方法可能是封装ajax中设置请求超时时长的原理)
有空还可以看看大佬手写promise.all和promise.race https://blog.csdn.net/qq1498982270/article/details/93922893
async和await语法糖
async和await时Promise的语法糖
(2021.6.27纠正,我在《红宝书的啃读》篇目解释了它们在内存中的差别)
使用 await 异步函数() 相当于 .then(res => { return 异步函数() }),处理异步任务,有异步任务 -> 同步任务的感觉,记得每次都把异步任务放在await后面,而且每次在await 异步操作之后的同步任务就像被放在另一个then里面,会等待异步任务的完成后再执行
async function pro(delay = 1000) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("hello");
        }, delay);
    })
}
async function show() {
    for (const item of ["Allen", "Bruce", "Carry"]) {
        let hello = await pro();
        console.log(item);
        console.log("hello");
        console.log("world");
    }
}
show();
// //原始方法
// let p = Promise.resolve();
// for (const item of ["Allen", "Bruce", "Carry"]) {
//   p = p.then((res) => {
//     return pro();
//   }).then(() => {
//     console.log(item);
//       console.log("hello");
//     console.log("world");
//   })
// }语法糖可以配合 then、 catch一起使用
async function fn() {
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("nononon");
            resolve();
        }, 1000);
    })
}
fn().then(res => {
    console.log("success");
}).catch((err) => {
    console.log(err);
})async + await实现并行执行 (配合Promise.all)
async function fn(k) {
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(k);
            resolve(k);
        }, 1000);
    })
}
async function hd() {
    let res = await Promise.all([fn("hello"), fn("world")])
    console.log(res);
}
hd(); await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator
Promise缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。(只能抛出错误中断(throw))(JavaScript高级程序设计有提及到)
- 如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。(JavaScript高级程序设计有提及到)
- 可能代码撰写比较繁琐 + 冗长
手写promise
函数体内部首先创建了常量 that,因为代码可能会异步执行,用于获取正确的 this 对象(即this一开始指向MyPromise,后面指向window)
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(fn) {
    const that = this //此时this指向MyPromise
    that.state = PENDING
    that.value = null
    that.resolvedCallbacks = []
    that.rejectedCallbacks = []
    // 待完善 resolve 和 reject 函数
    function resolve(value) {
        //此时this指向window
        if (that.state === PENDING) {
            that.state = RESOLVED
            that.value = value
            that.resolvedCallbacks.map(cb => cb(that.value))
        }
    }
    function reject(value) {
        if (that.state === PENDING) {
            that.state = REJECTED
            that.value = value
            that.rejectedCallbacks.map(cb => cb(that.value))
        }
    }
    // 待完善执行 fn 函数
    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}
// then方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
    //实际上这里面的的this都指向MyPromise,甚至不换成that也行,但是为了美观和易读性都换成that
    const that = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected =
        typeof onRejected === 'function'
        ? onRejected
    : r => {
        throw r
    }
    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
    }
    if (that.state === RESOLVED) {
        onFulfilled(that.value)
    }
    if (that.state === REJECTED) {
        onRejected(that.value)
    }
}实践
new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1) // 此时环境下this为window
    }, 2000)
}).then(value => {
    console.log(value)
})5.Error
当运行时错误产生时,Error 对象会被抛出。Error 对象也可用于用户自定义的异常的基础对象。下面列出了各种内建的标准错误类型。
我们通常在异步请求后使用try catch捕获,在发生请求错误的时候可以throw 一个 Error
fetchAPI().then((res) => {
  console.log(res);
  if (!res) throw new Error('当前没有返回值??: no response');
}).catch((e) => {
  console.log(e);
});其他error
除了通用的 Error 构造函数外,JavaScript 还有其它类型的错误构造函数。对于客户端异常,详见异常处理语句。
- 
创建一个 error 实例,表示错误的原因:与 eval()有关。
- 
创建一个 error 实例,表示错误的原因:数值变量或参数超出其有效范围。 
- 
创建一个 error 实例,表示错误的原因:无效引用。 
- 
创建一个 error 实例,表示错误的原因:语法错误。 
- 
创建一个 error 实例,表示错误的原因:变量或参数不属于有效类型。 
- 
创建一个 error 实例,表示错误的原因:给 encodeURI()或decodeURI()传递的参数无效。
- 
创建一个 error 实例,其中包裹了由一个操作产生且需要报告的多个错误。如: Promise.any()产生的错误。
- InternalError非标准- 创建一个代表 Javascript 引擎内部错误的异常抛出的实例。如:递归太多。 
当然也可以自定义Error
class PayError extends Error {
  constructor(code, message, other = null) {
    super(message);
    this.code = code;
    this.other = other;
  }
} 
                     
                        
                        