玩命加载中 . . .

ES6 语法汇总

一、兼容性

目前各大浏览器基本上都能够支持 ES6 的新特性,其中 Chrome 和 Firefox 对 ES6 最为友好,但 IE7~11 基本不支持。

以下是各大浏览器支持情况以及开始时间:

二、let 与 const

let声明变量,const声明常量:两个都有块级作用域,ES5没有块级作用域。

①变量提升(变量可在声明前使用,值为undefined): let,const不行,var可以,并将申明的变量挂载在window上。

②块级作用域: let和const只在块级作用域中有效,存在暂存死区,var不是。

③重复声明: 同一作用域下let和const命令都不允许重复声明同一个变量,var可以但不建议这么做。

④初始值: const一旦声明必须赋初始值,且声明后不许改变,如果声明的是复合类型数据,可以修改其属性,因为const只是指向对象的地址,地址不变,地址指向的内容是可以改变的。

for (var j = 0; j < 10; j++) {  
  setTimeout(function(){    
    console.log(j);  
})
}
// var: 输出十个10
// 因为所有j都使用同一个引用,而j最终会被更新为10

for (let j = 0; j < 10; j++) {  
  setTimeout(function(){    
    console.log(j);  
})
}
// 输出: 0123456789
// let不会在栈内存里预分配内存空间,在栈内存分配变量时检查是否存在相同变量名,有就报错,也就是暂时性死区
// 块级作用域中let命令所声明的变量不受外部影响

for (const j = 0; j < 10; j++) {  
  setTimeout(function(){    
    console.log(j);  
})
}
// 输出 0
// const声明后不许改变,所以为初始赋值0

三、解构赋值

针对数组对象进行匹配,然后对其中的变量进行赋值。常用于交换变量值,取函数返回值,或设置默认值。

3.1 数组的解构赋值(Array)

// 基本
let [a, b, c] = [1, 2, 3]; // a = 1  b = 2  c = 3

// 可嵌套
let [a, [[b], c]] = [1, [[2], 3]]; // a = 1  b = 2  c = 3

// 可忽略
let [a, , b] = [1, 2, 3]; // a = 1  b = 3

// 不完全解构
let [a = 1, b] = []; // a = 1, b = undefined

// 剩余运算符
let [a, ...b] = [1, 2, 3]; //a = 1  b = [2, 3]

// 字符串等
// 在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
let [a, b, c, d, e] = 'hello'; // a = 'h'  b = 'e'  c = 'l'  d = 'l'  e = 'o'

// 解构默认值
let [a = 2] = [undefined]; // a = 2

// 当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 3, b = a] = []; // a = 3, b = 3 
let [a = 3, b = a] = [1]; // a = 1, b = 1 
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
// a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
// a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a = 1
// a 与 b 正常解构赋值,匹配结果:a = 1,b = 2

3.2 对象的解构赋值(Object)

基本

let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb' let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd'

可嵌套可忽略

let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { y }] } = obj; // x = 'hello' // y = 'world' let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { }] } = obj; // x = 'hello'

不完全解构

let obj = {p: [{y: 'world'}] }; let {p: [{ y }, x ] } = obj; // x = undefined // y = 'world'

剩余运算符

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; // a = 10 // b = 20 // rest = {c: 30, d: 40}

解构默认值

let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5; let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;

四、Symbol

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

作为属性名:

let sy = Symbol("key1");  
// 写法1 
let syObject = {}; 
syObject[sy] = "kk"; 
console.log(syObject); // {Symbol(key1): "kk"} 

// 写法2 
let syObject = {  [sy]: "kk" }; 
console.log(syObject); // {Symbol(key1): "kk"} 

// 写法3 
let syObject = {}; 
Object.defineProperty(syObject, sy, {value: "kk"}); 
console.log(syObject); // {Symbol(key1): "kk"}

五、Map 与 Set

应用场景:Set用于数据重组,Map用于数据储存

  Set: 

  (1)成员不能重复

  (2)只有键值没有键名,类似数组

  (3)可以遍历,方法有add,delete,has

  Map:

  (1)本质上是健值对的集合,类似集合

  (2)可以遍历,可以跟各种数据格式转换

5.1 Map 对象

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

Maps 和 Objects 的区别:

  • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

5.2 Map 中的 key

key 是字符串

var myMap = new Map(); 
var keyString = "a string";   
myMap.set(keyString, "和键'a string'关联的值");  
myMap.get(keyString); // "和键'a string'关联的值" 
myMap.get("a string"); // "和键'a string'关联的值" 因为 keyString === 'a string'

key 是对象

var myMap = new Map(); 
var keyObj = {},   
myMap.set(keyObj, "和键 keyObj 关联的值");  
myMap.get(keyObj); // "和键 keyObj 关联的值" 
myMap.get({}); // undefined, 因为 keyObj !== {}

key 是函数

var myMap = new Map(); 
var keyFunc = function () {}, // 函数 
myMap.set(keyFunc, "和键 keyFunc 关联的值");  
myMap.get(keyFunc); // "和键 keyFunc 关联的值" 
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}

key 是 NaN

var myMap = new Map(); 
myMap.set(NaN, "not a number");  
myMap.get(NaN); // "not a number"  
var otherNaN = Number("foo"); 
myMap.get(otherNaN); // "not a number"

虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。

5.3 Map 的迭代

对 Map 进行遍历,以下两个最高级。

5.3.1 for…of

可以遍历数组,Set和Map结构,某些类数组对象,以及字符串。

var myMap = new Map(); 
myMap.set(0, "zero"); 
myMap.set(1, "one"); 
// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one" 
for (var [key, value] of myMap) {  
  console.log(key + " = " + value);
} 
for (var [key, value] of myMap.entries()) { 
  console.log(key + " = " + value); 
} 
/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */  
// 将会显示两个log。 一个是 "0" 另一个是 "1" 
for (var key of myMap.keys()) {  
  console.log(key); 
} 
/* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */  
// 将会显示两个log。 一个是 "zero" 另一个是 "one" 
for (var value of myMap.values()) {  
  console.log(value); 
} 
/* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。*/

5.3.2 forEach()

var myMap = new Map(); 
myMap.set(0, "zero"); myMap.set(1, "one");  
// 将会显示两个 logs。 一个是 "0 = zero" 另一个是 "1 = one" 
myMap.forEach(function(value, key) {  
  console.log(key + " = " + value); 
}, myMap)

5.4 Map 对象的操作

Map 与 Array的转换

var kvArray = [["key1", "value1"], ["key2", "value2"]];  
// Map 构造函数可以将一个二维键值对数组转换成一个 Map 对象 
var myMap = new Map(kvArray);  
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
var outArray = Array.from(myMap);

Map 的克隆

var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]); 
var myMap2 = new Map(myMap1);  console.log(original === clone);  
// 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。

Map 的合并

var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); 
var second = new Map([[1, 'uno'], [2, 'dos']]);  
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three 
var merged = new Map([...first, ...second]);

5.5 Set 对象

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

5.6 类型转换

Array

// Array 转 Set 
var mySet = new Set(["value1", "value2", "value3"]); 
// 用...操作符,将 Set 转 Array 
var myArray = [...mySet]; 
String 
// String 转 Set 
var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"}
// 注:Set 中 toString 方法是不能将 Set 转换成 String

5.7 数组去重

var mySet = new Set([1, 2, 3, 4, 4]); 
[...mySet]; // [1, 2, 3, 4]

5.8 并集

var a = new Set([1, 2, 3]); 
var b = new Set([4, 3, 2]); 
var union = new Set([...a, ...b]); // {1, 2, 3, 4}

5.9 交集

var a = new Set([1, 2, 3]); 
var b = new Set([4, 3, 2]); 
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}

5.10 差集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]); 
var difference = new Set([...a].filter(x => !b.has(x))); // {1}

六、Reflect 与 Proxy

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

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

6.1 Proxy

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25

// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"

// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt)
// {name: "Tom"}

// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty) // {name: "Tom"}

6.2 Reflect

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

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

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

静态方法

Reflect.get(target, name, receiver)

查找并返回 target 对象的 name 属性。

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
}
Reflect.get(exam, 'info', receiver); // Jerry20

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

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

七、字符串

ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。

  • **includes()**:返回布尔值,判断是否找到参数字符串。
  • **startsWith()**:返回布尔值,判断参数字符串是否在原字符串的头部。
  • **endsWith()**:返回布尔值,判断参数字符串是否在原字符串的尾部。

以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引。

let string = "apple,banana,orange";
string.includes("banana");     // true
string.startsWith("apple");    // true
string.endsWith("apple");      // false
string.startsWith("banana",6)  // true

注意点:

  • 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf 。
  • 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它。

7.1 字符串重复

repeat():返回新的字符串,表示将字符串重复指定次数返回。

console.log("Hello,".repeat(2));  // "Hello,Hello,"

如果参数是小数,向下取整

console.log("Hello,".repeat(3.2));  // "Hello,Hello,Hello,"

如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次

console.log("Hello,".repeat(-0.5));  // "" 

如果参数是 NaN,等同于 repeat 零次

console.log("Hello,".repeat(NaN));  // "" 

如果参数是负数或者 Infinity ,会报错:

console.log("Hello,".repeat(-1));  
// RangeError: Invalid count value

console.log("Hello,".repeat(Infinity));  
// RangeError: Invalid count value

如果传入的参数是字符串,则会先将字符串转化为数字

console.log("Hello,".repeat("hh")); // ""
console.log("Hello,".repeat("2"));  // "Hello,Hello,"

7.2 字符串补全

  • padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
  • padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。

console.log("h".padStart(5,"o"));  // "ooooh"
console.log("h".padEnd(5,"o"));    // "hoooo"
console.log("h".padStart(5));      // "    h"

如果指定的长度小于或者等于原字符串的长度,则返回原字符串:

console.log("hello".padStart(5,"A"));  // "hello"

如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:

console.log("hello".padEnd(10,",world!"));  // "hello,worl"

常用于补全位数:

console.log("123".padStart(10,"0"));  // "0000000123"

7.3 模板字符串

模板字符串相当于加强版的字符串,${} 拼接参数,可以在字符串中加入变量和表达式。

基本用法

普通字符串

let string = `Hello'\n'world`; 
console.log(string);  
// "Hello'
// 'world"

多行字符串:

let string1 =  `Hey, can you stop angry now?`; 
console.log(string1); 
// Hey, 
// can you stop angry now?

字符串插入变量和表达式。

变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。

let name = "Mike"; 
let age = 27; let info = `My Name is ${name},I am ${age+1} years old next year.` console.log(info); 
// My Name is Mike,I am 28 years old next year.

字符串中调用函数:

function f(){  return "have fun!"; } 
let string2= `Game start,${f()}`; 
console.log(string2);  
// Game start,have fun!

注意要点

模板字符串中的换行和空格都是会被保留的

应用

过滤 HTML 字符串,防止用户输入恶意内容。

function f(stringArr,...values){
 let result = "";
 for(let i=0;i<stringArr.length;i++){
  result += stringArr[i];
   if(values[i]){
     result += String(values[i]).replace(/&/g, "&amp;")
               .replace(/</g, "&lt;")
               .replace(/>/g, "&gt;");
    }
 }
 return result;
}
name = '<Amy&MIke>';
f`<p>Hi, ${name}.I would like send you some message.</p>`;
// <p>Hi, &lt;Amy&amp;MIke&gt;.I would like send you some message.</p>

国际化处理(转化多国语言)

i18n`Hello ${name}, you are visitor number ${visitorNumber}.`;  
// 你好**,你是第**位访问者

八、箭头函数

箭头函数:省去function关键字,用 () = > 来定义,函数的参数放在箭头前的括号,函数体在箭头后的花括号。

它的 this 继承了外层执行环境的 this,不能被 call() apply() bind() 改变指向。

没有自己的arguments,用rest参数代替arguments对象,来访问箭头函数的参数列表。

没有原型prototype,打印显示undefined。

不能用作Generator函数,所以不能使用yeild关键字。

不能用作构造函数,也就是说不能使用new命令,否则报错

适合使用的场景:

ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。

不适合使用的场景:

定义函数的方法,且该方法中包含 this 或需要动态 this 的时候。

九、… 拓展运算符

将数组或对象里面的值展开,或将多个值收集为一个变量,代替argument对象,对象未嵌套可用来浅拷贝。

十、Iterator 迭代器

Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:

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

迭代的过程如下:

  • 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
  • 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束
  • 当 done 为 true 时则遍历结束
    ```javascript
    const items = [“zero”, “one”, “two”];
    const it = itemsSymbol.iterator;

it.next();

{value: “zero”, done: false}
it.next();
{value: “one”, done: false}
it.next();
{value: “two”, done: false}
it.next();
{value: undefined, done: true}

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

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

**可迭代的数据结构:**

以下是可迭代的值:

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

## 十一、Class 类

在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。class 的本质是 function。它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。定义类更简便地实现类的继承。

**类定义**

类表达式可以为匿名或命名。

// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
}

**类声明**

class Example {
constructor(a) {
this.a = a;
}
}


**注意要点:**
不可重复声明。
方法间不能加分号。
类中方法不需要 function 关键字。
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
修饰器@: decorator是一个函数,用来修改类甚至于是方法的行为,修饰器本质就是编译时执行的函数。

**属性**

prototype,ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法。

Example.prototype={
//methods
}


添加方法

Object.assign(Example.prototype,{
//methods
})


静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。

class Example {
// 新提案
static a = 2;
}
// 目前可行写法
Example.b = 2;


公共属性

class Example{}
Example.prototype.a = 2;


实例属性:定义在实例对象( this )上的属性。

class Example {
a = 2;
constructor () {
console.log(this.a);
}
}


name 属性,返回跟在 class 后的类名(存在时)。

let Example=class Exam {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Exam

let Example=class {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Example


constructor 方法是类的默认方法,创建类的实例化对象时被调用。

class Example{
constructor(){
console.log(‘我是constructor’);
}
}
new Example(); // 我是constructor


返回对象

class Test {
constructor(){
// 默认返回实例对象 this
}
}
console.log(new Test() instanceof Test); // true

class Example {
constructor(){
// 指定返回对象
return new Test();
}
}
console.log(new Example() instanceof Example); // false


静态方法

class Example{
static sum(a, b) {
console.log(a+b);
}
}
Example.sum(1, 2); // 3


原型方法

class Example {
sum(a, b) {
console.log(a + b);
}
}
let exam = new Example();
exam.sum(1, 2); // 3


实例方法

class Example {
constructor() {
this.sum = (a, b) => {
console.log(a + b);
}
}
}


## 十二、模块

ES6 模块自动开启严格模式,即使没加 `use strict;`。

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

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

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

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

- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。 
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。

/—–export [test.js]—–/
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!


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

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

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

**export default 命令:**

- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。

## 十三、Promise 对象

是异步编程的一种解决方案。比传统的解决方案(回调函数和事件)更强大合理。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

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

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

**状态的缺点**

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

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

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

**then 方法**

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

**then 方法的特点**

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

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

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

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));`

reject是用来抛出异常,catch是用来处理异常;

reject是Promise的方法,而catch是Promise实例的方法;

reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch;

网络异常(比如断网),会直接进入catch而不会进入then的第二个回调。

**手写 promise:**

> promise 的三种状态:pending、fulfilled、rejected。

```javascript
class Promise {
  constructor(executor) {
    // Promise接一个方法  (resolve,reject)=>{}
    // 开始的状态
    this.state = 'pending'
    // 成功的状态
    this.value = undefined
    // 失败的状态
    this.result = undefined
    // 状态执行的时候,要立即改变,且不可逆转
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
      }
    }
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.result = reason
      }
    }
    executor(resolve, reject) //这个方法要立即执行
  }
  // then接受两个参数,都是方法
  then(onFulfilled, onRejected) {
    // 如果这样写是同步的,this.state一直是padding的状态
    console.log(this.state)
    // 如果成功,传入成功的参数
    if (this.state === 'fulfilled') {
      onFulfilled(this.value)
    }
    // 如果失败,传入失败的参数
    if (this.state === 'rejected') {
      onRejected(this.result)
    }
  }

}

上面是同步的,执行 then 方法后,this.state 一直是 pending 的状态。

发布订阅者模式。

module.exports = class Promise {
  constructor(executor) {
    // Promise接一个方法  (resolve,reject)=>{}
    // 开始的状态
    this.state = 'pending'
    // 成功的状态
    this.value = undefined
    // 失败的状态
    this.result = undefined
    // 成功的订阅者数组
    this.onResolvedCallbacks = []
    // 失败的订阅者数组
    this.onRejectedCallbacks = []
    // 状态执行的时候,要立即改变,且不可逆转
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        // 执行resolve的时候,把 this.onResolvedCallbacks里面的方法全部执行一遍
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.result = reason
        // 执行reject的时候,把 this.onRejectedCallbacks里面的方法全部执行一遍
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    executor(resolve, reject) //这个方法要立即执行
  }
  // then接受两个参数,都是方法
  then(onFulfilled, onRejected) {
    // 如果这样写是同步的,this.state一直是padding的状态
    // 如果成功,传入成功的参数
    if (this.state === 'fulfilled') {
      onFulfilled(this.value)
    }
    // 如果失败,传入失败的参数
    if (this.state === 'rejected') {
      onRejected(this.result)
    }

    // 发布订阅者模式
    // 如果状态是   pending
    // 就把要做的方法传入 待发布状态中去
    if (this.state === 'padding') {
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.result)
      })
    }
  }

}

但是上面没有链式调用。then() 方法后面不能接 then()。

解决 then() 方法不返回 promise 的问题。

class Promise {
  constructor(executor) {
    // Promise接一个方法  (resolve,reject)=>{}
    // 开始的状态
    this.state = 'pending'
    // 成功的状态
    this.value = undefined
    // 失败的状态
    this.result = undefined
    // 成功的订阅者数组
    this.onResolvedCallbacks = []
    // 失败的订阅者数组
    this.onRejectedCallbacks = []
    // 状态执行的时候,要立即改变,且不可逆转
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        // 执行resolve的时候,把 this.onResolvedCallbacks里面的方法全部执行一遍
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.result = reason
        // 执行reject的时候,把 this.onRejectedCallbacks里面的方法全部执行一遍
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    executor(resolve, reject) //这个方法要立即执行
  }
  // then接受两个参数,都是方法
  then(onFulfilled, onRejected) {
    // 如果这样写是同步的,this.state一直是padding的状态
    // 如果成功,传入成功的参数
    const promise2 = new Promise((resolve, reject) => {
      // 解决不能链式调用的问题。需要再返回一个promise
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          const x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        }, 0)

      }
      // 如果失败,传入失败的参数
      if (this.state === 'rejected') {
        setTimeout(() => {
          const x = onRejected(this.result)
          resolvePromise(promise2, x, resolve, reject)
        }, 0)
      }

      // 发布订阅者模式
      // 如果状态是   padding
      // 就把要做的方法传入 待发布状态中去
      if (this.state === 'padding') {
        this.onResolvedCallbacks.push(() => {

          setTimeout(() => {
            const x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          }, 0)
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            const x = onRejected(this.result)
            resolvePromise(promise2, x, resolve, reject)
          }, 0)
        })
      }
    })
    return promise2
  }
}

// 定义resolvePromise 方法
const resolvePromise = (promise2, x, resolve, reject) => {
  // console.log(promise2, x, resolve, reject)
  if (promise2 === x) {
    console.log(11111)
    return reject(new TypeError('循环引用!'))
  }
  // 判断 x 的类型
  if (typeof x === 'function' || (typeof x === 'object' && x !== null)) {
    // 复杂类型
    try {
      const then = x.then
      if (typeof then === 'function') {
        // 认为这个确实是一个promise
        then.call(x, y => {
          // 只能解决一次,如果resolve里面嵌套n个promise
          // resolve(y)
          // 递归解析当前的x
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          reject(r)
        })
      }
    } catch (error) {
      reject(error)
    }
  } else {
    // 基本类型
    resolve(x)
  }
}

十四、Generator 函数

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

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

  • 一是在 function 后面,函数名之前有个 * 。
  • 函数内部有 yield 表达式。

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

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

调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。

十五、async 函数

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联。通过编写类似同步的代码来处理异步流程,提高代码的简洁性和可读性。

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

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

async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})

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

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

function testAwait(){
   return new Promise((resolve) => {
       setTimeout(function(){
          console.log("testAwait");
          resolve();
       }, 1000);
   });
}
 
async function helloAsync(){
   await testAwait();
   console.log("helloAsync");
 }
helloAsync();
// testAwait
// helloAsync

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

[return_value] = await expression;

expression: 一个 Promise 对象或者任何要等待的值。

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

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

function testAwait (x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}
 
async function helloAsync() {
  var x = await testAwait ("hello world");
  console.log(x); 
}
helloAsync ();
// hello world

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

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

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

Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
非 Promise 对象:直接返回对应的值。

async较Generator的优势:

 (1)内置执行器: Generator函数的执行必须依靠执行器,而 Aysnc函数自带执行器,调用方式跟普通函数的调用一样

 (2)更好的语义: async和await相较于*和yield更加语义化  

 (3)更广的适用性: yield命令后面只能是Thunk函数或Promise对象,async函数的await后面可以是Promise也可以是原始类型的值

​ (4)返回值是 Promise: async函数返回的是Promise对象,比Generator函数返回的Iterator对象方便,可以直接使用then()方法进行调用


 上一篇
Webpack 初步了解 Webpack 初步了解
Webpack 是一种前端资源构建工具,是一个现代 JavaScript 应用程序的静态模块打包器,包含多种功能(代码转换、文件优化、代码分割、模块合并、自动刷新、代码校验、自动发布)。本文主要对 Webpack 进行初步的学习了解。
2022-08-06
下一篇 
多维数组转一维数组 多维数组转一维数组
前端在处理后端传来的数据时经常会遇到需要将嵌套的多维数组转换成一维然后在界面中呈现的情况,相应的解决方法非常之多,本文对使用 JavaScript 将多维数组转为一维数组的多种方法进行了一个大致的汇总。
2022-06-03
  目录