最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    正文概述 掘金(大汪bluesboneW)   2021-02-27   489

    1 模块

    1.1 什么是模块?什么是模块化?

    玩过FPS游戏的朋友应该知道,一把装配完整的M4步枪,一般是枪身+消音器+倍镜+握把+枪托

    如果把M4步枪看成是一个页面的话,那么我们可以做如下类比 枪身 -> <main></main> 消音器 -> <header></header> 倍镜 -> <nav></nav> 握把 -> <aside></aside> 枪托 -> <footer></footer>

    OK,你刚才做了一件事情,就是把m4步枪拆成了五个部分,你拆分的每一个部分就是一个模块【module】,你拆分的这个过程就是模块化【modularization】

    模块化是一种编程思想,其核心就是拆分任务,把复杂问题简单化,这样一来既方便多人分工协作,又可以帮助我们迅速定位问题

    • 方便多人分工协作 —— 可以不同的人开发不同的模块,再组合,大大增加团队效率
    • 帮助我们迅速定位问题 —— 后坐力太大,那八成是枪托或握把的问题;声音过大,那八成是消音器的问题。

    1.2 模块化的血泪史

    下面用一个小栗子讲一讲模块化的发展史

    于是他们俩一合计,安排龚先生的代码单独放在script1.js里写,棚先生的代码单独放在script2.js里写,然后用script标签分别引入

    // script1.js文件
    
    var a = 1
    console.log(a)
    
    // script2.js文件
    
    var a = 2
    console.log(a)
    
    <!--HTML文件-->
    
    <script src="./script1.js"></script>
    <script src="./script2.js"></script>
    

    很快他们遇到了第一个问题 —— 变量命名冲突 尤其是包含了异步的时候,会出现如下情况

    // script1.js文件
    
    var a = 1
    setTimeout(()=>{
      console.log(a)  // 我们想console出来1,却console出了2
    },1000)
    
    // script2.js文件
    
    var a = 2
    console.log(a)
    

    上面的问题明显是由于a是一个全局变量导致的,所以解决思路也很明确——造一个局部变量呗

    局部变量

    // script1.js文件
    !function(){
        var a = 1
        setTimeout(()=>{
          console.log(a)  // 这下是2了
        },1000)
    }()
    // 下面有5000行代码
    
    // script2.js文件
    
    console.log(2)
    
    // script1.js文件
    {
        let a = 1
        setTimeout(()=>{
          console.log(a)  // 这下是2了
        },1000)
    }
    
    // script2.js文件
    {
        let a = 2
        console.log(a)
    }
    

    通过window连接各个模块

    后来公司招了一个前端大佬,说现在只能由他来控制什么时候console变量,于是他新建了一个control.js文件 并通过window对象连接script1.js和scirpt2.js

    // script1.js文件
    {
        let a = 1
        window.module1 = function() {
            console.log(a)
        }
    }
    
    // script2.js文件
    {
        let a = 2
        window.module2 = function() {
            console.log(a)
        }
    }
    
    // control.js文件
    setTimeout(()=>{
        window.module1()
    },1000)
    
    window.module2()
    
    // script1.js文件
    {
        let a = 1
        // 把这个函数存放进window,就是导出到window
        window.module1 = function() {
            console.log(a)
        }
    }
    
    // script2.js文件
    {
        let a = 2
        window.module2 = function() {
            console.log(a)
        }
    }
    
    // control.js文件
    setTimeout(()=>{
        // 我们从window里取出module1函数进行调用,就是依赖了script1.js文件
        window.module1()
    },1000)
    
    window.module2()
    

    依赖加载的顺序

    烦人的产品对需求又进行了更改,给了一个name.js文件

    // name.js文件
    window.names = ['gongxiansheng','pengxiansheng']
    

    要求现在龚先生和棚先生需要Console出自己的名字 这还不简单?几秒钟写好

    // script1.js文件
    {
        window.module1 = function() {
            console.log(window.names[0])
        }
    }
    
    // script2.js文件
    {
        window.module2 = function() {
            console.log(window.names[1])
        }
    }
    
    // control.js文件
    setTimeout(()=>{
        window.module1()
    },1000)
    
    window.module2()
    
    <!--HTML文件-->
    
    <script src="./script1.js"></script>
    <script src="./script2.js"></script>
    <script src="./control.js"></script>
    <script src="./name.js"></script>
    

    但很快他们发现,console出来的都是undefined 前端大佬一眼看出了问题,对他们俩说 你们依赖的代码一定要在你们自己的代码前引入,不然是取不到值的;你看我的control.js是不是在你们俩的代码后面引入的,因为我用到了你们俩的代码了呀 噢噢,原来是js文件加载顺序问题,改一下吧

    <!--HTML文件-->
    
    <script src="./name.js"></script>
    <script src="./script1.js"></script>
    <script src="./script2.js"></script>
    <script src="./control.js"></script>
    

    但是在人多了以后,我们到时候会搞不清楚到底谁依赖了谁,保险起见只能全部都加载,性能浪费了太多,前端大佬摇头叹息道

    1.3 血泪史总结

    1. 变量冲突问题,使用了局部作用域解决

    2. 要用window连接各个模块

    3. 依赖需要全部加载

    4. 还要TMD注意加载顺序

    2 CommonJS

    2.1 应用于Server端的CommonJS

    2.1.1 为什么只有Server端使用CommonJS

    CommonJS描述的是**同步加载**的模块定义,能够一次性把所有模块加载到内存

    CommonJS的require语法是同步的,当我们使用require加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码

    2.1.2 CommonJS的使用细节

    // moduleB.js
    let a = 100;
    
    module.exports = {
        a
    }
    
    // moduleA.js
    let mb = require('./moduleB');
    console.log(mb.a); // 100
    
    // moduleB.js
    let a = 100;
    console.log('moduleB被加载了'); // 注意这一句只会执行一次
    
    module.exports = {
        a
    }
    
    // mb1和mb2都只是指针,指向内存中被缓存的moduleB
    let mb1 = require('./moduleB');
    let mb2 = require('./moduleB');
    console.log(mb1);
    console.log(mb2);
    mb1.a = -1;
    console.log(mb1);
    console.log(mb2); // 修改了mb1,就是修改了mb2
    

    运行结果

    ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    // moduleB.js
    let cnt = 0;
    
    let cntObj = {
        num: 0
    };
    
    function counter() {
        cnt++; // 只能影响moduleB里的cnt,无法影响export出去的cnt,因为浅拷贝
        console.log('counter', cnt);
    }
    
    function counterObj() {
        cntObj.num++;
        console.log('counterObj', cntObj.num);
    }
    
    module.exports = {
        counter,
        cnt,
        counterObj,
        cntObj
    }
    
    // moduleA.js
    let mb = require('./moduleB');
    console.log('cnt:', mb.cnt, ',cntObj', mb.cntObj);
    mb.counter();
    mb.counterObj();
    console.log('cnt:', mb.cnt, ',cntObj', mb.cntObj);
    

    ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    2.2 简单提一下已经死去的AMD和CMD

    AMD:

    1. AMD是require.JS在推广过程中对模块定义的规范化产出
    2. 被依赖的文件可以早于依赖他的文件加载到浏览器
    3. AMD是一种**异步加载**

    CMD:

    1. CMD是在sea.JS的推广过程中产生的
    2. 提倡**按需加载**

    3 ES6的模块

    3.1 import和export的用法

    3.1.1 export语法

    // 命名导出
    export { name1, name2, …, nameN };
    export { variable1 as name1, variable2 as name2, …, nameN };
    export let name1, name2, …, nameN; // also var
    export let name1 = …, name2 = …, …, nameN; // also var, const
    export function FunctionName() {...}
    export class ClassName {...}
    
    // 默认导出
    export default expression;
    export default function (…) { … } // also class, function*
    export default function name1(…) { … } // also class, function*
    export { name1 as default, … };
    
    // 将其它模块内的导出作为当前文件的导出
    export * from …;
    export { name1, name2, …, nameN } from …;
    export { import1 as name1, import2 as name2, …, nameN } from …;
    

    3.1.2 import用法

    import defaultExport from "module-name"; // 导入默认默认变量
    
    // 将模块内所有变量导出,并挂载到name下【name是一个module对象】
    // 为什么要有as——为了防止export出来的变量命名冲突
    import * as name from "module-name"; 
    
    // 解构赋值系列
    import { export } from "module-name"; // 导入某一个变量
    import { export as alias } from "module-name"; // 导入某一个变量并重命名
    import { export1 , export2 } from "module-name"; // 导入两个变量
    import { export1 , export2 as alias2 , [...] } from "module-name"; // 导入多个变量,同时可以给导入的变量重命名
    import defaultExport, { export [ , [...] ] } from "module-name"; // 导入默认变量和多个其它变量
    import defaultExport, * as name from "module-name"; // 导入默认变量并重新命名
    import "module-name"; // 导入并加载该文件【注意文件内的变量必须要通过export才能被使用】
    var promise = import(module-name); // 异步的导入
    

    3.1.3 使用import和export改写第一节的代码

    // name.js文件
    let names = ['gongxiansheng','pengxiansheng']
    export default names
    
    // script1.js
    import names from './name.js'
    
    let module1 = function () {
      console.log(names[0])
    }
    export default module1
    
    // script2.js
    import names from './name.js'
    
    let module2 = function() {
      console.log(names[1])
    }
    export default module2
    
    // control.js
    import module1 from './script1.js'
    import module2 from './script2.js'
    
    setTimeout(() => {
      module1()
    }, 1000)
    module2()
    
    <!--HTML文件-->
    
    <script type="module" src="./control.js"></script>
    <!--注意一定要加上type="module",这样才会将这个script内的代码当做模块来对待-->
    

    3.2 ES6 module的一些重要细节

    3.2.1 ES6模块输出的是值的引用,输出接口会动态更新

    // module1.js
    export var foo = 'bar';
    setTimeout(() => foo = 'baz', 500);
    
    // module2.js
    import {foo} from './module1.js'
    console.log(foo)
    setTimeout(() => console.log(foo), 1000);
    
    // console的结果
    // bar
    // baz
    

    3.2.2 ES6模块是编译时输出接口

    ES6模块不是对象,它的输出接口只是一种静态定义在代码静态解析阶段就会生成

    这样我们就可以使用各种工具对JS模块进行依赖分析,优化代码:

    • 比如Webpack中的tree shaking实际上就是依赖ES6模块化。

    3.2.3 ES6模块的异步加载方式

    ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    4 总结:CommonJS和ES6 module的异同

    4.1 相同点

    1. 每个模块都有自己的局部作用
    2. 模块是单例,只加载一次

    4.2 不同点

    最重要的两个不同

    • CommonJS模块输出的是一个**值的浅拷贝,ES6 模块输出的是值的引用**
    • CommonJS模块是运行时加载,ES6模块是编译时输出接口
    • CommonJS模块是同步加载,ES6是异步加载,先解析依赖图,依赖图解析完毕才会加载响应模块

    其它不同点:

    • ES6模块默认在严格模式下执行,因此this是undefined

    起源地下载网 » ES6指北【3】——5000字长文带你彻底搞懂ES6模块

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元