# ECMAScript6

也就是ECMAScript2015

## 变量声明`let`

### let变量不可重复声明

```javascript
let a = 1
let a = 2 // Duplicate declaration 
```

### 块级作用域

```javascript
// 在ES6之前，JS只有全局作用域和函数作用域
{
    var a = 'a变量';
}
console.log(a); // 可以正常输出


// ES6加入了块级作用域，如果想声明一个只在块作用域中有效的变量，需要使用let关键字
{
    let b = "变量b";
}
//console.log(b); // ReferenceError: b is not defined，let定义的变量的作用域位于块级作用域中
```

### 不存在变量提升

```javascript
// 使用var定义变量，存在变量提升
// 以下代码不会报错
console.log(a)
var a = 100

// ES6 的let不存在变量提升
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 100
```

## 常量声明 `const`

值不能被修改的量称为常量

### 不可被修改

```javascript
// 同样，在ES6之前，也没有常量（恒量）
const c = '变量c';
//c = '变量cc';
//console.log(c); // TypeError: Assignment to constant variable.
```

### 对数组和对象元素的修改，不算对常量的修改

```javascript
const d = [];
d.push("路飞"); // 正确
d.push("索隆"); // 正确
d.push("山治"); // 正确
// d = []; // 不正确
```

#### const变量不可重复声明

```javascript
const c = "变量c";
//const c = '新的变量c'; // SyntaxError: Identifier 'c' has already been declared  不能重复声明同一个值
```

### 在声明时就要被初始化

```javascript
const c; // SyntaxError: Missing initializer in const declaration
c = 100;
```

### 约定：常量一般使用大写

```javascript
const AMOUNT = 100;
```

### 块级作用于

同let一致

## Destructuring 变量解构赋值

### 数组解构

```javascript
// 数组的解构
function breakfast() {
    return ["🥛", "🍞", "🥓"];
}

// ES5 将breakfast中的元素一一赋值给一个变量
var temp = breakfast();
var milk = temp[0];
var bread = temp[1];
var bacon = temp[2];
console.log(milk, bread, bacon);

// ES6可以使用
let [milkN, breadN, baconN] = breakfast();
console.log(milk, bread, bacon);

// 忽略某些解构值
let [, , baconN] = breakfast();
```

### 对象解构

```javascript
// 对象的解构
function sports() {
    return {football: '⚽', basketball: '🏀', pingPong: '🏓'};
}

// 将sports.football 赋值给变量footballN ...
let {football: footballN, basketball: basketballN, pingPong: pingPongN} = sports();
console.log(footballN, basketballN, pingPongN);

// 对象连续解构
let person = {
    name: "张三",
    age: 12,
    hobby: {
        name: "打篮球",
        start: new Date()
    }
}

let {hobby: {name}} = person
console.log(name); // 打篮球
console.log(hobby); // undefined

// 连续解构赋值且重命名
let {hobby: {name: myname}} = person
console.log(myname)
```

### 函数解构参数

```javascript
// 函数的解构参数
// 给与解构参数默认值，否则不传会报错
function study(name, time, {location, classes} = {}) {
    console.log(name, time, location, classes); // 解构参数，可以直接获取对象中的值
}

study('李四', '明天'); // 李四 明天 undefined undefined
study('张三', '后天', {location: 'home', classes: 'Math'}); // 张三 后天 home Math
```

## 字符串 String

### 字符串包含

```javascript
let str = '我是字符串A';
console.log(str.startsWith('我')); // true
console.log(str.endsWith('A')); // true
console.log(str.includes('字符')); // true
```

### 模板字符串

#### 可有换行，会替换变量

```javascript
// 模板字符串
// ES6提供了模板字符串

let dessert = '🍞'
drink = '🍺';
let breakfastMsg = `今天的早餐是${dessert},
    并且喝个${drink}`; // 可以换行
console.log(breakfastMsg);
```

#### 带标签的模板字符串

```javascript
// Tagged Template 带标签的模板
let money = 20;
let dessert = '🍞'
drink = '🍺';
let breakfastMsg2 = kitchen`今天的早餐是${dessert},并且喝个${drink}, 总共花费了${money}`;

// 这里kitchen函数是用于生成模板字符串的函数
// strings+values就是目标字符串，values是插值
// 通常用于字符串预处理，比如传入了一个html标签，去除标签样式，让其变为纯文本
function kitchen(strings,
                 ...
                     values
) {
    // console.log(strings); // [ '今天的早餐是', ',\n并且喝个', '' ]
    // console.log(values);  //[ '🍞', '🍺' ]
    let result = '';
    result += strings[0];
    result += values[0];
    result += strings[1];
    result += "一杯" + values[1];
    result += strings[2];
    result += values[2] + '元';
    return result;
}

console.log(breakfastMsg2); // 今天的早餐是🍞,并且喝个一杯🍺, 总共花费了20元
```

## 对象

### 对象简化写法：属性赋值简化

```javascript
let name = "北京大学";
let change = function () {
    console.log("我们可以改变你");
}

// ES5，定义一个school对象
let school = {
    name: name,
    change: change,
}

// ES6，如果要赋值的变量与对象的key相同，可以省略
let school2 = {
    name,
    change
}

// 以上代码作用相同
```

### 对象简化写法：方法声明简化

```javascript
let school = {
    name: "北京大学",
    change: function() {
      console.log("我们可以改变你");
    },
}

let school2 = {
  name: "北京大学",
  change() {
    console.log("我们可以改变你");
  }
}

// 以上两种写法等价
```

### 对象属性值的赋值

```javascript
obj.height = 175;
obj['love girl'] = '莉莉';
```

### Object对象

#### `Object.is` 判断值是否相同

这里包括了数据类型的判断，类似于 `===`，但是`===`有些问题

```javascript
// ES5中的==和===存在着问题，==会自动转换数据类型；===情况下NaN不等于NaN，+0等于-0
// ES6中的Object.is() 判断两个值是否相同。即只要两个值是一样的，它们就应该相等
Object.is(NaN, NaN)     //true
Object.is(+0, -0)       //false
```

#### `Object.assign` 属性复制

```javascript
// 将primary对象中的所有属性复制到test1中
var test1 = {test: 'test'}; // 必须初始化，不可以是undefined或者null
var primary = {name: '李四'};
Object.(test1, primary);
console.log(test1); // { test: 'test', name: '李四' }
test1.name = "张三";
console.log(test1, primary); // { test: 'test', name: '张三' } { name: '李四' } // 原来的值不受影响
```

#### `Object.setPrototypeOf` 更改对象原型

```javascript
// Object.setPrototypeOf
// 对象在创建后，支持使用此方法改变对象的PrototypeOf

let p1 = {
    get() {
        return 'A';
    }
}
let p2 = {
    get() {
        return 'B';
    }
}
// 把p1作为原型创建对象
let p3 = Object.create(p1);
// 或者使用__proto__指定原型
let p4 = {
    __proto__: p1
}
console.log(p1.get()); // A
console.log(Object.getPrototypeOf(p3) == p1); // true
// 更换原型
Object.setPrototypeOf(p3, p2);
// 或者
// p3.__proto__ = p2;
console.log(p3.get()); // B
console.log(Object.getPrototypeOf(p3) == p2); // true
console.log(Object.getPrototypeOf(p4) == p1); // true
```

## 函数

### 获取函数名称

```javascript
// 获取函数的名称
function test1() {
}

console.log(test1.name); // test1

let test2 = function () {
}
console.log(test2.name); // test2

let test3 = function test4() {
}
console.log(test3.name); // test4
```

### 箭头函数 Arrow Function

类似Java的lambda表达式

```javascript
// Arraw Function 箭头函数

// 单个参数，直接返回参数的函数
let demo1 = param1 =>
param1;
/*
function demo1(param1) {
    return param1;
}
*/

// 多个参数，返回简单进行参数处理的函数
let demo2 = (param1, param2) =>
param1 + param2;
/*
function demo2(param1, param2) {
    return param1 + param2;
}
*/

// 多个参数，返回复杂的参数处理函数
let demo3 = (param1, param2) =>
{
    return param1 + param2;
}
/*
function demo2(param1, param2) {
    return param1 + param2;
}
*/
```

### 函数形参初始值

```javascript
// ES 可以给函数参数设置默认值

function breakfast(food = '🍞', drink = '🥛') {
    console.log(`我今天吃的 ${food}, 喝的 ${drink}`)
}

breakfast();
```

### rest参数(可变参数)

```javascript
// ES5 获取实参的方式
function date(){
    console.log(arguments);
}

date("a", "b", "c"); // [Arguments] { '0': 'a', '1': 'b', '2': 'c' } // 是一个Arguments对象

// ES6 rest参数
// 就是可变参数
function date2(a, ... args) {
    console.log(args);
}

date2("a", "b", "c"); // ['b', 'c' ] // 是一个参数数组
```

## `Spread`扩展运算符

```javascript
let fruits = ['🍎', '🍌', '🍋'],
    foods = ['🥪', '🍞',...fruits]; // 将fruits展开到foods

console.log(fruits);
console.log(...fruits); // 展开操作符
console.log(foods);  // 将fruits加入到foods中


// 应用在函数传参：
function test() {
    console.log(arguments);
}
let arr = ["a", "b", "c"];
test(...arr); // [Arguments] { '0': 'a', '1': 'b', '2': 'c' }
```

## 新数据类型：`symbol`

`symbol`代表独一无二的值。

### 函数`Symbol()`创建`symbol`

```javascript
// 创建Symbol
let s1 = Symbol();
console.log(s1, typeof s1); //Symbol() symbol
```

### 根据字符串创建`symbol`

```javascript
// 可根据字符串创建symbol，字符串类似一个symbol的备注
let s2 = Symbol("study");
let s3 = Symbol("study");

// 通过Symbol方法创建的任何symbol，比较都为false
console.log(s2 == s3) // false
```

### `Symbol.for`创建

```javascript
// 但是可以使用symbol.for方法创建相同的symbol
let s4 = Symbol.for("study");
let s5 = Symbol.for("study");
console.log(s4 === s5); // true
```

### 不可与任何数据类型运算

```javascript
// Symbol数据类型不能与其他任何数据类型进行运算
let result = s1 + 100; // 错误
let result = s1 > 100; // 错误
let result = s1 + s2; // 错误
```

### 使用场景：用作唯一方法/属性名

```javascript
let game = {
    name: "俄罗斯方块",
    up() {
    },
    down() {
    }
}

// 向game添加两个方法，是methods的up和down
game.up = function () {
}
game.down = function () {
}

// 上述做法，在复杂的对象中有可能出现方法被覆盖等问题
// 所以需要借助symbol来添加唯一方法

let methods = {
    up: Symbol(),
    down: Symbol(),
};

// 这样game新增的up和down方法key是symbol类型，不会发生冲突了
game[methods.up] = function () {
    console.log("symbol 向上");
}
game[methods.down] = function () {
    console.log("symbol 向下");
}

// 调用方法
game[methods.up]();
game[methods.down]();
```

或者使用如下方式：

```javascript
let youxi = {
    name: "俄罗斯方块",
    [Symbol.for("up")]: function () {
        console.log("向上");
    },
    [Symbol.for("down")]: function () {
        console.log("向下");
    }
}

youxi[Symbol.for("up")]();
youxi[Symbol.for("down")]();
```

### `Symbol`对象的内置值

`Symbol`对象有一些内置值，代表对象的一些内置方法，比如在调用`obj instanceof Person`时，会去调用类型内部的`static [Symbol.hasInstance](param) {}`方法去判断对象是否属于某一类型。

我们可以通过`Symbol`的内置值，对类型的一些功能进行重写。

参考：[Symbol - JavaScript | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol)

#### `Symbol.hasInstance` 控制类型检测

`instanceof`对象类型判断重写：

```javascript
class Person {
    static [Symbol.hasInstance](param) {
        console.log(`Person 与 ${param} 类型进行从属判断`);
        return true; // true表示属于该类型
    }
}

console.log("hello" instanceof Person);
// 输出结果
// Person 与 hello 类型进行从属判断
// true

```

#### `Symbol.isConcatSpreadable`是否可以被展开

```javascript
const arr1 = ["a", "b", "c"];
const arr2 = ["d", "e", "f"];
console.log(arr1.concat(arr2)); // [ 'a', 'b', 'c', 'd', 'e', 'f' ]

const arr3 = ["a", "b", "c"];
const arr4 = ["d", "e", "f"];
arr4[Symbol.isConcatSpreadable] = false; // 数组是否可以被展开，如果为false，说明数组是一个整体
console.log(arr3.concat(arr4));
// 结果
// [
//   'a',
//   'b',
//   'c',
//   [ 'd', 'e', 'f', [Symbol(Symbol.isConcatSpreadable)]: false ]
// ]
```

## 迭代器

迭代器是一种接口，为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口，就可以完成遍历操作。下面是原生带有`Iterator`接口的数据：

* `Array`
* `Arguments`
* `Set`
* `Map`
* `String`
* `TypedArray`
* `NodeList`

### Iterator接口

```javascript
// 生成迭代器

var foods = ['🍅', '🍚'];

function* chef() {
    for (var i = 0; i < foods.length; i++) {
        yield foods[i];
    }
}

let lisi = chef();
console.log(lisi.next());
console.log(lisi.next());
console.log(lisi.next());
```

### for of循环

```javascript
const ps = ["毛利兰", "江户川柯南", "灰原哀", "赤井秀一"];
for (const p of ps) {
    console.log(p); // 毛利兰 ... 
}
```

### 自定义类型支持Iterator

```javascript
const banji = {
    name: "一年级一班",
    students: [
        '张三',
        '李四',
        '王五',
        '赵六',
    ],
    [Symbol.iterator]() {
        let _this = this;
        let index = 0;
        return {
            next: function () {
                if (index < _this.students.length) {
                    return {value: _this.students[index++], done: false};
                } else {
                    return {value: undefined, done: true};
                }
            }
        }
    }
}

// 使用for of循环遍历
for (const bj of banji) {
    console.log(bj);
}
```

## 生成器

### 声明和调用

```javascript
// 1: 生成器是一个函数
// 2: 生成器返回迭代器
// 3: function关键字后面紧挨着或者留一个空格后，必须有一个星号（*）
// 4: 函数体里面会用到关键字yield，来依次返回想要迭代的值

function* createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

let iterator = createIterator();
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: 3, done: false}
console.log(iterator.next());//{ value: undefined, done: true }
```

### 参数传递

`yield`关键字可以接收`next`的返回值，`yield`将会在next方法调用之前阻塞。

```javascript
// 使用 * 定义一个生成器函数
function * gen(args) {
    console.log(`gen整体传参：${args}`);
    console.log("第一部分");

    let oneParam = yield "两只老虎";
    console.log(oneParam); // BBB
    console.log("第二部分");
    let twoParam = yield "跑的快";
    console.log(twoParam); // CCC

    console.log("第三部分");
    let threeParam = yield "一直没有眼睛";
    console.log(threeParam); // DDD
}

let g = gen("hello");
g.next("AAA");
g.next("BBB");
g.next("CCC");
g.next("DDD");
```

上面代码的输出结果为：

```
gen整体传参：hello
第一部分
BBB
第二部分
CCC
第三部分
DDD
```

### 实例：解决回调地狱

```javascript
// 生成器是新的异步解决方案
// 1s 后输出 1
// 2s 后输出 2
// 3s 后输出 3

// ES5的代码，会出现回调地狱
setTimeout(function () {
    console.log(1);
    setTimeout(function () {
        console.log(2);
        setTimeout(function () {
            console.log(3);
        }, 3 * 1000);
    }, 2 * 1000);
}, 1000);

// 使用ES6，生成器代码
// 首先定义三个任务
function one() {
    setTimeout(() => console.log(1), 1000);
}

function two() {
    setTimeout(() => console.log(2), 2 * 1000);
}

function three() {
    setTimeout(() => console.log(3), 3 * 1000);
}

// 然后使用生成器一次调用
function* gen() {
    yield one();
    yield two();
    yield three();
}

let g = gen();
g.next()
g.next()
g.next()
```

### 实例2：实际场景模拟

假设有如下场景，需要

1. 获取当前登录用户信息
2. 根据用户id查询订单列表
3. 取出订单列表第一条订单购买的第一个商品id，并请求查询商品详情

```javascript

// 获取当前登录的用户信息
function getUser(userid) {
    // 使用setTimeout模拟http接口的请求
    let user;
    setTimeout(() => {
        console.log("获取登录用户数据成功！http status : ok");
        user = {
            userid: userid,
            name: "宫野明美",
        }
        iterator.next(user);
    }, 1000);
    // 将值传给下一个iterator
    return user;
}

// 获取用户的订单列表信息
function getOrders(userid) {
    let orders;
    setTimeout(() => {
        console.log(`获取用户${userid}的订单列表成功！http status : ok`);
        orders = [
            {orderid: 102, totalAmount: 100.02, time: new Date(), goodsId: [901, 903, 878]},
            {orderid: 103, totalAmount: 20.4, time: new Date(), goodsId: [999, 675]},
        ];
        iterator.next(orders);
    }, 1000);
    return orders;
}

// 获取某个指定产品的信息
function getGoods(goodsId) {
    let goods;
    setTimeout(() => {
        console.log(`请求${goodsId}商品详细信息成功`);
        goods = {goodsid: goodsId, goodsName: "洗衣粉"};
        iterator.next(goods);
    }, 1000);
    return goods;
}

function * getUserFirstOrderFirstGoods(userid) {
    // 获取登录用户
    let user = yield getUser(userid);

    // 获取订单数据
    let orders = yield getOrders(user.userid);

    // 从订单取出第一个商品id再查询商品详情
    let goods = yield getGoods(orders[0].goodsId[0]);

    // 获取产品数据成功
    console.log(goods);
}

// iterator 是一个全局变量
let iterator = getUserFirstOrderFirstGoods();
iterator.next();
```

响应结果：

```
获取登录用户数据成功！http status : ok
获取用户undefined的订单列表成功！http status : ok
请求901商品详细信息成功
{ goodsid: 901, goodsName: '洗衣粉' }
```

## `await`和`sync`

`await`是生成器的语法糖：

```javascript
var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
```

上面的代码与下面是等价的：

```javascript
var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
```

## Promise

Promise是JS中进行异步编程的新解决方案，Promise可以封装一个异步操作，并获取成功/失败的结果值。通过Promise构造函数，创建一个Promise：

```javascript
const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
```

Promise中含有两个重要的属性，`PromiseState`和`PromiseResult`。当Promise执行异步任务时

* 如果经过一段时间异步任务执行成功，需要异步任务调用`resolve(value)`，此时
  * `PromiseState`将会从`pending`变为`resolved`
  * `PromiseResult`的值为`value`，一般是成功的值
* 如果经过一段时间异步任务执行失败，需要异步任务调用`reject(err)`或者直接抛出异常`throw err`，此时
  * `PromiseState`将会从`pending`变为`rejected`
  * `PromiseResult`的值为`err`，一般是失败的原因

此外，promise还提供了then以及catch方法，当状态发生变化时，会调用then传入的回调：

* then方法的调用需要传入两个回调函数（可以只传一个回调函数）

  ```javascript
  let p = promise.then(function (value) { // 状态变为resolved的时候的回调，参数为异步任务调用resolve方法的参数
    
  }, function (reason) { // 状态变为reject的回调， 参数为异步任务调用reject方法的参数
    
  });
  // 并且then方法的返回值是一个新的promise对象
  ```
* 有些异步任务仅仅需要处理失败的情况，Promise也提供了catch方法，单独用作失败回调

  ```javascript
  promise.catch(function(reason){
    
  }); 
  ```
* 可以指定多个then，这些then指定的回调都会被执行：

  ```javascript
  promise.then(function (value) {
    console.log("1");
  }).then(function (value) {
    console.log("2");
  });
  // 结果： 
  // 1
  // 2 
  ```
* then方法返回的promise对象是由如下三种情况决定的
  * 如果then的函数参数在处理中抛出异常，then返回状态为resolved的新的promise

    ```javascript
    let promise = new Promise(function (resolve, reject) {
      setTimeout(() => {
        resolve("success");
      }, 2000)
    });

    let p = promise.then(function (value) {
      throw "出错了";
    });

    console.log(p === promise); // false，说明是新的promise
    console.log(p); // Promise {<rejected>}
    ```
  * 如果then的函数参数返回的值是非promise的任意值，那么then返回状态为resolved的新promise对象

    ```javascript
    let promise = new Promise(function (resolve, reject) {
      setTimeout(() => {
        resolve("success");
      }, 2000)
    });

    let p = promise.then(function (value) {
      console.log(value);
    });

    console.log(p === promise); // false，说明是新的promise
    console.log(p); // Promise {<pending>}
    ```
  * 如果then的函数参数返回的值是一个新的promise对象，那么then返回这个新的promise对象（这种方式可以通过链式调用完成嵌套操作）

    ```javascript
    let promise = new Promise(function (resolve, reject) {
      setTimeout(() => {
        resolve("success");
      }, 2000)
    });

    promise.then(function (value) {
      console.log(value); // success
      return new Promise(function (resolve, reject) {
        resolve("1");
      });
    }).then(value => {
      console.log(value); // 1
    });
    ```

### 实例：文件读取

```javascript
const fs = require("fs");

const p = new Promise(function (resolve, reject) {
    fs.readFile("./a.txt", function (err, data) {
        if (err) {
            reject(err); // 处理失败调用reject函数，将会把Promise对象置为失败状态
        } else {
            resolve(data); // 处理成功调用resolve函数，将数据传输过去，并会将Promise对象置为成功状态
        }
    });
});

// 使用then方法，指定promise对象成功或者失败后的处理方式，也就是resolve和reject函数
p.then(function (value) {
    console.log("处理成功， 值为 ", value.toString());
}, function (reason) {
    console.log("处理失败， 错误为 ", reason);
});
```

### 实例：封装AJAX

```javascript
const xmlhttprequest = require("xmlhttprequest")
let p = new Promise(function (resolve, reject) {
    let xhr = new xmlhttprequest.XMLHttpRequest();
    xhr.open("GET", "https://baidu.com");
    xhr.send();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) { // 进入最后一个阶段，响应体已经返回
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.responseText);
            } else {
                reject(xhr.status);
            }
        }
    };
});

p.then(function (response) {
    console.log(response)
}, function (status) {
    console.error(status);
});
```

## Set

```javascript
// 无序不可重复集合 Set
// 注意构造函数Set()的参数传入的是iterable接口，
// string本身也是一个iterable接口，所以下面的元素数量是5
let food = new Set('🍎🍌🍅🥓🥔');
console.log(food.size); // 5
// 添加元素
food.add('🍉');
console.log(food, food.size); // 不相同元素会添加
food.add('🍎');
console.log(food, food.size); // 相同元素不会添加 6

// 删除元素
food.delete('🍉');
console.log(food, food.size); // 删除西瓜，5

// 判断元素是否存在于集合中
console.log(food.has('🍉')); // false

// 遍历元素
food.forEach(f = > {
    console.log(f);
})

// 清空set
food.clear();
console.log(food, food.size); // 0
```

## Map

```javascript
// js对象默认就是一个map,为什么还需要map？ 
// 因为js对象的key只能是string类型，不可以是复杂的对象

// 新建Map
let food = new Map();
let fruit = {}, cook = function () {
}, dessert = '甜点';

// 向map中添加元素
food.set(fruit, '🍋');
food.set(cook, '🔪');

// 获取map的长度
console.log(food.size);

// 获取指定元素
console.log(food.get(fruit));

// 删除某个元素
food.delete(fruit);

// 判断元素是否存在
console.log(food.has(fruit));

// 遍历map
console.log("-------");
food.forEach((key, value) => {
    console.log(key, value);
})
;

// 清空map
food.clear();
```

## 面向对象

ES6 的面向对象就是 ES5 的语法糖，这些功能都可以在 ES5 中实现。

### class

```javascript
class Chef {
    constructor(food) {
        this.food = food; // 声明属性
        this.dish = [];
    }

    cook() {
        console.log(this.food);
    }

    // getter and setter
    get menu() {
        return this.dish;
    }

    set menu(dish) {
        this.dish.push(dish);
    }

    // static method 不需要创建对象就可以调用
    static cook(food) {
        console.log('我炒了' + food)
    }
}
let lisi = new Chef('🍅');
lisi.menu = '🍕';
lisi.menu = '🍞';
lisi.cook();
console.log(lisi.menu);

// 调用静态方法
Chef.cook('🍅');
```

### 继承

```javascript
// 类也可以继承
class Person {
    constructor(name, birthday) {
        // 定义属性
        this.name = name;
        this.birthday = birthday;
    }

    info() {
        return `姓名：${this.name}, 出生日期: ${this.birthday}`;
    }
}

// Person的子类
class Student extends Person {
    constructor(name, birthday) {
        super(name, birthday);
    }

    // 重写方法
    info() {
        return super.info() + " 是一个学生";
    }
}

let zhangsan = new Student('张三', '1995年9月2日');
console.log(zhangsan.info());
```

### 封装

```javascript
class Phone {
  get price() {
    return "haha";
  }
  
  set price(num) {
    console.log("haha")；
  }
}

// 调用set方法
let p = new Phone();
console.log(p.price);
// 调用get方法
p.price = 100;
```

## 模块化

在ES5的时候，第三方提供的模块化规范有：

| 模块化规范      | 实现                    |
| ---------- | --------------------- |
| `CommonJS` | `NodeJS`，`Browserify` |
| `AMD`      | `requireJS`           |
| `CMD`      | `seaJS`               |

### `export`和`import`

创建模块`m1.js`：

```javascript
export let name = "张三";
export function hello() {
    console.log("hello");
}
```

创建模块`m2.js`，并引用`m1`导出的方法和变量：

```javascript
import * as m1 from './m1';

console.log(m1.name);
m1.hello();
```

### 统一暴露

```javascript
let name = "张三";
function hello() {
    console.log("hello");
}
export {name, hello};
```

### 默认暴露

```javascript
export default {
  name : "张三",
  function hello() {
    console.log("hello");
  }
}
```

### 通用别名导入方式

```javascript
import * as m1 from "./m1.js";
```

### 解构赋值形式

```javascript
import {name, hello} from "./m1.js";
// 下面就不用使用别名了
hello();
```

### 结构部分别名

```javascript
// 如果不同模块有同名方法，可以使用as别名
import {name, hello as hello1} from "./m1.js";
hello1();
```

### 导入默认暴露的模块

```javascript
import {default as m1} from "./m1.js";
```

### 导入的简便模式:针对默认暴露

```javascript
import m1 from "./m1.js";
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/javascript/ecmascript/ecmascript6.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
