• 前端工程化1-模块


    写在前面

    最近一两个月在思考未来自己的职业规划,团队内小伙伴陆陆续续走的走,来的来。大家都处于职业的焦虑之中,我一直都很想进一家大厂,目前仍然没有机会,也没有能力能够进去。毕业已经3年整了,不能继续说打好基础,把所有的业余精力都放在js基础学习上。应该着眼于自己的优势和有竞争力的方面努力,这样才不容易被取代。所以前端工程化是必不可少的深挖领域,以后博客都会产出工程化相关的文章。

    前端工程化是一个很大的议题,基本上工程化体系是离不开webpack和js模块。首先先从模块说起,面试的时候免不了会被问到几个模块相关的问题。例如

    1. 说一说模块化的好处?前端模块化解决了哪些问题?
    2. 说一说es module和commonjs module的区别?commonjs require函数的工作原理?
    3. module.export、export 与 export defalut 有什么区别?
      ...
      ...
      ...

    js模块介绍

    模块化就是将一个大文件拆分成相互依赖的小文件,再通过打包工具进行统一的拼装和加载。有人会问了模块化的好处是什么,或者说为什么要做模块化?其实这样做的好处可以从两个角度分析

    1. 前端开发人员可以遵循统一标准规范,多人协作同时进行系统开发,加快开发速度
    2. 代码稳定,模块间相互隔离,相互独立,互不影响。解决命名冲突等。
    3. 代码可读性:代码被分割成细小的整体,可读性变强。
    4. 可复用性:模块间相互独立,互不影响,可复用性高。
    5. 可维护性:依赖关系明确清晰,易于维护。

    js的模块标准有两种 commonjs module和es6之后开始支持的es module。node应用采用的commonjs module规范,web应用一般采用es module规范。

    commonjs module和es module

    commonjs module简称cjs,es module简称mjs,下同。
    示例源码

    cjs

    • require函数,用于导入
    • module.exports或者exports变量,用于导出
      例如
    //add.js
    function add(a,b){
      return a+b;
    }
    
    module.exports={add};
    
    //index.js
    const {add} =require('./add');
    add(1,2);
    

    模块的导入导出需要注意的问题

    • exports不能直接对其赋值,例如下代码是不会正常导出的
    //add.js
    function add (a,b){
      return a+b
    };
    exports={add};
    
    • 在同一个文件中,exports和module.exports尽量不要同时使用,例如下代码
    exports.add=function(a,b){
      return a+b;
    }
    module.exports={
      name:'jack'
    }
    
    • module.exports或者exports尽量放在文件的末尾,可以提高代码的可读性
    • require函数是CommonJS规范之中,用来加载其他模块的函数。它其实不是一个全局函数,而是指向当前模块的module.require函数,而后者又调用Node的内部命令Module._load。
    • require函数有缓存,当第一次加载某个模块时,node会缓存该模块,以后再加载该模块时,就直接从缓存中取出该模块的module.exports属性
    • require函数的加载规则,根据参数的不同格式,require命令去不同路径寻找模块文件
      1. 如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。
      2. 如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件
      3. 如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)
      4. 如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require('example-module/path/to/file'),则将先找到example-module的位置,然后再以它为参数,找到后续路径
      5. 如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索。.js件会以文本格式的JavaScript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析
      6. 如果想得到require命令加载的确切文件名,使用require.resolve()方法

    mjs

    • import命令用于导入
    • export(命名导出)或者export default(默认导出)命令用于导出
      例如
    //add.js
    function add(a,b){
      return a+b;
    }
    export {
      add
    }
    
    // index.js
    import {add} from './add';
    add(1,2);
    

    到这里可以思考一个问题,
    我们可以直接export default一个字面量对象吗?在导入的时候能解构到想要到值吗?来看下下面的代码

    //add.js
    function add(a,b){
      return a+b
    }
    export default {
      add
    }
    
    // index.js
    import {add} from "./add.mjs";
    add(1,2);
    

    上面的代码其实是会报错的,报错的文件是index.js,export default {...}这种写法是没有不会报错的,只是不规范。那为啥index.js中解构的形式导出会报错呢,我们先来看看报的什么错误

    import { add } from './add.mjs';
             ^^^
    SyntaxError: The requested module './add.mjs' does not provide an export named 'add'
    

    看报错信息说没有请求的模块中export没有提供add。既然这样,我们就直接给请求到的add模块赋一个值,然后我们打印出来看看究竟是一个什么东西,add模块的代码不变,index.js修改后的代码如下

    // index.js
    import util from "./add.mjs";
    cosnole.log(util);
    
    

    运行之后我们可以看到控制台输出

    > [lzm]% node index.mjs
    > { add: [Function: add] }
    

    我们可以看到是一个拥有add函数的对象,按理说应该可以结构赋值才对呀。其实从上面的报错能看出来,import的解构会自动匹配export,如果没有则会报错。

    可以再思考一个问题,在node环境下可以使用es module吗?
    答案是可以的,以上例子都是在node环境下运行的。现在node已经支持es module规范了,参考官网的解释
    esm_enabling

    commonjs module和es module的异同

    最本质的区别是模块依赖解析的时机不同

    运行时和编译时
    所谓运行时即代码已经解析成机器可以识别的形态,代码可以直接运行的阶段。而编译时即我们的js代码解析成机器可以识别的形态的过程。

    • cjs在运行时解析模块依赖,mjs在编译时解析模块依赖
      cjs的加载机制是输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

    cjs是在运行时才确定引入, 然后执行这个模块, 相当于是调用一个函数, 返回一个对象.
    mjs是语言层面的, 导入导出是声明式的代码集合. 声明式的意思就是说, 直接利用关键字声明说我要导入/导出一个模块啦, 而不是粗鄙(节目效果)地将一个对象赋值给一个变量

    如何处理模块的循环加载?
    CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行,CommonJS的做法是,一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。require函数第一次导入时会自动缓存结果,第二次再导入模块时会直接给到结果。
    import不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值

    es 模块并没有帮我们解决循环依赖,所以循环依赖问题需要开发者自己解决。那么问题又来了,在node环境下我们在使用es 模块的时候是不是会出现死循环呢?我们先来看个例子

    //add.mjs
    import { log } from './log.mjs';
    function add(a, b) {
      log(a, b)
      return a + b;
    }
    export {
      add
    }
    
    //log.mjs
    import { add } from './add.mjs';
    add(1, 2);
    function log() {
      console.log(...arguments);
    };
    export {
      log
    }
    

    在node环境下运行

    node add.mjs
    

    我们会发现控制台不会无限打印,只打印出一次1 2。

    其他模块

    除了commonjs module和es module模块,还有amd,cmd和umd等等。amd示例实现依赖于库requirejs,cmd示例实现依赖于库seajs

    示例源码

    参考

  • 相关阅读:
    mysql命令大全(转发)
    算法大神学习之路
    MYSQL之explain的作用与相关知识
    FastDFS(分布式存储系统)+nginx web 服务器
    获取用户浏览历史记录(django_redis)
    用户登陆装饰器和页面跳转(包含mixin的使用)
    .NET 方法回调
    asp.net viewstate 数据过大 导致错误
    asp.net viewstate 数据大导致错误
    软件测试 Record
  • 原文地址:https://www.cnblogs.com/xingguozhiming/p/14915305.html
Copyright © 2020-2023  润新知