ES6笔记

本文最后更新于:2022年9月21日 晚上

运算符的拓展
1.链式判断运算符 ?.
1
2
3
4
5
6
1. obj?.properities
//判断对象的属性是否存在
2. obj?.[expressions]
//判断对象的属性是否存在
3. func?.[...args]
//函数或者方法对象是否存在

eg:

1
2
3
4
5
6
7
8
9
let hex = "#COFFEE".match(/#([A-Z]+)/i)?.[1];
//COFFEE //正则表达式的使用

let a = () => {
console.log('这是a的匿名函数');
}
a?.()
//这是a的匿名函数
//等价于 a==null ? undefined :a() 函数a是null吗?是的话就赋值为undefined,否则(不是null)就执行a()

总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Javascript中一切皆对象

a?.b
// 等同于
a == null ? undefined : a.b
//对象a是null吗? 是的话就赋值为undefined,否则执行a.b 获得对象a的b属性

a?.[x]
// 等同于
a == null ? undefined : a[x]
//对象a是null吗? 是的话就赋值为undefined,否则执行a[x] 获得对象a[x]的方法

a?.b()
// 等同于
a == null ? undefined : a.b()
//对象a是null吗? 是的话就赋值为undefined,否则执行a.b() 对象a中的b()方法

a?.()
// 等同于
a == null ? undefined : a()
//对象a是null吗? 是的话就赋值为undefined,否则执行a() 对象a()方法

注意:如果a?.b()里面的a.b有值,但不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是nullundefined,但也不是函数,那么a?.()会报错。

Symbol
1.为了解决的问题

对象的属性名称都是字符串,特别是自己定义的时候,特别容易冲突。

2.含义

Symbol()代表唯一的值

3.机制

如果Symbol()的参数是一个对象,就会调用改对象的toString()方法,将其转为字符串,然后生成一个Symbol

eg:

1
2
3
4
5
6
7
const sym = Symbol(obj);
sym // Symbol(abc)

Symbol()函数只表示当前赋值对象的描述
let a = Symbol()
let b = Symbol()
a === b //false
总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1.可以给Symbol添加描述
let a = Symbol('Attribute a')
conlog(a.discription)
//Attribute a

//Symbol()作为属性名,此时不能用点运算符获得属性
let mySymbol = Symbol()
//创建变量
let b = {
[mySymbol]: 'helo'
}
//或者
let c={}
c[mySymbol]='helo'
console.log(d[mySymbol]);
//获得属性

let d ={
[mySymbol](value){
console.log(value)
}
}
d[mySymbol]('打印值')
//调用方法,注意参数args的传入方式 [name](args){expression}
应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getArea(shape, options) {
let area = 0
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height
break
case shapeType.square:
area = 6
break
}
return area
}
console.log(getArea(shapeType.triangle, { width: 100, height: 100, }));
//5000
Promise与顶层await
1.什么是Promise
  • 在Javascript中表示期约,说白了就是当想获取的数据需要花时间计算时候,“等”这个计算完成,再执行后面的代码,避免后面拿值拿不到。

  • 解释大白话讲解Promise(一)

2.await
  • await用于等待一个Promise对象,只能在异步函数async function中使用
    • 首先为什么是一个Promise对象,因为await我理解为一个标志,标志后面的函数需要返回Promise对象
    • 如果表达式的值不是Promise对象,await会把该值转换为已经正常处理的Promise结果并返回
    • 通过Promise对象实现异步
3.顶层await
  • ES6之后,可以单独使用await? ==存疑==

    • 旧版本在一个async函数外单独使用await会引起错误,常用立即执行函数表达式(IIFE)

      1
      2
      3
      4
      5
      6
      7
      await Promise.resolve('ok') //错误用法

      (async()=>{
      await Promise.resolve(
      console.log('ok')
      )
      }) //正确用法
  • 顶层await想解决的问题: ES6模块化中导入导出,防止模块之间相互访问时异步访问无值的问题。

    • 看下面一个例子 来源为何要在Javascript中使用顶层await

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      </head>

      <body>
      <script type="module" src="./main.js"></script>
      <!-- 注意这边的module 像浏览器解释js脚本的风格 -->
      </body>

      </html>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      //------ library.js ------
      export const sqrt = Math.sqrt;
      export function square(x) {
      return x * x;
      }
      export function diagonal(x, y) {
      return sqrt(square(x) + square(y));
      }
      //暴露具体的函数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      //------ middleware.js ------ 中间件
      import { square, diagonal } from './library.js';

      console.log('From Middleware');

      let squareOutput;
      let diagonalOutput;

      // IIFE
      (async () => {
      await delay(1000);
      squareOutput = square(13);
      diagonalOutput = diagonal(12, 5);
      })();

      function delay(delayInms) {
      return new Promise(resolve => {
      setTimeout(() => {
      resolve(console.log('❤️'));
      }, delayInms);
      });
      }
      //中间件中计算并暴露值
      export {squareOutput,diagonalOutput};
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //------ main.js ------
      import { squareOutput, diagonalOutput } from './middleware.js';

      console.log(squareOutput); // undefined
      console.log(diagonalOutput); // undefined
      console.log('From Main');

      setTimeout(() => console.log(squareOutput), 2000);
      //169

      setTimeout(() => console.log(diagonalOutput), 2000);
      //13
      • 问题:main.js中第一次打印,读取到了中间件中暴露出来的值,但中间件中线程并没有计算完成。
    • 两种解决方案

      • 计算值的时候用一个Promise作为总的判断依据

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        //------ middleware.js ------ 中间件
        import { square, diagonal } from './library.js';

        console.log('From Middleware');

        let squareOutput;
        let diagonalOutput;

        // IIFE 用export把IIFE返回的Promise暴露出去
        // 用该Promise作为判断函数是否执行完的标准
        export default (async () => {
        await delay(1000);
        squareOutput = square(13);
        diagonalOutput = diagonal(12, 5);
        })();


        function delay(delayInms) {
        return new Promise(resolve => {
        setTimeout(() => {
        resolve(console.log('❤️'));
        }, delayInms);
        });
        }
        //中间件中计算并暴露值
        export {squareOutput,diagonalOutput};
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        //------ main.js ------
        import promise,{squareOutput,diagonalOutput} from './middleware.js';

        promise.then(()=>{
        console.log(squareOutput); // 169
        console.log(diagonalOutput); // 13
        console.log('From Main');
        setTimeout(() => console.log(squareOutput), 2000);// 169
        setTimeout(() => console.log(diagonalOutput), 2000);// 13
        })
        • 可行,但带来了新的问题
          • main.js 调用中middleware.js中暴露出来的变量没有问题
          • 但是如果有另一个模块B.js需要调用main.js,那么就需要再在main.js中使用一次和middleware.js中间件中一样的IIFE Promise才能让B.js正确访问变量。
      • 用导出的变量去resolve IIFE promise

        • 将变量作为IIFE promise的返回值返回

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          //------ middleware.js ------ 中间件
          import { square, diagonal } from './library.js';

          console.log('From Middleware');

          let squareOutput;
          let diagonalOutput;

          // IIFE
          export default (async () => {
          await delay(1000);
          squareOutput = square(13);
          diagonalOutput = diagonal(12, 5);
          return { squareOutput, diagonalOutput } //作为返回值返回
          })();

          function delay(delayInms) {
          return new Promise(resolve => {
          setTimeout(() => {
          resolve(console.log('❤️'));
          }, delayInms);
          });
          }

          export { squareOutput, diagonalOutput };

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          import promise, { squareOutput, diagonalOutput } from './middleware.js';

          //解析返回值,返回值其实就包含Promise的状态(已完成)
          promise.then(({squareOutput,diagonalOutput}) => {
          console.log(squareOutput); // 169
          console.log(diagonalOutput); // 13
          console.log('From Main');
          setTimeout(() => console.log(squareOutput), 2000);
          //169
          setTimeout(() => console.log(diagonalOutput), 2000);
          //13
          })
          • 问题:大部分函数模块都放在了then()中,缺乏动态性和灵活性
        • 顶层await

          • 允许我们让模块系统去处理Promise之间的关系

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            //------ middleware.js 中间件函数------
            import { square, diagonal } from './library.js';

            console.log('From Middleware');

            let squareOutput;
            let diagonalOutput;

            //使用顶层 await
            await delay(1000);
            //await 标志 等待delay执行完之后,才会生成squareOutput
            squareOutput = square(13);
            diagonalOutput = diagonal(12, 5);

            function delay(delayInms) {
            return new Promise(resolve => {
            setTimeout(() => {
            resolve(console.log('❤️'));
            }, delayInms);
            });
            }

            export {squareOutput,diagonalOutput};

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            //------ main.js 主函数------
            import { squareOutput, diagonalOutput } from './middleware.js';

            console.log(squareOutput); // 169
            console.log(diagonalOutput); // 13
            console.log('From Main');

            setTimeout(() => console.log(squareOutput), 2000);// 169

            setTimeout(() => console.log(diagonalOutput), 2000); // 13
          • 注意:顶层await只在ES模块中生效,这些ES模块之间必须具备某种相互依赖关系

          • 总结:

            • 顶层await顾名思义,在某个代码块的顶层,下面的代码块全需要等它。
            • await函数的Promiseresolve之前,await下所有的代码都不会执行。
            • 而如果仅仅在一个函数外面使用await函数外面export的东西,外部也会读到
Generator函数
1.遍历 Iterator
  • 任何数据结构只要部署Iterator接口,就可以完成遍历操作

  • 作用

    1. 为各种数据结构提供一个统一的、简便的数据访问接口
    2. 使得数据结构成员能够按照某种次序排列
    3. for of使用
  • 遍历过程:

    1. 创建一个指针对象,指向起始位置(遍历器对象的本质是一个指针对象)
    2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
    3. 第二次调用指针对象的next方法,可以将指针指向数据结构的第二个成员。
    4. 不断调用指针对象的next方法,直到指向数据结构的结束位置
  • 返回值{value: data , done: boolean} data是当前属性的值,done属性表示遍历是否结束

  • 一个函数只要部署了`Symbol.iterator属性,就被视为具有iterator接口

  • for of循环,内部调用的就是Symbol.iterator方法

  • 常见的具有iterator接口的结构

    1. 数组
    2. Set和Map结构
    3. 计算生成的数据结构(ES6数组、Set、Map)
      1. entries()
      2. keys()
      3. values()
    1
    2
    3
    4
    let arr = ['a', 'b', 'c'];
    for (let pair of arr.entries()) {
    console.log(pair);
    }
    1. 类似数组的对象

      1. 字符串
      2. DOM Nodelist对象
      3. arguments对象

    注意:并不是所有的类数组对象都有Iterator接口,需要使用Array.from方法将其转化为数组

    1. 对象

      • 普通对象,for in可以遍历键名,for of循环会报错

      • 如果要用for of

        1. Object.keys方法将对象的键名生成一个数组

          1
          2
          3
          for (var key of Object.keys(someObject)) {
          console.log(key + ': ' + someObject[key]);
          }
        2. Generator函数将对象重新包装一下

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          const obj = { a: 1, b: 2, c: 3 }

          function* entries(obj) {
          for (let key of Object.keys(obj)) {
          yield [key, obj[key]];
          }
          }

          for (let [key, value] of entries(obj)) {
          console.log(key, '->', value);
          }
          // a -> 1
          // b -> 2
          // c -> 3
2.Generator函数
  • 写法

    1
    2
    3
    4
    5
    6
    function* generator(){       //函数名前,fucntion后有星号
    yield 'hello'
    return 'ending'
    }

    var gen = generator()
  • 机制

    • 调用该函数后,返回一个指向内部状态的指针对象(Iterator对象)

    • 调用遍历器的next()方法,使指针指向下一个状态

      • 第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止
      • yield表达式
        • 是一种暂停标志,next()方法遇到yield表达式就暂停后面的操作
        • 将紧跟在yield后面的表达式的值作为返回对象的value
      • 下一次调用next方法时,内部指针从函数头部或者上一次停下来的地方再继续往下执行,直到遇到下一个yield表达式。
      • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止
      • 并将return语句后面的表达式的值,作为返回的对象的value属性值。
    • next()方法中的参数

      • 该参数会被当成上一个yield表达式的返回值
      • 通过该参数,就能在Generator()函数运行后,继续向该函数中注入值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
      }

      var a = foo(5);
      a.next()
      // Object{value:6, done:false}
      // 第一次运行,x值为5 返回var y = 2 * (yield (x + 1))中 yield后面的(x+1),返回值为6
      a.next()
      // Object{value:NaN, done:false}
      // 第二次运行,next()中不带参数,undefined 返回var y = 2 * undefined,返回值为NaN
      a.next()
      // Object{value:NaN, done:true}
      // 第三次运行,next()中不带参数,undefined 返回var z = undefinedw;,返回值为NaN

      var b = foo(5);
      b.next() // { value:6, done:false }
      b.next(12) // { value:8, done:false }
      b.next(13) // { value:42, done:true }

      解释:

      上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN

      如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用bnext方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5y等于24,所以return语句的值等于42

ES6中的Module模块
1. 注意是ES6模块中的不是Nodejs模块中的CommonJS规范

module.exports、exports、export都是导出,有何区别?

2. ES6中export
  • ES6模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

  • 可以输出变量、函数和类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //变量
    var firstName = 'Lingyi';
    var lastName = 'Zhu';
    export {firstName,lastName}

    export var firstName ='Lingyi';
    export var lastName = 'Zhu';

    //函数
    export function multiply(x,y){
    return x*y;
    };
  • 通常情况下,export输出的变量是原本的名字,但是可以用as关键字来重新命名

    1
    2
    3
    4
    5
    6
    7
    8
    function v1(){...}
    function v2(){...}

    export{
    v1 as streamV1;
    v2 as streamV2;
    v2 as streamLatestVersion; //v2可以使用不同的名字输出两次
    }
  • export模块暴露的接口必须与内部元素建立起一一对应的关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    export 1;//报错
    export var m =1;//正确

    var m =1;
    export m;//报错
    export {m};//正确

    var n = 1;
    export {n as m}; //正确,用m来接收
  • 关于整体暴露的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //temp.js
    export function plus(x,y){
    return x+y;
    }
    eport function multi(x,y){
    return x*y;
    }

    //main.js
    import {plus,multi} from 'temp.js'
    let a = plus(1,2);
    let b = multi(1,2);

    //也可以写为
    import * as Math from 'temp.js'
    let a = Math.plus(1,2);
    let b = Math.multi(1,2);
  • 关于动态绑定,export接口与对应的元素是动态绑定关系,可以通过该接口获取该元素内部实时、后面变化的值

    1
    2
    export var foo = '没变之前就先暴露出去';
    settimeout(()=>{foo='暴露之后变化成现在这个值,5秒后别的文件读也会变成这个值'},5000);
  • 关于继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //circleplus.js
    export * from 'circle';
    export var e = 2.7;
    export default function(x){
    return Math.exp(x);
    }
    export {area as circleArea} from 'circle';

    //main.js
    import * as math from 'circleplus';
    import exp from 'circleplus'; //将circleplus模块中的默认方法加载为exp方法
    console.log(exp(math.e));

  • 关于export default

    • 为了用户快速上手,可以不命名元素
    • 本质是系统自动给default后面的元素赋值为default
    • 用户可以import时,设置元素的名称
    • 用于指定模块的默认输出,一个模块只能有一个默认输出,因此 export default模块只能使用一次
  • 关于const变量跨模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //constants.js
    export const A = 1;
    export const B = 2;
    export const C = 3;

    //test1.js 写法1
    import * as constants from 'constants.js'
    console.log(constants.A); //1
    console.log(constants.B);//2

    //test2.js 写法2 加载单一的输出项
    import {A,C} from 'constants.js.'
    console.log(A);//1
    console.log(B);//3

    在使用的变量非常多的时候,常常专门建立一个constants目录,将常量写在歌中不同的文件里,然后合并在index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //本代码块包含两个文件的代码,分别是db.js和user.js
    //constants/db.js
    export const db = {
    url: 'http://my.couchdbserver.local:5984',
    admin_username: 'admin',
    admin_password: 'admin password'
    };

    // constants/user.js
    export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
    1
    2
    3
    //index.js  将上述两个js文件中的常量合并
    export {db} from 'db.js'
    export {users} from 'users.js' //跨模块常量的引入,带{}
  • export可以出现在任何位置,只要处于模块顶层就行。在块级作用域中则会报错。

3.ES6中import
  • 返回一个Promise对象,需要用.then()方法指定处理函数,推荐使用await方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    async function renderWidget() {
    //async封装Promise,下面await
    const container = document.getElementById('widget');
    if (container !== null) {
    // 等同于
    // import("./widget").then(widget => {
    // widget.render(container);
    // });
    const widget = await import('./widget.js'); //await相当于Promise中的then(),所以前面说等同于
    widget.render(container);
    }
    }

    renderWidget();
  • import

    • 按需加载,什么时候需要,什么时候加载

    • 条件加载,可以放在if模块中

    • 允许加载模块的路径是动态生成的 import(function(var))

    • 加载成功后,该模块会当作一个对象,作为.then()的参数

      1
      2
      3
      4
      5
      //如果有default模块的话
      import('./myModule.js')
      .then(myModule => {
      console.log(myModule.default);
      });
    • 加载方式

      • ```html
        1
        2
        3
        4
        5
        6

        + ```html
        <script type='module'>
        import foo from 'foo.js'
        ...
        </script>
4. ES6模块和Common JS模块的差异
  • ES6模块是对外接口的一种静态定义,在代码静态解析阶段就会完成
  • ES6模块输出的是值的引用,模块中变化了输出也变化了。
  • Common JS模块加载的是一个对象,该对象在脚本运行时才会生成
  • Common JS模块输出是一个值的拷贝,内部变化,不会影响已经输出的值。
  • 其他区别Module 的加载实现
5.Common JS加载ES6模块
  • CommonJS模块的require()不能加载ES6模块,只能用import()

    1
    2
    3
    (async () => {
    await import('./my-app.mjs');
    })();
  • require()不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await命令,导致无法被同步加载。

6.ES6加载CommonJS模块
  • ES6需要静态代码分析,而CommonJS输出的是一个对象,无法被静态解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 正确 只能整体加载
    import packageMain from 'commonjs-package';

    // 报错 不能只加载单一的输出项
    import { method } from 'commonjs-package';

    //加载输出单一项的写法(折中)
    import packageMain from 'commonjs-package';
    const { method } = packageMain;

深入了解ES6点module export

随时查的技巧总结

变量、字符串、数值、数组、函数


ES6笔记
https://anonymouslosty.ink/2022/09/08/ES6笔记/
作者
Ling yi
发布于
2022年9月8日
更新于
2022年9月21日
许可协议