十一.一篇文章搞定ES6

目录

一.let 和 const 命令

  • let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。
  • const 声明的就是常量,保证指针是固定的;
    • 1.使用 const 声明常量,一旦声明,就必须立即初始化,不能留到以后赋值
    • 2.const 声明的常量,允许在不重新赋值的情况下修改它的值,所以基本数据类型是不能修改,但是引用数据类型是可以修改的。
    • 3.不知道用 let 和 const 优先用 const。
    • 详细解读const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
const person = { username: 'Alex' };
  // person = {};报错
  person.username = 'ZhangSan';//不报错 
 console.log(person);
 

let、const 与 var 的区别:

1.重复声明

  • var 允许重复声明,let、const 不允许

2.变量提升

  • var 会提升变量的声明到当前作用域的顶部
  • let、const 不存在变量提升

3.暂时性死区

  • 只要作用域内存在 let、const,它们所声明的变量或常量就自动“绑定”这个区域,不再受到外部作用域的影响
  • let、const 存在暂时性死区
var name='jack';
{
 name='bob';
 let name;    //Uncaught ReferenceError: Cannot access 'name' before initialization
}
 
  • ES6 中,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。因为JS清楚地感知到了 name 是用 let 声明在当前这个代码块内的,所以会给这个变量 name 加上了暂时性死区的限制,它就不往外探出头了。
  • 那么,如果我们把上面的let name;去掉,程序也将正常运行, name 的值也被成功修改为了bob,就是正常地按照作用域链的规则,向外探出头去了。

4.window 对象的属性和方法:

全局作用域中,var 声明的变量,以及通过 function 声明的函数,会自动变成 window 对象的属性或方法,let、const 不会。

5.块级作用域

var 没有块级作用域,let/const 有块级作用域。

{}
for(){}
while(){}
do{}while()
if(){}
switch(){}
function(){}//函数只有在执行时才生成函数作用域
const person = {
        getAge: function () {}
     };//对象没有块级作用域
 

小题:点击哪个按钮就显示当前索引

方案一:行不通

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>let 和 const 的应用</title>
    <style>
      body {
        padding: 50px 0 0 150px;
      }

      .btn {
        width: 100px;
        height: 100px;
        margin-right: 20px;
        font-size: 80px;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <button class="btn">0</button>
    <button class="btn">1</button>
    <button class="btn">2</button>

    <script>
      var btns = document.querySelectorAll(".btn");
      // 方案一:不行(var)

      for (var i = 0; i < btns.length; i++) {
        /*
       首先: 进来就一轮循环
        i=0,按钮0绑定事件
        i=1,按钮1绑定事件
        i=2,按纽2绑定事件
        i=3,不绑定了
        所以,i=3了
        然后:点击任何一个按钮都会打印3
        */
        btns[i].addEventListener(
          "click",
          function () {
            console.log(i); //3
          },
          false
        );
      }
    </script>
  </body>
</html>
 

方案二:可行(立即执行)

var btns = document.querySelectorAll(".btn");

for (var i = 0; i < btns.length; i++) {
  (function (index) {
    btns[index].addEventListener(
      "click",
      function () {
        console.log(index);
      },
      false
    );
  })(i);
}
/*
for循环遍历的过程中,立即执行函数会形成三个作用域,同时会将i=0,1,2分别传入到函数作用域中,
以index=0,1,2的形式存入,当用户点击按钮时,就会打印相应的数字。
*/
 

方案三:可行(let)

let btns = document.querySelectorAll(".btn");

for (let i = 0; i < btns.length; i++) {
  btns[i].addEventListener(
    "click",
    function () {
      console.log(i);
    },
    false
  );
}
/*
1.循环遍历会创建三个块级作用域,
2.点击事件会在当前的块级作用域内创建函数作用域,同时触发函数打印i
*/
 

二.模板字符串

  • 1.模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式
  • 2.模板字符串中,所有的空格、换行或缩进都会被保留在输出之中
  • 3.模板字符串的注入
    只要最终可以得出一个值的就可以通过 ${} 注入到模板字符串中
const username = "alex";
const person = { age: 18, sex: "male" };
const getSex = function (sex) {
  return sex === "male" ? "男" : "女";
};
const info = `${username}, ${person.age + 2}, ${getSex(person.sex)}`;
console.log(info);
 

三.箭头函数

1.箭头函数的结构

      const/let 函数名 = 参数 => 函数体
      const add = () => {};
 

2.箭头函数简化

- 单个参数可以省略圆括号
  const add = x => {};
- 单行函数体可以省略花括号以及 return
  const add = (x, y) => x + y;
- 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
  const add = (x, y) => ( {value: x + y} );
 

3.this 指向

1.全局作用域中的 this 指向
     console.log(this); // window

2.一般函数(非箭头函数)中的 this 指向
      // 'use strict';
      function add() {
        console.log(this); 
        // 严格模式就指向 undefined
        // undefined->window(非严格模式下)
      }
      add();
     
     
3.点击事件的this指向
      document.onclick = function () {
         console.log(this);  //this指向document
       };
   
      // 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
      // this 指向和函数在哪儿调用没关系,只和谁在调用有关
      // 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
      
4.箭头函数中的 this 指向
    箭头函数没有自己的 this,所以会一层一层的往外找,最后找到window,他的this指向window
 

4.不适用箭头函数的场景

  • 1.箭头函数不能作为构造函数
  • 2.需要 this 指向调用对象的时候,不能用箭头函数
      document.onclick = function () {
        console.log(this);
      };
      document.addEventListener(
        'click',
        () => {
          console.log(this); //window
        },
        false
      );
     
  • 3.需要使用 arguments 的时候
    箭头函数中没有 arguments

5.箭头函数的应用(3-11)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>箭头函数的应用</title>
    <style>
      body {
        padding: 50px 0 0 250px;
        font-size: 30px;
      }

      #btn {
        width: 100px;
        height: 100px;
        margin-right: 20px;
        font-size: 30px;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <button id="btn">开始</button>
    <span id="result">0</span>

    <script>
      const btn = document.getElementById("btn");
      const result = document.getElementById("result");
      const timer = {
        time: 0,
        start: function () {
          // this
          btn.addEventListener(
            "click",
            () => {
              // this
              setInterval(() => {
                console.log(this);
                this.time++;
                result.innerHTML = this.time;
              }, 1000);
            },
            false
          );
        },
      };

      timer.start();
    </script>
  </body>
</html>
 

四.解构赋值

解构赋值是指解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或常量;

4.1 数组解构赋值

数组解构赋值非常简单,遵循模式匹配原则就可以了,如果不需要结构的用逗号跳过即可。

   //   1.模式(结构)匹配
       [] = [1, 2, 3];

   //    2.索引值相同的完成赋值
       const [a, b, c] = [1, 2, 3];
       console.log(a, b, c);

  //     3.不取的,可以直接用逗号跳过
      const [a, [, , b], c] = [1, [2, 4, 5], 3];
      console.log(a, b, c);
 

4.1.1 默认值:

  • 1.只有当一个数组成员严格等于(===)undefined 时,对应的默认值才会生效
 const [a = 1, b = 2] = [3, 0];//3 0
 const [a = 1, b = 2] = [3, null]; 3 null
 
  • 2.如果默认值是表达式,默认值表达式是惰性求值的
 const func = () => {
       console.log("我被执行了");
      return 2;
   };
  // const [x = func()] = [1]; // x=1 且不执行函数
 const [x = func()] = []; //执行函数体代码
console.log(x); //2
 

4.1.2 应用

  • 1.常见的类数组的解构赋值
//arguments
const [a, b] = arguments;
console.log(a, b);

//NodeList
console.log(document.querySelectorAll('p'));
 const [p1, p2, p3] = document.querySelectorAll('p');
 console.log(p1, p2, p3);
 
  • 2.函数参数的解构赋值
      let [x, y] = [1, 1];
      const add = ([x = 0, y = 0]) => x + y;
      console.log(add([x, y]));
 
  • 3.交换变量的值
      let x = 1;
      let y = 2;
/*
       之前的做法
       let tmp = x;
       x = y;
       y = tmp;
       console.log(x, y);
*/
       [x, y] = [y, x];
      console.log(x, y);
 

4.2 对象解构赋值

4.2.1 基本结构

1.模式(结构)匹配
       {}={}
       
2.属性名相同的完成赋值
  const { age, username } = { username: 'Alex', age: 18 };
  const { age: age, username: username } = { username: 'Alex', age: 18 };
  
3.可以取别名
      const { age: age, username: uname } = { username: 'Alex', age: 18 };
      console.log(age, uname);//解构赋值之后再赋值给uname
 

4.2.2 默认值

  • 默认值(=)的生效条件
    对象的属性值严格等于 undefined 时,对应的默认值才会生效

        const { age = 16, username: uname } = { username: "Alex" };
        console.log(age, uname);//16 alex
     
  • 2.默认值表达式是惰性求值的

  • 3.将一个已经声明的变量用于解构赋值
    如果将一个已经声明的变量用于对象的解构赋值,整个赋值需在圆括号中进行,防止被当成块级作用域

     let x = 2;
    ({ x } = { x: 1 });
    console.log(x);    //1
     

4.2.3 对象解构赋值的应用

  • 1.函数参数的解构赋值
     const PersonInfo = (user) => console.log(user.username, user.age);
     PersonInfo(({ age, username: username } = { username: "alex", age: 18 })); //alex 18 使用了解构后的值
     
     const person = ({ age = 0, username = "ZhangSan" }) =>console.log(username, age);
     person({}); //ZhangSan 0  使用了默认值
     person({ username: "alex", age: 18 }); //alex 18 使用了解构后的值
 
  • 2.复杂的嵌套
      const obj = {
        x: 1,
        y: [2, 3, 4],
        z: {
          a: 5,
          b: 6,
        },
      };
      const { x, y, z } = obj;
      console.log(x, y, z);//  1   [2,3,4]  {a:5,b:6}
 
      const obj = {
        x: 1,
        y: [2, 3, 4],
        z: {
          a: 5,
          b: 6,
        },
      };
      const {
        y,
        y: [, yy],
        z,
        z: { b },
      } = obj;
      console.log(yy, y, z, b); //3   [2,3,4]  {a:5,b:6} 6
 

4.3 字符串的解构赋值

      // 字符串可以按照数组形式的解构赋值
      const [a, b, , , c] = "hello";
      console.log(a, b, c); //h e o
      
      // 字符串可以按照对象形式的解构赋值(可以按照索引值和length)
      const { 0: d, 1: e, length } = "hello";
      console.log(d, e, length); //h e 5
      console.log("hello".length); //5
 

五.对象字面量增强

5.1 属性简写

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

键名和变量或常量名一样的时候,可以只写一个

   const age = 18;
      const person = {
          // age: age日常写法
      age  //简洁写法
   };
     console.log(person);
 

方法可以省略冒号和 function 关键字

     const person = {
       // speak: function () {}之前写法
      speak() {}//简洁表示法
     };
     console.log(person);
 

5.2.方括号语法增强

1.方括号语法可以写在对象字面量中

      const prop = 'age';
      const person = {
        [prop]: 18
      };
 

2.方括号中可以放什么?

  • ${}
  • [值或通过计算可以得到值的(表达式)]

3.方括号语法和点语法的区别:

 点语法是方括号语法的特殊形式
 const person = {};
 person.age 等价于 person['age']
 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语
 合法标识符可以用来作为变量或常量名
 当你的属性或方法名是合法标识符时,可以使用点语法,其他情况下请使用方括号语法
 

六.函数参数默认值

  • 调用函数的时候传参了,就用传递的参数;如果没传参,或者明确的传递 undefined 作为参数,就用默认值;
  • 接收很多参数的时候,接收一个对象作为参数
const logUser = ({ username = 'zhangsan', age = 0, sex = 'male' } = {}) =>
console.log(username, age, sex);
logUser();
 

七.剩余参数与展开运算符

剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(…),后面跟着一个含有iterator接口的数据结构

7.1 剩余参数

  • 剩余参数语法允许将不确定数量的参数表示为数组。
  • 剩余参数永远是个数组,即使没有值,也是空数组.
  • 剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
  • 剩余参数不一定非要作为函数参数使用
     const add = (x, y, ...args) => {
       console.log(x, y, args);
     };
     add(1, 2, 3, 4, 5); //1 2  [3, 4, 5]
 

7.1.1 箭头函数的剩余参数

对于箭头函数来说,没有 arguments,使用剩余参数替代 arguments 获取实际参数,此时不能省略圆括号;

const add = (...args) => {
console.log(args);
};
add(1, 2);
 

7.1.2 剩余参数的应用

  • 1.完成 add 函数
      const add = (...args) => {
         let sum = 0;

         for (let i = 0; i < args.length; i++) {
           sum += args[i];
         }

       return sum;
       };

       console.log(add(1, 2, 3));
 
  • 2.与解构赋值结合使用
        const [num, ...args] = [1, 2, 3, 4];
        const func = ({ x, y, ...z }) => {};
        func({ a: 3, x: 1, y: 2, b: 4 });
     

7.2 数组展开运算符

将一个数组转为用逗号分隔的参数序列,该运算符主要用于函数调用。
区分剩余参数和展开运算符

根本区别 :

  展开运算符把数组`转化为参数`
  [3,1,2]->3,1,2
  剩余参数把剩余`参数转化为数组`
  3,1,2->[3,1,2]
 
      const arr1 = [4, 5, 6];
      const arr = [1, 2, 3, ...arr1, 7, 8, 9];
      console.log(arr);//[1, 2, 3, 4, 5, 6, 7, 8, 9]
 
console.log(Math.min(...[3, 1, 2]));
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);
 

7.2.1 替代函数的 apply 方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了

// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);
 

7.2.2 数组展开运算符的应用

  • 1.复制数组
       const a = [1, 2];
       const c = [...a];
       a[0] = 3;
       console.log(a);//[3,2]
       console.log(c);//[1,2]
 
  • 2.合并数组(浅拷贝)
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
 
  • 3.字符串转为数组
       console.log([...'alex']);//['a','l','e','x']
       console.log('alex'.split(''));//es6之前
 
  • 4.常见的类数组转化为数组
    4.1 arguments
       function func() {
         console.log([...arguments]);
       }
       func(1, 2);

    4.2 NodeList
      console.log([...document.querySelectorAll('p')]);
 
    1. 实现了 Iterator 接口的对象

任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
 

7.3 对象展开运算符的基本用法

对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

1. 展开对象(复制对象)

  • 对象不能直接展开,必须在 {} 中展开
  • 对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象
      const apple = {
        color: "红色",
        shape: "球形",
        taste: "甜",
      };
      const Ob = { ...apple };
      console.log(Ob);
      console.log(Ob === apple); //false
 

2.合并对象

      const apple = {
        color: '红色',
        shape: '球形',
        taste: '甜'
      };
      const pen = {
        color: '黑色',
        shape: '圆柱形',
        use: '写字'
      };
       console.log({ ...apple, ...pen });
       //   新对象拥有全部属性,相同属性,后者覆盖前者
 

3. 用户参数和默认参数

      const logUser = userParam => {
        const defaultParam = {
          username: 'ZhangSan',
          age: 0,
          sex: 'male'
        };
        const param = { ...defaultParam, ...userParam };
        console.log(param.username);
      };
      logUser();
 

4.注意事项

    1. 如果展开一个空对象,则没有任何效果
    1. 非对象的展开

如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象

console.log({ ...'alex' });//{0: "a", 1: "l", 2: "e", 3: "x"}
console.log([...'alex']);// ["a", "l", "e", "x"]
console.log(...'alex');// a l e x
 
let arr = ["cccc", "dddd", "eeeee"];
let obj = { ...arr };
console.log(obj);//{0: "cccc", 1: "dddd", 2: "eeeee"}
 

八.Symbol唯一值

ES6新规定的Symbol(符号)是原始值,且符号实例唯一、不可变的,它的用途是确保对象属性使用唯一标识符。

需要使用Symbol()函数初始化

 let name1 = Symbol('liming');
 let name2 = Symbol('liming');
 console.log(name1 == name2);  //false
 
// 希望能够多次使用同一个symbol值
 let name1 = Symbol.for('name'); //检测到未创建后新建
 let name2 = Symbol.for('name'); //检测到已创建后返回
 console.log(name1 === name2); // true
 

Symbol的另一特点是隐藏性,Symbol 作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

 let id = Symbol("id");
 let obj = {
  [id]:'symbol'
 };
 for(let option in obj){
     console.log(obj[option]); //空
 }
 

但是也有能够访问的方法:Object.getOwnPropertySymbols.该方法会返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

 let id = Symbol("id");
 let obj = {
  [id]:'symbol'
 };
let array = Object.getOwnPropertySymbols(obj);
 console.log(array); //[Symbol(id)]
 console.log(obj[array[0]]);  //'symbol'

 

九.Set 和 Map 数据结构

9.1 Set数据结构

  • ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
  • 要创建一个 Set,需要提供一个 Array 作为输入,或者直接创建一个空 Set:
      var s1 = new Set(); 
      var s2 = new Set([1, 2, 3]); 
      console.log(s1, typeof s1);   //Set(0) {} "object"
      console.log(s2, typeof s2);   //Set(3) {1, 2, 3} "object"
 

9.1.1 Set 实例的方法和属性

    1. add添加成员(可以连写)
      const s = new Set();
      s.add(1).add(2).add(2);
      console.log(s);//{1,2}
 
    1. has判断是否有某个成员
     const s = new Set(["ccc", "ssss", "dddd"]);
     console.log(s.has("dddd")); //true
 
  • 3.delete删除某个成员

使用 delete 删除不存在的成员,什么都不会发生,也不会报错

      const s = new Set(["ccc", "ssss", "dddd"]);
      s.delete("dddd");
      console.log(s);
 
    1. clear全部清除
      s.clear();
       console.log(s);
 
    1. forEach():使用回调函数遍历每个成员

    有两个参数,第一个是回调函数,第二个是改变this指向
    Set 中 value = key

       const s = new Set();
       s.add(1).add(2).add(2);
       s.forEach(function (value, key, set) {
         console.log(this);
         console.log(value, key, set === s);
      }, document);
     // 按照成员添加进集合的顺序遍历
     
  • 6.属性size–用来获取成员个数,相当于length
     const s = new Set(["ccc", "ssss", "dddd"]);
     console.log(s.size); //3
 
  • 7.遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

9.1.2 Set 构造函数的参数

可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

//1.数组
       const s = new Set([1, 2, 1]);


//2.字符串、arguments、NodeList、Set 等
       console.log(new Set('hi'));
       function func() {
         console.log(new Set(arguments));
       }
       func(1, 2, 1);
       console.log(new Set(document.querySelectorAll('p')));

      const s = new Set([1, 2, 1]);
      console.log(new Set(s) === s);
      console.log(s);
 

9.1.3.Set 的注意事项

1.判断重复的方式

      const s = new Set([NaN, 2, NaN]);
      console.log(NaN === NaN);

   //  Set 对重复值的判断基本遵循严格相等(===)
   //  但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN
 

2.什么时候使用 Set?

① 数组或字符串去重时
② 不需要通过下标访问,只需要遍历时
③ 为了使用 Set 提供的方法和属性时(add delete clear has forEach size 等)

9.1.4.Set 应用

1.数组去重

  console.log([...new Set([1, 2, 1])]);
 

2.字符串去重 ‘abbacbd’;
把字符串用Set方法变成去重后对象,再把对象转化成数组,用数组的join方法转化成字符串

       const s = new Set('abbacbd');
       console.log(s);//{'a','b','c','d'}
       console.log([...s].join(''));
       console.log(s);

       //一行搞定
       console.log([...new Set('abbacbd')].join(''));
 

3.存放 DOM 元素

 <p>1111</p>
 <p>22222</p>
 <p>3333</p>
      // for()
      const s = new Set(document.querySelectorAll('p'));
      console.log(s);
      s.forEach(function (elem) {
        console.log(elem);
        elem.style.color = 'red';
        elem.style.backgroundColor = 'yellow';
      });
 

9.2 Map数据结构

  • Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数 Map 可以接受一个数组作为参数。
  • 有一个size:返回 Map 对象中所包含的键值对个数;
  • 如果只是需要 key -> value 的结构,或者需要字符串以外的值做键,使用 Map 比对象更合适,Map 有 forEach 遍历,size 判断个数

9.2.1 Map 和 Object 的区别

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

9.2.2 Map 对象的方法

  • set(key, val): 向 Map 中添加新元素;
  • get(key): 通过键值查找特定的数值并返回;
  • has(key): 判断 Map 对象中是否有 Key 所对应的值,有返回 true,否则返回 false;
  • delete(key): 通过键值从 Map 中移除对应的数据;
  • 使用 delete 删除不存在的成员,什么都不会发生,也不会报错;
  • clear(): 将这个 Map 中的所有元素清空;
  • forEach遍历
      const m = new Map();
      // 1.set()增加
      m.set("age", 18).set(true, "true").set("age", 20);
      console.log(m); // {"age" => 20, true => "true"}

      // 2.get()查找 , get 获取不存在的成员,返回 undefined
      console.log(m.get("age")); //20

      // 3.has(key):是否有
      console.log(m.has("age")); //true

      // 4.delete(key)删除
      m.delete("age");
      console.log(m); //{true => "true"}

      // 5.clear():清空
      m.clear();
      console.log(m); //{}
 
      const m = new Map();
      m.set("age", 20).set(true, "true");
      m.forEach(function (value, key, map) {
        console.log(value, key, map === m);
        console.log(this);
        /*
         第一次:  20 "age" true   document
         第二次: true true true  document
        */
      }, document);
 

9.2.3 Map 构造函数的参数

1.以二维数组传参
      const arr2 = [["key", "value"]];
      const m = new Map(arr2);
      console.log(m); //Map(1) {"key" => "value"}
 
  • Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
  ['name', '张三'],
  ['title', 'Author']
];
const map = new Map();
items.forEach(
  ([key, value]) => map.set(key, value)
);
 
2.Set和Map作为参数

事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
Set 中也必须体现出键和值

      const s = new Set([
        [20, "二十"],
        [30, "三十"],
      ]);
      console.log(s); // {Array(2), Array(2)}
      console.log(new Map(s)); //Map(2) {20 => "二十", 30 => "三十"}
 

Map——复制了一个新的 Map

 
      const m1 = new Map([
        ["name", "alex"],
        ["age", 18],
      ]);
      console.log(m1);
      const m2 = new Map(m1); //{"name" => "alex", "age" => 18}复制一个新map
      console.log(m2, m2 === m1); //{"name" => "alex", "age" => 18} false
 
  • 改变三个 P 标签文字的颜色
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Map 的应用</title>
  </head>
  <body>
    <p>1</p>
    <p>2</p>
    <p>3</p>

    <script>
      //把三个p标签解构出来
      const [p1, p2, p3] = document.querySelectorAll("p");
      console.log(p1, p2, p3);
      // const m = new Map();
      // m.set(p1, 'red');
      // m.set(p2, 'green');
      // m.set(p3, 'blue');

      const m = new Map([
        [
          p1,
          {
            color: "red",
            backgroundColor: "yellow",
            fontSize: "40px",
          },
        ],
        [
          p2,
          {
            color: "green",
            backgroundColor: "pink",
            fontSize: "40px",
          },
        ],
        [
          p3,
          {
            color: "blue",
            backgroundColor: "orange",
            fontSize: "40px",
          },
        ],
      ]);

      m.forEach((propObj, elem) => {
        for (const p in propObj) {
          elem.style[p] = propObj[p];
        }
      });

      // m.forEach((color, elem) => {
      //   elem.style.color = color;
      // });
      console.log(m);
    </script>
  </body>
</html>
 

十.Iterator 和 for…of 循环

10.1 Iterator遍历器(迭代器)

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

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

10.1.1 Iterator 的作用有三个:

  • 一是为各种数据结构,提供一个统一的、简便的访问接口;
  • 二是使得数据结构的成员能够按某种次序排列;
  • 三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

10.1.2 迭代的过程如下:

  • 通过 Symbol.iterator 创建一个迭代器,指向当前数据结*构的起始位置
  • 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束。
      const items = ["zero", "one", "two"];
      const it = items[Symbol.iterator]();
      console.log(it.next()); //{value: "zero", done: false}
      console.log(it.next()); //{value: "one", done: false}
      console.log(it.next()); //{value: "two", done: false}
      console.log(it.next());// {value: undefined, done: true}
      console.log(it.next()); //{value: undefined, done: true}
 

10.1.3 为什么需要 Iterator 遍历器–for..of 来遍历

我们之前的遍历方法是:

  • 遍历数组:for 循环和 forEach 方法
  • 遍历对象:for in 循环

而 Iterator 遍历器是一个统一的遍历方式,使用 Iterator 封装好的 for..of 来遍历,不管是数组还是对象都可以遍历

10.2 for..of 来统一遍历遍历

1.什么是可遍历?

只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是可遍历的, 只要可遍历,就可以使用 for…of 循环来统一遍历

2.原生可遍历的有哪些?

  • 数组
  • 字符串
  • Set
  • Map
  • arguments
  • NodeList
for (const elem of document.querySelectorAll('p')) {
        console.log(elem);
       elem.style.color = 'red';
      }
 

3.非原生可遍历的有哪些?

一般的对象可以用for … in遍历,也可以给他添加一个Symbol.iterator然后用for … of 遍历

10.2.1 for…of 遍历数组

//keys() 得到的是索引的可遍历对象,可以遍历出索引值
      const arr = [1, 2, 3];
      for (const key of arr.keys()) {
          console.log(key);
       }
//values() 得到的是值的可遍历对象,可以遍历出值
       for (const value of arr.values()) {
         console.log(value);
       }
   相当于
       for (const value of arr) {
         console.log(value);
       }

//entries() 得到的是索引+值组成的数组的可遍历对象
       for (const entries of arr.entries()) {
         console.log(entries);//数组[0,1]
       }
      for (const [index, value] of arr.entries()) {
        console.log(index, value);值0  1
      }
 

10.2.2 for…of 遍历普通对象

      const person = {
        name: "John Smith",
        job: "agent",
      };

      for (const [key, value] of Object.entries(person)) {
        console.log(key, value);
      }
 

十一.Es6新增方法

11.1 Es6字符串新增方法

1. includes()判断字符串中是否含有某些字符

//判断字符串
console.log('abc'.includes('a', 0));//true
1.第一个参数------表示是否包含该值
2.第二个参数----- 表示开始搜索的位置,默认是 0

//判断数组
 arr = ["name", 2, 3, 4, 5, 6, 7, 8];
 console.log(arr.includes("name"));//true
 
应用:给url添加参数
      // https://www.imooc.com/course/list
      // https://www.imooc.com/course/list?c=fe&sort=pop&name=value
      let url = "https://www.imooc.com/course/list?";
      const addURLParam = (url, name, value) => {
        // 第一步:判断url有没有问号,有的话说明后面接了数据,只需加&拼接就欧克,否则先加问号

        url += url.includes("?") ? "&" : "?";
        // 第二步:传入key=value,添加给url
        url += `${name}=${value}`;

        return url;
      };
      url = addURLParam(url, "c", "fe");
      console.log(url);//https://www.imooc.com/course/list?&c=fe
      url = addURLParam(url, "sort", "pop");
      console.log(url);//https://www.imooc.com/course/list?&c=fe&sort=pop
 

2.padStart() 和 padEnd()补全字符串长度

  • 用来在字符串本身的头部或尾部补全字符串长度
  • 原字符串的长度,等于或大于最大长度,不会消减原字符串,字符串补全不生效,返回原字符串
  • 用来补全的字符串与原字符串长度之和超过了最大长度,截去超出位数的补全字符串,原字符串不动
      console.log("abc".padStart(10, "0123456789")); //0123456abc
      console.log("abc".padEnd(10, "0123456789")); //abc0123456
      //  第一个参数表示补全后的字符串长度,第二个参数是要用到的元素补全字符串
      // 如果省略第二个参数,默认使用空格补全长度
 
应用—-显示日期格式

3.trimStart() 和 trimEnd()清除字符串的首或尾空格

       const s = '  a b c  ';
       console.log(s);
       console.log(s.trimStart());
       console.log(s.trimEnd());
 
应用-表单验证提交
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>trimStart() 和 trimEnd()</title>
  </head>
  <body>
    <input type="text" id="username" />
    <input type="submit" value="提交" id="btn" />

    <script>
      const usernameInput = document.getElementById("username");
      const btn = document.getElementById("btn");

      btn.addEventListener(
        "click",
        () => {
          console.log(usernameInput.value);

          // 验证
          console.log(usernameInput.value.trim());
          if (usernameInput.value.trim() !== "") {
            // 可以提交
            console.log("可以提交");
          } else {
            // 不能提交
            console.log("不能提交");
          }

          // 手动提交
        },
        false
      );
    </script>
  </body>
</html>

 

11.2 Es6数组新增方法

1.includes()

  • 判断数组中是否含有某个成员
  • 第二个参数表示搜索的起始位置,默认值是 0
      //从索引值为2的地方开始,判断后面是否含有2这个元素
      console.log([1, 2, 3].includes(2, 2));
      // 基本遵循严格相等(===),但是对于 NaN 的判断与 === 不同,includes 认为 NaN === NaN
 
应用-数组去重
      const arr = [];
      for (const item of [1, 2, 1]) {
        if (!arr.includes(item)) {
          arr.push(item);
        }
      }
      console.log(arr); //[1,2]
 

2.Array.from()

  • Array.from()将类数组对象(一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。)或可迭代对象(数组、字符串、Set、Map、NodeList、arguments)转化为数组。
  • 一般情况下把拥有 length 属性的任意对象转化为数组。
      // 类数组:{number:'value',length:1}
      a = { 0: "aaa", 1: "bbb", 2: "cccc", 3: "ddd", 4: "eeee", length: 5 };
      let array1 = Array.from(a);
      console.log(array1); // ["aaa", "bbb", "cccc", "ddd", "eeee"]
 
  • 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组
  • 第一个参数—能被转化的数据
  • 第二个参数—作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
  • 第三个参数–第二个参数为一般回调函数时,第三个参数可以改变 this 指向
     // 类数组:{number:'value',length:1}
      a = { 0: "aaa", 1: "bbb", 2: "cccc", 3: "ddd", 4: "eeee", length: 5 };
      let array1 = Array.from(a, (value) => value + "你真帅");
      console.log(array1); // ["aaa你真帅", "bbb你真帅", "cccc你真帅", "ddd你真帅", "eeee你真帅"]
 

3.find() 和 findIndex()

find():find()返回第一个匹配的元素

  • 1.find()方法对数组中的每一项元素执行一次 callback 函数,直至有一个 callback 返回 true。
  • 2.当找到了这样一个元素后,该方法会立即返回这个元素的值,否则返回 undefined。
  • 3.callback函数带有3个参数:当前元素的值、当前元素的索引,以及数组本身。
     var num = [10, 20, 30, 40, 50, 60, 70, 80, 90];
     var newNum1 = num.find((item, index) => {
       return item > 40;
     });
     console.log(newNum1); //50
 

findIndex()返回数组中第一个满足条件的索引(从0开始), 不满足返回-1;

     var num = [10, 20, 30, 40, 50, 60, 70, 80, 90];
     var newNum1 = num.findIndex((item, index) => {
       return item > 40;
     });
     console.log(newNum1); //4  index=length-1
 
应用-筛选数据
      const students = [
        {
          name: "张三",
          sex: "男",
          age: 16,
        },
        {
          name: "李四",
          sex: "女",
          age: 22,
        },
        {
          name: "王二麻子",
          sex: "男",
          age: 32,
        },
      ];
      console.log(students.find((value) => value.sex === "男"));
      console.log(students.findIndex((value) => value.sex === "女"));
 

11.3 Es6对象新增方法

1.Object.assign()合并对象

  • Object.assign(目标对象, 源对象 1,源对象 2,…): 目标对象
  • Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
应用–合并默认参数和用户参数
      const logUser = (userOptions) => {
        const DEFAULTS = {
          username: "ZhangSan",
          age: 0,
          sex: "male",
        };
        const options = Object.assign({}, DEFAULTS, userOptions);
        console.log(options);
      };
      logUser();
 

2.Object.keys()、Object.values() 和 Object.entries()

  • 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
  • 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组
      const person = {
        name: "Alex",
        age: 18,
      };

      console.log(Object.keys(person)); //['name',"age"]
      console.log(Object.values(person));//["Alex", 18]
      console.log(Object.entries(person)); //二维数组  [["name", "Alex"],["age", 18]]
 
应用-和for…of…一起遍历对象
      obj = { name: "蔡徐坤", age: "18", hobby: ["唱歌", "写歌", "跳舞"] };
      console.log(Object.entries(obj));

      for (key of Object.entries(obj)) {
        console.log(key[0], key[1]);
      }
 

十二.Promise对象

  • Promise是ES6中新增的异步编程解决方案, 在代码中的表现是一个对象。
  • Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用;
  • 企业开发中为了保存异步代码的执行顺序, 那么就会出现回调函数层层嵌套,如果回调函数嵌套的层数太多, 就会导致代码的阅读性, 可维护性大大降低。Promise对象可以将异步操作以同步流程来表示, 避免了回调函数层层嵌套(回调地狱)。

12.1 基本使用

第一步:创建一个Promise实例对象,并传入执行器函数,执行器函数会同时执行

//Promise是一个构造函数
let p = new Promise(executor)
 

第二步:executor执行器函数接受两个函数参数,resolve和reject,这两个函数是改变Promise状态的;

  1. pending: 默认状态,只要没有告诉Promise任务是成功还是失败就是pending状态
  1. fulfilled(resolved): 只要调用resolve函数, 状态就会变为fulfilled, 表示操作成功
  1. rejected: 只要调用rejected函数, 状态就会变为rejected, 表示操作失败

注意点:状态一旦改变既不可逆, 既从pending变为fulfilled, 那么永远都是fulfilled , 既从pending变为rejected, 那么永远都是rejected.

      let p = new Promise((resolve, reject) => {
        console.log("我同步执行");
        resolve("我是resolve携带的数据");
        // reject("我是reject携带的失败信息");
      }).then(
        function result(res) {
          console.log(res);
        },//期约兑现回调
        function reason(reason) {
          console.log(reason);
        }//期约拒绝回调
      );
 
let p=new Promise((resolve,reject)={
  /* 
  这里一般存放的都是我们即将要处理的异步任务(比如网络请求成功),任务成功我们执行resolve吧数据传出去,
  任务失败我们执行reject(当然写同步的也可以),之后根据状态就可以调用原型里面的三个方法
  */
})
 

12.2 Promise.prototype.then()方法的使用

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

  • then()方法执行后返回的是一个新的 Promise 对象

      let promise = new Promise(function (resolve, reject) {
        resolve("111");
      });
      p1 = promise.then(function success(res) {
        console.log(res);

        return 222222;
      });
      console.log(p1);

      /*
      p1是一个新的Promise;
         [[PromiseState]]: "fulfilled"
         [[PromiseResult]]: "222"
      */
 
  • 同一个promise对象可以多次调用then方法 . 当该promise对象的状态对应时所有then方法都会被执行。
  • 可以通过上一个promise对象的then方法给下一个promise对象的then方法传递参数,传递的参数通过return返回给下一个promise对象的then方法。
  • 链式调用then返回的的Promise对象状态都与第一个一样,接受的参数(对象的结果)由上个链式调用函数的返回值有关

第一次使用resolve函数:

      let promise = new Promise(function (resolve, reject) {
        resolve("我是resolve函数传过来的参数");
      });
      p1 = promise.then(function success(res) {
        console.log(res);

        return res;
      });
      console.log("我是第1次链式调用then方法", p1);
      p2 = p1.then(function success(res) {
        console.log(res);

        return res;
      });
      console.log("我是第2次链式调用then方法", p2);
      p3 = p2.then(function success(res) {
        console.log(res);

        return res;
      });
      console.log("我是第3次链式调用then方法", p3);
      p4 = p3.then(function success(res) {
        console.log(res);

        return res;
      });
      console.log("我是第4次链式调用then方法", p4);
      /*
      p1,p2,p3,p4是一个新的Promise;
         [[PromiseState]]: "fulfilled"
         [[PromiseResult]]: "我是resolve函数传过来的参数"
      */
 

如果第一次是reject:

第一步:new Promise()并执行reject

 let promise = new Promise((resolve, reject) => {
        reject("我是reject函数传过来的参数");
        // resolve("我是resolve函数传过来的参数");
      });
      console.log("我是new出来开的promise", promise);
      /*
      [[PromiseState]]: "rejected"
      [[PromiseResult]]: "我是reject函数传过来的参数"//始终不变

      */
 

第二步:

  • promise.then方法执行(根据拒绝状态他应该调用then的第二个失败回调);同时使第一次then产生的Promise对象,他的状态是成功的;值是undefined
  • 你一定想改他的状态和结果吧?失败函数里面抛出错误,就能改了。
      p1 = promise.then(
        (res) => {
          console.log(res);
        },
        (err) => {
          console.log("失败回调函数被调用了", err);
          // throw new Error(err);
        }
      );

      console.log("我是第1次链式调用then方法返回的promise", p1);
      /*
       [[PromiseState]]: "fulfilled"
       [[PromiseResult]]: undefined

       如果在失败回调里面抛出错误,p1就会发生变化   
        [[PromiseState]]: "rejected"
        [[PromiseResult]]: Error: 我是reject函数传过来的参数 
      */
 

第三步:

  • then方法执行第2次产生的Promise对象(执行哪个函数根据上一个promise的状态决定),这里假定为成功。
  • 之前有一次状态是失败,这里就算执行成功的回调也传不过来原始数据;
  • 在成功的回调里面写上返回值改写p2的结果
      p2 = p1.then(
        (res) => {
          console.log('我是第二次执行then',res); // res为undefined,没有数据因为第一次调用的时候是reject,传不过来数据
          return 1111;//这里是为了让p2的结果是111
        },
        (err) => {
          console.log("失败回调函数被调用了", err);
        }
      );
console.log("我是第2次链式调用then方法", p2);
      /*
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: 1111
      
      */
 
  • 结论:

执行reject()的后果:

让当前Promise对象状态变为失败,不影响后面的,后面的都为成功;

只要调用过程用到了reject(),就再也不能获取原始的数据了;

前面使用reject之后,若果想要后面也跟着状态变为失败,就要在reject()对应的失败函数里面抛出错误语句: throw new Error(err);

若果你想要改变当前调用then返回的Promise结果,就需要在当前then里面添加return返回想要的结果。

无论是在上一个promise对象成功的回调还是失败的回调传递的参数, 都会传递给下一个promise对象成功的回调。

12.2 Promise.prototype.catch()方法的使用

    1. catch 其实是 then(undefined, () => {}) 的语法糖;
    1. 如果需要分开监听, 也就是通过then监听成功通过catch监听失败,那么必须使用链式编程, 否则会报错;

catch方法使用链式编程的原因是:

a.如果promise的状态是失败, 但是没有对应失败的监听就会报错
b.then方法会返回一个新的promise, 新的promise会继承原有promise的状态,状态改变的话所有的then都会执行
c.如果新的promise状态是失败, 但是没有对应失败的监听也会报错

  1. 如果 catch里面的回调函数 抛出一个错误或返回一个本身失败的 Promise , 通过 catch() 返回的Promise 被rejected;否则,它将显示为成功(resolved)。
            //抛出错误
           throw new Error("我失败了")
             
            //返回失败的Promise
           return new Promise((resolve, reject) => {
            reject();
          });
 
  • 和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常。

之前这样的做法会报错

      let promise = new Promise(function (resolve, reject) {
        resolve();
      });

      promise.then(
        function () {
          console.log("成功");

          xxx; // Uncaught (in promise) ReferenceError: xxx is not defined
         
        },
        function () {
          console.log("失败");
        }
      );
 

把成功回调里面的异常抛给catch了

      let promise = new Promise(function (resolve, reject) {
        resolve();
      });

      promise
        .then(function () {
          console.log("成功");

          xxx;
        })
        .catch(function (e) {
          console.log("失败", e); //捕获到前面的异常并传递给e了,失败 ReferenceError: xxx is not defined
        });
 

js异常处理

利用try{}catch{}来处理异常可以保证程序不被中断, 也可以记录错误原因以便于后续优化迭代更新。

try {
  可能遇到的意外的代码
}
catch(e) {
  捕获错误的代码块
}
 

catch的特点:

  • (1)和then一样, 在修改promise状态时, 可以传递参数给catch方法中的回调函数;
  • (2)和then一样, 同一个promise对象可以多次调用catch方法, 当该promise对象的状态时所有catch方法都会被执行.
  • (3)和then一样, catch方法每次执行完毕后会返回一个新的promise对象
  • (4) 和then方法一样, 上一个promise对象也可以给下一个promise成功的传递参数。但是,无论是在上一个promise对象成功的回调还是失败的回调传递的参数, 都会传递给下一个promise对象成功的回调。
  • (5).和then一样, catch方法如果返回的是一个Promise对象, 那么会将返回的Promise对象的执行结果中的值传递给下一个catch方法。
  • (6) .和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常。

12.3 Promise.all()方法的使用

参数:

    1. all方法接收一个数组.
    1. 如果数组中有多个Promise对象,只有都成功才会执行then方法,并且会按照添加的顺序, 将所有成功的结果重新打包到一个数组中返回给我们。
    1. 如果数组中不是Promise对象, 那么会直接执行then方法

返回:

  • 1.all方法会返回一个新的Promise对象

  • 2.会按照传入数组的顺序将所有Promise中成功返回的结果保存到一个新的数组返回

  • 3.数组中有一个Promise失败就会失败, 只有所有成功才会成功

  • 应用场景: 批量加载, 要么一起成功, 要么一起失败
      arr = [
        "/wp-content/uploads/front-end-baike/block_picture_1555422597.jpg",
        "/wp-content/uploads/front-end-baike/block_picture_1555422597.jpg",
        "/wp-content/uploads/front-end-baike/block_picture_1555419713.jpg",
      ];
      function loadImage(url) {
        return new Promise(function (resolve, reject) {
          let oImg = new Image();
          let time = Math.random() * 1000;
          // console.log(time);
          setTimeout(function () {
            oImg.src = url;
          }, time);
          // oImg.src = url;
          oImg.onload = function () {
            resolve(oImg);
          };
          oImg.onerror = function () {
            reject("图片加载失败了");
          };
        });
      }
      Promise.all([loadImage(arr[0]), loadImage(arr[1]), loadImage(arr[2])])
        .then(function (result) {
          // console.log(result);
          result.forEach(function (oImg) {
            document.body.appendChild(oImg);
          });
        })
        .catch(function (e) {
          console.log(e);
        });
 

12.4 Promise.race()方法的使用

  • 1.race方法接收一个数组,
  • 2.如果数组中有多个Promise对象, 谁先返回状态就听谁的, 后返回的会被抛弃
  • 3.如果数组中不是Promise对象, 那么会直接执行then方法
  • 应用场景: 接口调试, 超时处理

给超时函数和加载函数均包装一个promise对象,然后把这个对象写入race里面,紧接着写处理函数,5s之内没有获取资源,就直接超时处理

      let url =
        "/wp-content/uploads/front-end-baike/5e852f2dd56c27fd.jpg.webp";
      function loadImage(url) {
        return new Promise(function (resolve, reject) {
          let oImg = new Image();
          setTimeout(function () {
            oImg.src = url;
          }, 2000);
          oImg.onload = function () {
            resolve(oImg);
          };
          oImg.onerror = function () {
            reject("图片加载失败了");
          };
        });
      }
      function timeout() {
        return new Promise(function (resolve, reject) {
          setTimeout(function () {
            reject("超时了");
          }, 5000);
        });
      }
      Promise.race([loadImage(url), timeout()])
        .then(function (value) {
          console.log("成功", value);
        })
        .catch(function (e) {
          console.log("失败", e);
        });
 

12.4 Promise.resolve()和Promise.reject()方法的使用

Promise.resolve()返回一个成功的Promise对象

      p = Promise.resolve("success");
      console.log(p);

      /*
      [[PromiseState]]: "fulfilled"
      [[PromiseResult]]: "success"
      */
 

Promise.reject()返回一个失败的Promise对象

      p = Promise.reject("failed");
      p.catch((err) => console.log(err));
      console.log(p);

      /*
       [[PromiseState]]: "rejected"
       [[PromiseResult]]: "failed"
      */
 

Promise封装网络请求放到网络那一块

总结:Promise如何发挥作用?
相当于告诉你家保姆去做几件事:

1.你先去超市买菜。
2.用超市买回来的菜做饭。
3.将做好的饭菜送到老婆单位。
4.送到单位后打电话告诉我。

我们知道,上面三步都是需要消耗时间的,我们可以理解为三个异步任务。利用 Promise 的写法来书写这个操作:

//写好任务计划表
function 买菜(resolve,reject) {
    setTimeout(function(){
        resolve(['西红柿''鸡蛋''油菜']);
    },3000)
}
function 做饭(resolve, reject){
    setTimeout(function(){
        //对做好的饭进行下一步处理。
        resolve ({
            主食: '米饭',
            菜: ['西红柿炒鸡蛋''清炒油菜']
        })
    },3000) 
}
function 送饭(resolve,reject){
    //对送饭的结果进行下一步处理
    resolve('老婆的么么哒');
}
function 电话通知我(){
    //电话通知我后的下一步处理
    给保姆加100块钱奖金;
}

//开始做任务
// 告诉保姆帮我做几件连贯的事情,先去超市买菜
new Promise(买菜)
//用买好的菜做饭
.then((买好的菜)=>{
    return new Promise(做饭);
})
//把做好的饭送到老婆公司
.then((做好的饭)=>{
    return new Promise(送饭);
})
//送完饭后打电话通知我
.then((送饭结果)=>{
    电话通知我();
})
 
  • 请一定要谨记:如果我们的后续任务是异步任务的话,必须return 一个 新的 promise 对象。
  • 如果后续任务是同步任务,只需 return 一个结果即可。
  • 我们上面举的例子,除了电话通知我是一个同步任务,其余的都是异步任务,异步任务 return 的是 promise对象。

十三.async 函数

async和await是Promise的语法糖
async是对函数的一个修饰,使一个函数返回Promise实例。
await是等待一个Promise实例成功后执行

  • 1.执行async函数后可以写then
      async function foo() {
        //1.返回成功的
        //return Promise.resolve();
        return 1;

        // 2.返回失败的
        // return Promise.reject();
        // throw new Error("我错了");
      }
      console.log(foo()); //Promise实例
      
      foo().then((res) => {
        console.log(res); //1
      });
 
  • 2.必须在async函数里面使用await
      P1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("ok");
        }, 2000);
      });

      async function func() {
        /*
        代码的含义:是等待p1实例状态成功了才去执行await后面的代码,
        相当于then里面的的成功处理语句
        */
        let result = await P1;
        console.log(result);
      }
      func();
 
面试题:编写一个 sleep 函数,让其等待 1000ms 后再去做其他事情?
  • 方案一:用定时器
      function sleep(interval, callback) {
        if (typeof interval === "function") {
          callback = interval;
          interval = 2000;
        }
        setTimeout(() => {
          callback();
        }, interval);
      }
      sleep(function () {
        console.log("我在2000ms之后执行");
      });
 
  • 方案二:函数返回值为Promise对象
      function sleep(interval = 5000) {
        return new Promise((resolve) => {
          setTimeout(resolve, interval);
        });
      }
      sleep(2000).then(() => {
        console.log("我在2000ms之后执行");
      });
 
  • 方案三:利用await语句
      function sleep(interval = 1000) {
        return new Promise((resolve) => {
          setTimeout(resolve, interval);
        });
      }
      (async function () {
        await sleep();
        console.log("我在1000ms之后执行");
        await sleep(2000);
        console.log("我在2000ms之后执行");
        await sleep(5000);
        console.log("我在5000ms之后执行");
      })();
 

十四.Module 的语法

  • ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
  • 模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

14.1 export 命令

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。下面是一个 JS 文件,里面使用 export 命令输出变量。

1.输出变量

// profile.js
export var firstName = 'Michael'
export var lastName = 'Jackson'
export var year = 1958

//等同于下列代码
var firstName = 'Michael'
var lastName = 'Jackson'
var year = 1958

export { firstName, lastName, year }
 

上面代码是 profile.js 文件,保存了用户信息。ES6 将其视为一个模块,里面用 export 命令对外部输出了三个变量。

2.输出函数或类(class)

export function multiply(x, y) {
  return x * y;
};

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
 

另:export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

14.2 import 命令

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

import { firstName, lastName, year } from './profile.js'

function setName(element) {
  element.textContent = firstName + ' ' + lastName
}

//import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,
//必须与被导入模块(profile.js)对外接口的名称相同。
 

如果想为输入的变量重新取一个名字,import 命令要使用 as 关键字,将输入的变量重命名。

import { lastName as surname } from './profile.js'
 

import 命令输入的变量都是只读的,不允许在加载模块的脚本里面,改写接口。

import { a } from './xxx.js'
a = {} // Syntax Error : 'a' is read-only;

// 但是如果a是一个对象,改写a的属性是允许的
import { a } from './xxx.js'
a.foo = 'hello' // 合法操作
 

import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

import { myMethod } from 'util'
//  util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。
 

import 命令具有提升效果,会提升到整个模块的头部,首先执行。

模块的整体加载

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

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

export function circumference(radius) {
  return 2 * Math.PI * radius
}
 
import * as circle from './circle'

console.log('圆面积:' + circle.area(4))
console.log('圆周长:' + circle.circumference(14))
 

export default 命令

为了给用户提供方便,让他们不需要知道所要加载的变量名或函数名,就能加载模块。

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

上面代码是一个模块文件 export-default.js,它的默认输出是一个函数。其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。

// import-default.js
import customName from './export-default'
customName() // 'foo'
 

module章节内容参考掘金作者:小和山的菜鸟们,在这表示感谢!文章传送门

十五.剩余章节(未完待续)

Class、Generator 函数、Proxy未完待续…..

(0)
上一篇 2021年6月1日 下午4:43
下一篇 2021年6月1日 下午4:59

相关推荐

发表评论

登录后才能评论