乐趣区

关于javascript:深入12-前端模块化

导航

[[深刻 01] 执行上下文](https://juejin.im/post/684490…
[[深刻 02] 原型链](https://juejin.im/post/684490…
[[深刻 03] 继承](https://juejin.im/post/684490…
[[深刻 04] 事件循环](https://juejin.im/post/684490…
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…
[[深刻 08] 前端平安](https://juejin.im/post/684490…
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…
[[深刻 11] 前端路由](https://juejin.im/post/684490…
[[深刻 12] 前端模块化](https://juejin.im/post/684490…
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…
[[深刻 14] canvas](https://juejin.im/post/684490…
[[深刻 15] webSocket](https://juejin.im/post/684490…
[[深刻 16] webpack](https://juejin.im/post/684490…
[[深刻 17] http 和 https](https://juejin.im/post/684490…
[[深刻 18] CSS-interview](https://juejin.im/post/684490…
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…
[[深刻 20] 手写函数](https://juejin.im/post/684490…

[[react] Hooks](https://juejin.im/post/684490…

[[部署 01] Nginx](https://juejin.im/post/684490…
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…
[[部署 03] gitlab-CI](https://juejin.im/post/684490…

[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…
[[源码] Redux React-Redux01](https://juejin.im/post/684490…
[[源码] axios ](https://juejin.im/post/684490…
[[源码] vuex ](https://juejin.im/post/684490…
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…

前置常识

js 中省略每行结尾的 ; 分号时,须要留神的问题

  • <font color=red>() 小括号结尾的前一条语句,小括号前必须加分号,或者在前一条语句完结时加分号 </font>
  • <font color=red>[] 中括号结尾的前一条语句,中括号前必须加分号,或者在前一条语句完结时加分号 </font>
js 中省略每行结尾的 ; 分号,须要留神的问题:- () 小括号结尾的前一条语句,小括号前必须加分号,或者在前一条语句完结时加分号
- [] 中括号结尾的前一条语句,中括号前必须加分号,或者在前一条语句完结时加分号


例子:(1) () 小括号结尾的前一条语句,小括号前要加 ';' 分号,或者前一条语句结尾加分号
var a = 1
(function() { // 报错:Uncaught TypeError: 1 is not a function
  console.log(a)
})()
解决办法:var a = 1
;(function() {   <---------
  // 在 () 前加分号
  // 或者在 var a = 1; 结尾加分号 
  console.log(a)
})()

(2) [] 中括号结尾的前一条语句,须要在 [] 后面加上 ';' 分号,或者前一条语句结尾加分号
var a = 1
[1,2,3].forEach(item => console.log(item)) // Uncaught TypeError: Cannot read property 'forEach' of undefined
解决办法:var a = 1
;[1,2,3].forEach(item => console.log(item))    <---------

作用域

  • 作用域:指变量存在的范畴
  • 作用域分为:<font color=red> 全局作用域,函数作用域,eval</font>

    <script>
    var a = 1
    var c = 2
    function x() {
      var a = 10 // 全局中也有 a,然而函数作用域申明的 a 不会影响全局
      var b = 100
      c = 20 // 然而函数外部能批改全局的变量,(作用域链外部能批改内部的变量)}
    x()
    console.log(a) // 1
    console.log(c) // 20
    ;(function() {// 留神分号是必须的,因为 ()[] 结尾的前一条语句开端,或者 ()[] 结尾加 ;
      console.log(c) // 20
    })()
    console.log(b) // 报错,b is not defined 函数内部不能拜访函数外部的变量
    </script>

浏览器解析 js 文件的流程

浏览器加载 javascript 脚本,次要通过 script 标签 来实现

  • (1) 浏览器一边下载 html 文件,一边开始解析 就是说:不等到 html 下载实现,就开始解析
  • (2) 解析过程中,遇到 script 标签就暂定解析,把网页的渲染控制权交给 javascript 引擎
  • (3) 如果 script 标签援用了 内部脚本 ,则 下载脚本并执行 ;如果 没有 间接执行script 标签内的代码
  • (4) javascript 引擎 执行结束 ,控制权交还 渲染引擎 ,恢復 解析 html 网页

    浏览器解析 js 文件流程
    
  • html 是一边下载,一边解析的
  • script 标签会阻塞 html 解析,如果耗时较长,就会呈现浏览器假死景象
  • script 标签之所以会阻止 html 解析,阻止渲染页面,是因为 js 可能会批改 DOM 树和 CSSOM 树,造成简单的线程比赛和控制权竞争的问题

js 同步加载,js 异步加载

  • <font color=red> 同步加载:阻塞页面 </font>
  • <font color=red> 异步加载:不会阻塞页面 </font>

script 标签 异步加载的形式 async defer

defer:是提早的意思

  • (1) script 标签搁置在 body 底部

    • 严格说不算异步加载,然而这也是常见的通过扭转 js 加载形式来晋升页面性能的一种形式
  • (2) <font color=red>defer 属性 </font>

    • 异步加载,不阻塞页面,在 DOM 解析实现后才执行 js 文件
    • 程序执行,不影响依赖关系
  • (3) <font color=red>async 属性 </font>

    • 异步加载,加载不阻塞页面,然而 async 会在异步加载实现后,立刻执行,如果此时 html 未加载完,就会阻塞页面
    • 留神:异步加载,加载不会阻塞页面,执行会阻塞页面
    • 不能保障各 js 文件的执行程序
  • defer(1)加载:是异步加载,加载不阻塞页面;(2)执行:要 DOM 渲染完才执行,能保障各 js 的执行程序
  • async(1)加载:是异步加载,加载不阻塞页面;(2)执行:加载完立刻执行,不能保障各 js 的执行程序

    bgcolor=orange>

  • <font color=red>defer,async,放在 body 底部,三种办法哪种好?</font>

    • 最稳当的方法还是把 <script> 写在 <body> 底部,没有兼容性问题,没有白屏问题,没有执行程序问题

tree 命令 – 生成目录构造

tree [<Drive>:][<Path>] [/f] [/a]

tree 命令生成目录构造


tree [<Drive>:][<Path>] [/f] [/a]
/f:显示每个目录中文件的名称
/a:应用文本字符而不是图形字符连贯

例子:C:.
│  index.html
│
├─a
│  └─b
│          ccc.js
│
└─requirejs
        a.js
        b.js
        c.js

立刻调用的函数表达式 IIFE

IIFE Immediately-invoked Function Expressions 立刻调用的函数表达式
(function(){...})()(function(){...}())

  • 需要:在函数定义后,立刻调用该函数
  • 呈现问题:<font color=red> 如果间接在函数前面加上括号去调用,就会报错 </font>

    • 报错:function(){}();
  • 报错起因:<font color=red>function 关键词呈现在行首,会被 js 解析成语句(即函数的定义),不应该以圆括号结尾,所以报错 </font>
  • 如何解决:IIFE 利用立刻调用的函数表达式去解决

    • 即让其成为一个表达式,而不是语句
    • <font color=red> 语句不能以圆括号结尾,然而表达式能够 </font>
  • 须要把握的知识点:

    • (1) IIFE 如何传参
    • (2) 多个 IIFE 一起时,分号不能省略
    • (3) IIFE 不会净化全局变量,因为不必为函数命名
    • (4) IIFE 能够造成一个独自的作用域名,则能够封装一些内部无奈读取的变量
    • (5) IIFE 的两种写法
    IIFE 立刻调用的函数表达式
    
    须要了解的几个方面:(1) IIFE 如何传参 - (作为模块时,依赖项就是通过参数传递实现的)
    (2) 多个 IIFE 一起时,分号不能省略
    (3) IIFE 不会净化全局变量,因为不必为函数命名
    (4) IIFE 能够造成一个独自的作用域名,则能够封装一些内部无奈读取的变量
    (5) IIFE 的两种写法
    
    
    ---------------
    案例:const obj = {name: 'woow_wu7'};
    
    (function(params) { // params 形参
    console.log(obj) // 这里拜访的 obj,不是函数参数传入的,而是拜访的父级作用域的 obj
    console.log(params)
    })(obj); // obj 是实参
    // (1)
    // 留神:这里开端的分号是必须的,因为是两个 IIFE 间断调用
    // 打印:都是 {name: 'woow_wu7'}
    // (2)
    // IIFE 的两种写法:// 1. (function(){...})()
    // 2. (function(){...}())
    // 下面的 (1)(2) 都会被 js 了解为表达式,而不是语句
    // 表达式能够以圆括号结尾,函数定义语句不能以圆括号结尾
    // (3)
    // 因为 function 没有函数名,防止了变量名净化全局变量
    
    (function(params2){console.log(obj)
    console.log(params2)
    }(obj))

前端模块化

模块的概念

  • 将一个简单程序的各个局部,依照肯定的 规定 (标准) 封装不同的 块(不同的文件),并组合在一起
  • <font color=red> 块 外部的变量和办法是公有的,只会向外裸露一些接口,通过接口与内部进行通信 </font>

非模块化存在的问题

  • 对全局变量的净化
  • 各个 js 文件外部变量相互批改,即只存在全局作用域,没有函数作用域
  • 各个模块如果存在依赖关系,依赖关系含糊,很难分清谁依赖谁,而依赖又必须前置
  • 难以保护

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      var me = 'changeMe';
      console.log(window.me, '全局变量未被批改前的 window.me') // changeMe
    </script>
    <script src="./home.js"></script> <!-- var home = 'chongqing' -->
    <script src="./me.js"></script> <!-- var me = 'woow_wu7' -->
    <script src="./map.js"></script> <!-- var map = 'baidu' -->
    </head>
    <body>
    <script>
      console.log(window.me, '全局变量被批改后的 window.me') // 'woow_wu7' 阐明净化了全局变量
      console.log(map, '模块内变量 map 被批改前') // baidu
      var map = 'Amap'
      console.log(map, '别的模块内 mao 竟然被批改了') // Amap 阐明模块内的变量被批改了,因为只有全局作用域
    </script>
    </body>
    </html>

模块化的益处

能够先记忆一下,有个概念

  • 更好的拆散:防止一个 html 搁置多个 script,只需引入一个总的 script
  • 防止命名抵触:模块内的变量不会影响到模块外,即各个模块能够有雷同命令的变量
  • 更好的解决依赖:每个模块只用放心本人所依赖的模块,不必思考其余模块的依赖问题,<font color=red> 如果在一个 html 中引入很多 script,各个模块 (script) 的依赖关系是很难分分明的 </font>
  • 更利于保护

模块化须要解决的问题

  • 模块的平安装置,即不能净化任何模块外的代码
  • 惟一的标识每个模块
  • 优雅的裸露 api,不能减少任何全局变量
  • 能够援用其余依赖

<font color=red> 模块化不同计划比照 </font>

IIFE,CommonJS 标准,AMD,CMD,ES6 的模块化计划
commonjs -------------------------------- node.js 应用的标准
AMD:Asynchronous Module Definition ----- 异步模块定义
CMD:Common Module Definition ----------- 通用模块定义

  • commonjs 用于服务端,同步加载 —————————— node.js 应用的规范
  • AMD 和 CMD 次要用于浏览器端,异步加载
  • ES6 的模块计划:用于浏览器和服务器,通用计划,动态化
  • AMD 依赖前置,依赖必须一开始写好,提前加载依赖 (依赖前置,提前执行) ——– RequireJS
  • CMD 依赖就近,须要应用的时候,才去加载依赖 (依赖就近,延时执行) ————- seajs
  • ES6 模块是动态化的,在 (编译时) 就能确定模块得依赖关系,输出,输入;而 AMD 和 CMD 只能在运行时能力确定

  • 2021/3/23 补充

    • ES6 的模块化计划 => 动静更新
    • CommonJS => 模块输出的是 (值的缓存),不存在动静更新
    • ES6 的模块中

      • export {a, b}; export 前面的花括号并不是对象,而是一个接口,这个接口中有 a 和 b 两个变量
    • CommonJS 中

      • module.export = {a: 1}; 导出的就是一个对象

        es6 模块
        -------
        
        export var foo = 'bar';
        setTimeout(() => foo = 'baz', 500);
        
        下面代码输入变量 foo,值为 bar,500 毫秒之后变成 baz

模块化的倒退历程

(1)原始阶段 – 只用函数作用域

var a1 = 'a1'
function a1() {}
function a2() {}

毛病:函数名会净化全局变量

(2)对象封装

var a3 = 'a3'
var a1 = {a2: function() {}
    a3: function() {}
}

长处:1. a1 对象的 a3 属性不会净化全局变量 a3
2. 缩小了全局作用域内的变量数量:- 这里只有 a3,a1 ------- 2 个
    - 而全用函数:---------- 3 个
毛病:1. 还是会净化全局变量
2. 内部能够批改 a1 的属性,即会裸露所有属性并且能够被内部批改

(3)用 IIFE(立刻调用的函数表达式) 实现模块化

IIFE Immediately-invoked Function Expressions

  • IIFE 实现的模块化能解决的问题:

    • <font color=red> 在其余中央都要不能批改模块外部的变量 </font>

      • 不能在其余中央批改模块内的变量,阐明每个模块都有本人的(<font color=red> 独自的作用域 </font>),<font color=red> 内部无法访问 </font>
      • 函数就具备(<font color=red> 函数作用域 </font>),函数内部无法访问函数外部的变量
    • <font color=red> 模块内的变量不能净化全局变量 </font>

      • 即模块内的变量的作用域不能是(全局作用域),则能够用函数来解决(函数作用域)
      • 什么叫不能净化全局变量:

        • 即不能变量笼罩,模块内的变量不能笼罩全局的变量,从而影响全局变量
    • <font color=red> 防止间接应用函数,函数名净化全局变量 </font>
    • <font color=red>IIFE 实现的模块化,依赖其余模块,可用传参来解决 </font>
    • <font color=red> 模块须要裸露的办法和变量,都能够挂载都 window 上 </font> => 衡量全局变量净化问题,能够应用特殊符号防止
    用 IIFE(立刻调用的函数表达式) 实现模块化
    
    
    须要解决的问题:(1) 各个模块中定义的变量不能在模块外被批改,只能在该模块内批改,则每个模块须要造成独自的作用域
    (2) 模块内的变量不能净化全局变量 => 即不能在同一个作用域,用函数能够解决
    
    
    ------
    未解决以上问题前的模块:<!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      var me = 'changeMe';
      console.log(window.me, '全局变量未被批改前的 window.me') // changeMe
    </script>
    <script src="./home.js"></script> <!-- var home = 'chongqing' -->
    <script src="./me.js"></script> <!-- var me = 'woow_wu7' -->
    <script src="./map.js"></script> <!-- var map = 'baidu' -->
    </head>
    <body>
    <script>
      console.log(window.me, '全局变量被批改后的 window.me') // 'woow_wu7' 阐明净化了全局变量
      console.log(map, '模块内变量 map 被批改前') // baidu
      var map = 'Amap'
      console.log(map, '别的模块内 mao 竟然被批改了') // Amap 阐明模块内的变量被批改了
    </script>
    </body>
    </html>
    
    
    ------
    IIFE 实现的模块化:<!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      (function(window,$) {
        var me = 'changeMe';
        console.log(me) // changeMe
        window.me = me
      })(window, jquery) 
      // 该模块依赖 jquery
      // 须要裸露的变量,能够挂载到 window 对象上
    </script>
    </head>
    <body>
    <script>
     console.log(me, '内部无法访问,报错') 
     // me is not defined
    </script>
    </body>
    </html>

(4)CommonJS 标准

  • <font color=red>Nodejs 采纳 CommonJS 标准,次要用于服务端,同步加载 </font>
  • 同步加载

    • nodejs 次要用服务端,加载的模块文件个别都存在硬盘上,加载起来比放慢,不必思考异步加载的形式
    • 但如果是浏览器环境,要从服务器加载模块,就必须采纳异步形式,所以就有了 AMD CMD 计划
  • <font color=red>module 示意以后模块,module.exports 是对外的接口,require 一个模块其实就是加载 module.exports 属性 </font>

    • 留神:在 node.js 中 moudle.exports 和 exports 的区别?
    
    nodejs 中 moudle.exports 和 exports 的区别
    
    
    案例:--- modu.js ---
    const a = 11;
    module.exports = a ---------------------------------- module.exports 裸露模块
    
    --- modu2.js ---
    const b = 22;
    exports.bVar = b ------------------------------------ exports 裸露模块
    
    --- index.js ---
    const a = require('./modu.js') ---------------------- require 引入模块
    const b = require('./modu2.js')
    console.log(a, 'a') // 11
    console.log(b, 'b') // {bVar: 22}
    console.log(b.bVar, 'b.bVar') // 22

(5) AMD – Asynchronous Module Definition 异步模块定义 <font color=red>// RequireJS</font>

  • AMD 用于浏览器端,异步加载,依赖前置
  • <font color=red> 浏览器端不能应用 commonjs 同步加载计划 </font>

    • <font color=red> 是因为浏览器端加载 js 的文件在服务器上,须要的工夫较长,同步加载会阻塞页面的加载和渲染 </font>
    • <font color=red> 而对于服务器端,文件则在硬盘中,加载和读取都非常快,所以能够同步加载,不必思考加载形式 </font>
  • RequireJS

    RequireJS
    
    (1) 目录构造:C:.
    │  index.html
    │
    └─requirejs
          b.js
          c.js
    
    (2) 例子
    b.js
    define(function () {// ----------------- define(function(){...}) 定义一个模块
    return 'string b'
    })
    
    c.js
    define(['./b.js'], function(res) {// --- define(['a'], function(res){...}) 定义一个有依赖的模块,c 依赖模块 b
    return res + 'c'
    });
    
    index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
    </head>
    <body>
    <script>
      require(['./requirejs/c.js'], function(res) { // 引入模块,并应用模块裸露的值,res 就是模块 c 裸露的值
        console.log(res, 'res')
      })
    </script>
    </body>
    </html>

(6) CMD – Common Module Definition 通用模块定义 <font color=red>// seajs</font>

  • CMD 用于浏览器端,异步加载,依赖就近,即应用时才会去加载
  • CMD 是 SeaJS 在推广过程中对模块定义的规范化产出

    // 定义没有依赖的模块
    define(function(require, exports, module){
    var value = 1
    exports.xxx = value
    module.exports = value
    })
    
    // 定义有依赖的模块
    define(function(require, exports, module){var module2 = require('./module1') // 引入依赖模块(同步)
    require.async('./module2', function (m3) {// 引入依赖模块(异步)
    })
    exports.xxx = value // 裸露模块,也能够用 module.exports
    })
    
    // 引入应用模块
    define(function (require) {var a = require('./module1')
    var b = require('./module2')
    })

(7) ES6 中的模块计划

  • <font color=red>ES6 的模块化计划作为通用计划,能够用于浏览器端和服务器端 </font>
  • <font color=red>ES6 中的模块化计划,设计思维是动态化,即在编译时就能确定模块的依赖关系,输出变量,输入变量;而 CommonJS 和 AMD 和 CMD 都只有在运行时能力确定依赖关系,输出和输入 </font>

    • CommonJS 模块就是对象,输出时 (引入模块) 必须查找对象属性
  • CommonJS 是运行时加载,因为只有运行时能力生成对象,从而失去对象,才能够拜访对象的值
  • ES6 模块不是对象,而是通过 exports 显示输入的代码,通过 import 输出
  • ES6 的模块,默认采纳严格模式

    // CommonJS 模块
    let {stat, exists, readFile} = require('fs');
    (1) 本质上是整体加载模块 fs,在 fs 对象上再去读取 stat,exists 等属性
    (2) 像 CommonJS 这种加载形式成为运行时加载,因为只有运行时能力失去这个对象
    
    
    
    // ES6 模块
    import {stat, exists, readFile} from 'fs';
    (1) 本质是从 fs 模块加载 3 个办法,其余办法不加载 - 称为编译时加载或者动态加载
    (2) ES6 在编译时实现加载,而不须要像 CommonJS,AMD,CMD 那样运行时加载,所以效率较高
      - 这会导致没法援用 ES6 模块自身,因为它不是对象
    
    
    
    // ES6 模块的益处
    (1) 动态加载,编译时加载 ----- 效率较高,能够实现 (类型查看) 等只能靠 (动态剖析) 实现的性能
    (2) 不再须要 (对象) 作为(命名空间),将来这些性能能够通过模块提供
  • export 命令

    • 模块的性能次要由两个命令形成:import 和 export
    • <font color=red>export 能够输入变量,函数,类 </font>
    • export 输入的变量,就是变量原本的名字,然而能够通过 <font color=red>as</font> 关键字来重命名
    // 报错
    export 1;
    
    // 报错
    var m = 1;
    export m;
    
    // 写法一
    export var m = 1;
    
    // 写法二
    var m = 1;
    export {m};
    
    // 写法三
    var n = 1;
    export {n as m};
    
    // 报错
    function f() {}
    export f;
    
    // 正确
    export function f() {};
    
    // 正确
    function f() {}
    export {f};
  • 模块的整体加载

    • 除了指定加载某个输入值,还能够应用整体加载。
    • 即用 <font color=red>*</font> 指定一个对象,所有输入值都加载到这个对象下面
  • export default

    • import 须要直到函数名或变量命,否则无奈加载,– export default 指定模块的默认输入
    • <font color=red>export default 其实时输入 default 的变量,所以他前面不能跟变量的申明语句 </font>
    // 正确
    export var a = 1;
    
    // 正确
    var a = 1;
    export default a; ---------------->  export default a : 意思是把变量 a 赋值给 default 变量
    
    // 谬误
    export default var a = 1
    
    // 正确
    export default 42; --------------->  留神:能够将值赋值给 default 变量,对外的接口是 default
    
    // 报错
    export 42; ----------------------->  没有指定对外的接口
  • <font color=red>export 与 import 的复合写法 </font>

    export {foo, bar} from 'my_module';
    
    // 能够简略了解为
    import {foo, bar} from 'my_module';
    export {foo, bar};

import()函数 – 反对动静加载模块

  • 因为 ES6 模块化计划是动态加载,即编译时就能确定依赖关系,输出,输入;不必等到运行时
  • <font color=red> 那如何做到 动静加载 ?</font>
  • <font color=red>import(specifier) ———- specifier:说明符 </font>
  • <font color=red>import()返回一个 promise</font>

    • import()相似于 Node 的 require 办法,区别次要是前者是异步加载,后者是同步加载。
    • 它是 <font color=red> 运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。</font>
    import() 语法
    
    
    (1) 按需加载:在须要的时候,再加载某个模块
    
    (2) 条件加载:能够依据不同的条件加载不同的模块,比方 if 语句中
    
    (3) 动静的模块门路:容许模块门路动静生成
    import(f()).then(...); // 据函数 f()的返回值,加载不同的模块。(4) import()加载模块胜利当前,这个模块会作为一个对象,当作 then 办法的参数。// 因而,能够应用对象解构赋值的语法,获取输入接口。// import('./myModule.js').then(({export1, export2}) => {...});
    
    (5) 如果模块有 default 输入接口,能够用参数间接取得。// import('./myModule.js').then(myModule => {console.log(myModule.default)});
    // 下面 myModule 模块具备 defalut 接口,所以能够用 (参数.default) 获取
    
    
    (6) 同时加载多个模块
    Promise.all([import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {···});

材料

具体 (模块化) 真的写得好:https://juejin.im/post/684490…
超残缺 (模块化):https://juejin.im/post/684490…
js 中哪些状况不能省略分号:https://blog.csdn.net/BigDrea…
ES6 模块化计划:http://es6.ruanyifeng.com/#do…
我的语雀:https://www.yuque.com/woowwu/…
模块化参考资料 https://juejin.cn/post/684490…

退出移动版