• 前端模块化CommonJS、AMD、CMD、ES6模块(二)


    一 CommonJS规范:是Node.js使用的模块化规范方式

    1 暴露定义的模块:

    // m1.js
    
    // 1. 正确  
    module.exports = {  
        name: 'lindaidai',  
        sex: 'boy'  
    }  
      
    // 2. 正确  
    exports.name = 'lindaidai';  
    exports.sex = 'boy'  
      
    // 3. 正确  
    module.exports.name = 'lindaidai';  
    module.exports.sex = 'boy'  
      
    // 4. 无效  
    exports = {  
        name: 'lindaidai',  
        sex: 'boy'  
    }  

    2 引入模块:require()全局方法是 node 中的方法哈,它不是 window 下面的,所以如果你没做任何处理,想直接在 html 里用肯定就是不行的了,只能在node环境中使用该方法。

    3 模块标识符:指在引入模块时调用 require() 函数的参数,如:

    // 直接导入  
    const path = require('path');  
    // 相对路径  
    const m1 = require('./m1.js');  
    // 直接导入  
    const lodash = require('lodash');  

        path这种它是Node.js就自带的模块,m1是路径模块,lodash是我们使用npm i lodash下载到node_modules里的模块。模块引入方式分为三类:

    • 核心模块(Node.js自带的模块):查找方式:直接跳过路径分析和文件定位
    • 路径模块(相对或绝对定位开始的模块):查找方式:直接得出相对路径就好了
    • 自定义模块(node_modules里的模块):查找方式(路径分析):先在当前目录的node_modules里找这个模块,如果没有,它会往上一级目录查找,查找上一级的node_modules,依次往上,直到根目录下都没有, 就抛出错误。

    3.1文件定位:

    在NodeJS路径分析中, 省略了扩展名的文件, 会依次补充上.js, .node, .json来尝试, 如果传入的是一个目录, 那么NodeJS会把它当成一个包来看待, 会采用以下方式确定文件名

    第一步, 找出目录下的package.json, 用JSON.parse()解析出main字段

    第二步, 如果main字段指定的文件还是省略了扩展, 那么会依次补充.js, .node, .json尝试.

    第三步, 如果main字段制定的文件不存在, 或者根本就不存在package.json, 那么会默认加载这个目录下的index.js, index.node, index.json文件.

    以上就是文件定位的过程, 再搭配上路径分析的过程, 进行排列组合, 这得有多少种可能呀. 所以说, 自定义模块的引入, 是最费性能的.

    4 CommonJS规范特点:

    • 所有代码都运行在模块作用域,不会污染全局作用域;
    • 模块是同步加载的,即只有加载完成,才能执行后面的操作;
    • 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
    • CommonJS输出是值的拷贝(即,require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。

    二 AMD(Asynchronous Module Definition)异步模块定义,是一种前端模块组织规范:AMD的实现主要有两个: requireJS和curl.js

    AMD的理念可以用如下两个API概括: define和require

    define方法用于定义一个模块,它接收两个参数:

    • 第一个参数是一个数组,表示这个模块所依赖的其他模块
    • 第二个参数是一个方法,这个方法通过入参的方式将所依赖模块的输出依次取出,并在方法内使用,同时将返回值传递给依赖它的其他模块使用。

    三 CMD(Common Module Definition)通用模块定义:sea.js是遵循CMD规范实现的模块加载器

    和AMD不同的是,CMD没有提供前置的依赖数组,而是接收一个factory函数,这个factory函数包括3个参数

    • require: 一个方法标识符,调用它可以动态的获取一个依赖模块的输出
    • exports: 一个对象,用于对其他模块提供输出接口,例如:exports.name = "xxx"
    • module: 一个对象,存储了当前模块相关的一些属性和方法,其中module.exports属性等同于上面的exports

    如下所示

    // CMD
    define(function (requie, exports, module) {
        //依赖就近书写
        var module1 = require('Module1');
        var result1 = module1.exec();
        module.exports = {
          result1: result1,
        }
    });
    
    // AMD
    define(['Module1'], function (module1) {
        var result1 = module1.exec();
        return {
          result1: result1,
        }
    }); 

    AMD和CMD区别:

    AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同,二者皆为异步加载模块。

    // 在 AMD中 math.js
    define(['m1'], function (m1) {
      console.log('我是math, 我被加载了...')
      var add = function (a, b) {
        return a + b;
      }
      var print = function () {
        console.log(m1.name)
      }
      return {
        add: add,
        print: print
      }
    })
    
    // 在CMD中math.js
    define(function (require, exports, module) {
      console.log('我是math, 我被加载了...')
      var m1 = require('m1');
      var add = function (a, b) {
        return a + b;
      }
      var print = function () {
        console.log(m1.name)
      }
      module.exports = {
        add: add,
        print: print
      }
    })  

    假如此时m1.js中有一个语句是在m1模块被加载的时候打印出"我是m1, 我被加载了..."

    执行结果区别:

    • AMD,会先加载m1"我是m1"会先执行
    • CMD,我是"我是math"会先执行,因为本题中console.log('我是math, 我被加载了...')是放在require('m1')前面的。

    即 两者之间,最明显的区别就是在模块定义时对依赖的处理不同:

    1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块2、CMD推崇就近依赖,只有在用到某个模块的时候再去require

    四 ES6 Modules规范:ES6标准出来后,ES6 Modules规范基本成为了前端的主流

    1 export有两种模块导出方式:

    • 命名式导出(名称导出)
    • 默认导出(自定义导出)

    命名式导出:

     // 以下两种为错误  
    // 1.  
    export 1;  
    // 2.  
    const a = 1;  
    export a;  
      
    // 以下为正确  
    // 3.  
    const a = 1;  
    export { a };  
      
    // 4. 接口名与模块内部变量之间,建立了一一对应的关系  
    export const a = 1, b = 2;  
      
    // 5. 接口名与模块内部变量之间,建立了一一对应的关系  
    export const a = 1;  
    export const b = 2;  
      
    // 或者用 as 来命名  
    const a = 1;  
    export { a as outA };  
      
    const a = 1;  
    const b = 2;  
    export { a as outA, b as outB };  

    容易混淆的可能是24两种写法了,看着很像,但是2却不行。2直接导出一个值为1的变量是和情况一一样,没有什么意义,因为你在后面要用的时候并不能完成解构。

    但是4中,接口名与模块内部变量之间,建立了一一对应的关系,所以可以。

    默认导出,默认导出会在export后面加上一个default

    // 1.  
    const a = 1;  
    export default a;  
      
    // 2.  
    const a = 1;  
    export default { a };  
      
    // 3.  
    export default function() {}; // 可以导出一个函数  
    export default class(){}; // 也可以出一个类  

    2 import导入:import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。

    // 某个模块的导出 moudule.js  
    export const a = 1;  
      
    // 模块导入  
    // 1. 这里的a得和被加载的模块输出的接口名对应  
    import { a } from './module'  
      
    // 2. 使用 as 换名  
    import { a as myA } from './module'  
      
    // 3. 若是只想要运行被加载的模块可以这样写,但是即使加载2次也只是运行一次  
    import './module'  
      
    // 4. 整体加载  
    import * as module from './module'  
      
    // 5. default接口和具名接口  
    import module, { a } from './module'  

    第四种写法会获取到module中所有导出的东西,并且赋值到module这个变量下,这样我们就可以用module.a这种方式来引用a了。

    3 Babel下的ES6模块转换:如果你有使用过一些ES6的Babel的话,你会发现当使用export/import的时候,Babel也会把它转换为exports/require的形式。

    五 CommonJS与ES6 Module规范的区别:

    • CommonJS模块是运行时加载,ES6 Modules是编译时输出接口
    • CommonJS输出是值的浅拷贝;ES6 Modules输出的是值的引用,被输出模块的内部的改变会影响引用的改变
    • CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 Modules只能是字符串
    • CommonJSthis指向当前模块,ES6 Modulesthis指向undefined
    • 且ES6 Modules中没有这些顶层变量:argumentsrequiremoduleexports__filename__dirname

    相关阅读:

    CommonJS、AMD、CMD、ES6模块规范讲解

    深入浅出Nodejs 

  • 相关阅读:
    如何解决跨域问题?
    eclipse 显示堆内存状态
    MATLAB解一元线性回归问题
    Java深入理解深拷贝和浅拷贝区别
    Spring中使用DataSourceTransactionManager进行事务管理的xml配置
    jsp:forward
    Mybatis入门笔记链接
    python 判断字符串是否全为字母或数字
    java对象的浅克隆和深克隆
    安装使用ipython notebook
  • 原文地址:https://www.cnblogs.com/terrymin/p/15740414.html
Copyright © 2020-2023  润新知