前端模块化CommonJS、AMD、CMD、ES6

一、前端模块化

前端模块化CommonJS、AMD、CMD、ES6_第1张图片

什么是模块化?为什么前端需要模块化?

js代码量激增,放在同一个文件里面,不容易维护,而且牵一发而动全身。

这时候就需要将代码按照逻辑放在不同的文件里面,按照一定的语法规则,遵循特定的规范将一个庞大的文件拆分若干个相互依赖的文件。这些文件对外暴露数据或接口,在需要的时候导入引用。这就是前端模块化。

说的很官方,举个简单的栗子来通俗的理解。就像是社会的分工合作,彼此依赖,彼此独立,每个社会的部门可以理解为一个模块,按照某种规则负责特定的功能,组装起来形成一个整体,从而完成整个社会系统所要求的功能。

前端模块化的三个阶段

在这里插入图片描述

(1)早期“伪”模块化时代

借助函数的作用域来实现伪模块化,不同的功能封装成不同的函数。

变化过程如下

①第一个阶段—函数模块化

JS函数有独立的作用域,在函数中可以放任何代码,只需要在使用的地方调用它,实现代码分离组织,视觉上看起来也很清晰。

局限性:如果代码量巨大,无法保证模块之间不发生冲突,各个函数在同一个文件中,没有前后逻辑的依赖关系,混乱的调用,而且存在命名冲突和变量污染问题。

    function fn1() {
        //...
    }

    function fn2() {
        //...
    }

    function fn3() {
        fn1()
        fn2()
    }

②第二个阶段—对象模块化

对象可以有属性,而且它的属性可以是数据,也可以是方法。对象的属性可以通过对象名字来访问,相当于设定了一个命名空间,于是对象模块化也叫做命名空间模式

局限性:数据安全性低,内部属性是赤裸暴露的,对象的内部成员可以随意修改。

    const module1 = {
        data1: 'date1',
        fn1: function () {
            //...
        },
    };
    const module2 = {
        data2: 'data2',
        fn2: function () {
            //...
        },
    };
    module2.data2 = 'data1'; //可以随意修改,安全系数低

解决方案:IIFE立即执行函数+闭包+对外暴露数据和接口。闭包可以解决数据访问安全,立即执行函数创建一个私有的作用域。意思就是利用函数闭包的特性来实现私有数据和共享方法。

//只能通过调用模块暴露给外界(window)的函数修改data值
	(function (window) {
        var data = "data";

        function showData() {
            console.log(`data is ${data}`);
        }

        function updateData() {
            data = "newData";
            console.log(`data is ${data} `);
        }
        window.module1 = {
            showData,
            updateData
        };
    })(window);//自执行函数传入window使window是全局变量也是局部变量,当内部代码访问window对象时,不用往上逐层查找
module1.showData()
module1.updateData()
    var myModule = (function () {
        var name = 'gaby'

        function getName() {
            console.log(name)
        }
        return {
            getName
        }
    })()
    myModule.getName() //获取name实现属性私有化
    myModule.name //外部调用不到

如果myModule需要依赖其他的函数(模块)怎么办呢,这时候就要给函数传入参数(引入依赖)

    var yourModule = (function () {
        return {
            a: 1,
            b: 2,
            c: 3,
            d: 4
        }
    })()

    var myModule = (function () {
        var name = 'gaby'

        function getName() {
            console.log(name)
        }
        return {
            getName
        }
    })(yourModule)

传参的形式也是现代模块规范的思想来源

依赖注入

三大框架之一的Angular,核心思想就是依赖注入

// 模块fnA
let fnA = function(){
  return {name: '我是fnA'}
}

// 模块fnB
let fnB = function(){
  return {name: '我是fnB'}
}

//模块fnC 调用fnA、fnB,依赖函数作为显式参数传入
let fnC = function(){
  let a = fnA()
  let b = fnB()
  console.log(a, b)
}
(2)多种规范标准时代

CommonJS:node.js应用模块,一个文件就是一个模块,拥有自己独立的作用域,变量和方法都在独立的作用域内。

AMD规范:CommonJS是同步加载,在服务端没有问题。但是在浏览器端不合适,会导致页面失去响应,同步加载的时间越长,用户体验就越差。所以AMD出现就是浏览器端的异步加载。运用require.js库定义define模块、加载require模块。

CMD规范:综合CommonJS和AMD的优点,可以同步加载也可以异步加载。运用Sea.js库实现模块化。

UMD规范:通用模块定义规范Universal Module Definition。通过运行编译时让同一个代码模块在使用CommonJS、CMD、AMD的项目中运行。js可以运行在服务器端、浏览器端、移动端,遵循同一个语法规范就行.

二、CommonJS规范(服务器端)

(1)什么是CommonJS

CommonJS是服务器端规范,Nodejs采用了这个规范,首先采用js模块化的概念。

CommonJS用同步的方法加载模块,在服务端,模块文件都存在本地磁盘,读取速度快。服务器端同步加载不会有问题,但是在浏览器端的AMD、CMD还是异步加载方案更合理。

(2)CommonJS规范

定义模块分为:module模块标识、exports模块定义、require模块引用

定义当前模块对外输出的接口(不推荐,直接用exports)

module.exports与exports的区别

  • module.exports

表示当前模块对外输出的接口,当其他文件加载,实际上是读取module.exports这个属性。

  • exports

node为每一个模块提供了一个exports对象。这个exports对象的引用指向module.exports。

相当于var exports = module.exports。可以在这个变量上直接添加属性方法:exports.xxx = function(){}

但是不能写成直接指向一个值赋值,因为这样会改变exports的引用地址,这就使得exports和module.exports没有关系

    //定义模块math.js
    var number = 0;

    function add(x, y) {
        return x + y
    }
    module.exports = { //对外暴露的数据和接口函数
        number: number,
        add: add
    }
    //引用自定义模块时,包含参数路径,可以省略js
    var math = require('./math.js');
    math.add(1, 2)
    //引用核心模块,不需要路径
    var http = require('http');
    http.createService().listen();
(3)CommonJS规范特点

①所有的代码都在独立的模块作用域中,不会污染全局作用域

②模块加载的顺序,按照其在代码中的引入顺序加载。

③模块可以多次加载,但是只会在第一次加载时运行一次,运行结果被缓存。之后加载从缓存中直接读取,清空缓存重新运行。

④module.exports属性输出是值拷贝。一旦操作完成,模块内发生的任何变化不会影响到已经输出的值。

三、AMD规范

(1)什么是AMD

AMD(Asynchronous Module Definition)异步模块定义指定一种机制,在该机制下模块和依赖可以异步加载,这对浏览器端的异步加载尤其适用。AMD是在RequireJS在推广过程中对模块化定义的规范化产出。

(2)AMD可以解决什么问题

①多个js文件存在依赖,被依赖的文件需要早于依赖它的文件加载到浏览器

②js加载的时候浏览器会停止渲染页面,加载的文件越多,页面失去响应的时间就会越长。AMD可以异步加载

(3)AMD规范

require.js实现AMD规范的模块化:用require.config()指定引用路径、define()定义模块、require()调用模块

①定义模块define(id,dependencies,factory),全局变量

id:定义模块的名字,如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定的脚本的名字

dependcies:当前模块的一个依赖,已被模块定义的模块标识的数组字面量。依赖参数可选,忽略默认的是[‘require’,‘exports’,‘module’]

factory:模块初始化要执行的函数或者对象。如果是函数,一般只执行一次。如果是对象,此对象应该为模块的输出值。

②调用模块require([dependencies],function(){})

dependencies:第一个参数是数组,表示依赖的模块

function(){}:第二个参数是回调函数,当前指定的模块都加载成功后,它将被调用。加载的模块会以参数的形式传入函数,因此在回调函数的内部就可以使用这些模块。

require()函数在加载模块的时候是异步加载,浏览器不会失去响应。回调函数只有前面的模块加载成功后,才会运行,解决依赖性问题。

    //定义模块myModule.js
    define(['dependency'], function () {
        var name = 'gaby';

        function printName() {
            console.log(name)
        }
        return {
            printName: printName
        };
    });
    //加载模块
    require(['myModule'], function (my) {
        my.printName();
    });
    define(['dependency'], function () {
        var name = 'gaby'

        function printName() {
            console.log(name);
        }
        return {
            printName: printName //对外暴露的接口
        };
    });
    //加载模块
    require(['myModule'], function (my) {
        my.printName()
    });

四、CMD规范

(1)什么是CMD

CMD是Common Module Definition通用模块定义,CMD规范国内发展出来的。CMD规范中,一个模块就是一个文件。CMD是在SeaJS在推广过程中对模块化定义的规范化产出。

(2)CMD规范

①define(id,d,factory)

  • CMD推崇一个文件一个模块,经常用文件名作为id
  • CMD推崇就近依赖,一般不在define中写依赖
  • factory中写三个参数

②function(require,exports,module)

  • require是一个方法,用来获取其他模块提供的接口
  • exports是一个对象,用来向外提供模块接口
  • module是一个对象,上面存储模块相关联的一些属性和方法
// 定义模块  myModule.js
define(function(require,exports,module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});
// 加载模块
seajs.use(['myModule.js'],function(my){
});
    //math.js
    define(function (require, exports, module) {
        exports.add = function () {
            var sum = 0,
                i = 0,
                args = arguments,
                l = args.length;
            while (i < l) {
                sum += args[i++];
            }
            return sum;
        };
    });
    // module1.js
    define(function (require, exports, module) {
        var add = require('math').add;
        exports.module1 = function (val) {
            return add(val, 1);
        };
    });
    // module2.js

    define(function (require, exports, module) {
        var inc = require('module1').module1;
        var a = 1;
        ml1(a); //2

        module.id == 'module2';
    });

五、ES6原生模块

(1)ES6模块化的两个特点

①ES6模块化规范中模块输出的是值的引用

ES 模块化规范中导出的值是引用,所以不论何时修改模块中的变量,在外部都会有体现。

②静态化,编译的时候就确定模块之间的关系,每个模块的输入和输出变量也是确定的。

静态化是为了实现tree shaking提升运行性能。

tree shaking

减少web项目中js的无用代码,以达到减少用户打开页面的等待的时间,缩短渲染的时间,提升响应的用户体验。

DCE

减少无用代码的操作有一个名字叫做DCE(dead code elemination),无用代码的减少意味着更小的体积,缩减bundle size,从而获得更好的用户体验。

(2)ES6模块化静态性的局限性

①import依赖必须在文件的顶部

②export导出的变量类型严格限制

③依赖不能动态确定

(3)ES6导出export VS export default

ES6模块化导出有exportexport default,建议用export,因为

  • export default导出整体对象,不利于减少无用代码tree shaking
  • export default导出的结果可以随意命名,不利于代码的管理
(4)ES6模块的两个命令,exportimport

①export:规定模块对外的接口

②import:输入其他模块提供的功能

    //定义模块math.js
    var number = 0;

    var add = function (x, y) {
        return x + y
    };
    export {
        number,
        add
    } //暴露对外数据和接口

    //引用模块
    import {
        number,
        add
    } from './math';

    function test(ts) {
        ts.textContent = add(9999999 + number)
    }

模块的两个命令,exportimport

①export:规定模块对外的接口

②import:输入其他模块提供的功能

    //定义模块math.js
    var number = 0;

    var add = function (x, y) {
        return x + y
    };
    export {
        number,
        add
    } //暴露对外数据和接口

    //引用模块
    import {
        number,
        add
    } from './math';

    function test(ts) {
        ts.textContent = add(9999999 + number)
    }

你可能感兴趣的:(前端工程化,前端,javascript,前端模块化)