美文网首页
模块化发展历程

模块化发展历程

作者: dosher_多舍 | 来源:发表于2019-11-06 09:54 被阅读0次

    什么是模块化?

    • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
    • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

    模块化的理解

    模块化是一种处理复杂系统分解为更好的可管理模块的方式。简单来说就是解耦,简化开发,一个模块就是实现特定功能的文件,可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范。

    主要从IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module 这几个角度考虑。

    IIFE

    IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。特点:
    在一个单独的函数作用域中执行代码,避免变量冲突

    (function () {
        statements
    })();
    
    • 这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。

    • 第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。

    (function () { 
        var name = "Barry";
    })();
    // 无法从外部访问变量 name
    name // 抛出错误:"Uncaught ReferenceError: name is not defined"
    
    
    var result = (function () { 
        var name = "Barry"; 
        return name; 
    })(); 
    // IIFE 执行后返回的结果:
    result; // "Barry"
    

    AMD

    AMD 即 Asynchronous Module Definition,中文名是“异步模块定义”的意思。在浏览器环境,要从服务器端加载模块,就必须采用非同步模式,因此浏览器端一般采用AMD规范。AMD是一个在浏览器端模块化开发的规范,而AMD规范的实现,就是require.js。特点:依赖必须提前声明好

    • 优点:异步加载,不阻塞页面的加载,能并行加载多个模块
    • 缺点:不能按需加载,必须提前加载所需依赖
    // 定义
    // 独立模块 es1.js
    define({
        module1: function() {},
        module2: function() {},
    });
    // 等价写法
    define(function () {
        return {
            module1: function() {},
            module2: function() {},
        };
    });
    // 非独立模块 只有先加载这两个模块,新模块才能正常运行 es2.js
    define(['es1', 'es2'], function(m1, m2) {
           return {
            method: function() {
                m1.methodA();
                m2.methodB();
            }
        };
    });
    
    // 使用
    require(['es1', 'es2'], function ( es1, es2) {
        es1.module1()
        es1.module2()
        es2.mothod()
    });
    

    CMD

    CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。特点:支持动态引入依赖文件。

    • 优点:可以按需加载,依赖就近
    • 缺点:依赖SPM打包,模块的加载逻辑偏重
    // 定义
    // 使用 exports 直接向外提供接口。
    define(function(require, exports) { 
        // 对外提供 name属性
        exports.name = 'Tom'; 
        // 对外提供 say 方法
        exports.say= function(name) {
            console.log("hello"+name)
        };
    });
    // 使用 return 直接向外提供接口。
    define(function(require) {  
        return {
            name : 'Tom',    
            say: function(name) {
                console.log("hello"+name)
            }
        };
    });
    // 使用 module.exports 直接向外提供接口。
    define(function(require, exports, module) { 
        module.exports = {
            name: 'Tom', 
            say: function(name) {
                console.log("hello"+name)
            }
        };
    });
    
    
    // 使用
    define(function (require) {
        var m1 = require('./module1')
        console.log(m1.name)      // Tom
        m1.say(m1.name)           // Hello Tom
    })
    
    // require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。
    define(function(require, exports, module) {  
        // 异步加载一个模块
        require.async('./module1', function(a) {
            a.doSomething();
        }); 
    
        // 异步加载多个模块,在加载完成时,执行回调
        require.async(['./module2', './module3'], function(b, c) {
            b.doSomething();
            c.doSomething();
        });
    })
    

    CommonJS

    Node 应用由模块组成,采用 CommonJS 模块规范,前端的webpack也是对CommonJS原生支持的。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理

    // 定义
    
    es1.js
    var name = "Tom";
    var age = 16;
    function say(name,age){
       console.log(name+"is"+age+"years old")
    }
    
    // 语法1
    module.exports.name = name;
    module.exports.age = age;
    module.exports.say = say;
    
    // 语法2
    exports.name = name;
    exports.age = age;
    exports.say = say;
    
    // 语法3
    module.exports = {
      name,
      age,
      say
    }
    
    // 语法4
    module.exports = {
      name : "Tom",
      age : 16,
      say(name,age){
       console.log(name+"is"+age+"years old")
      }
    }
    
    
    // 引用
    var module = require('./es1.js')
    consolo.log(module.name)          //Tom
    consolo.log(module.age)           //16
    module.say(name,age)              //Tom is 16 years old
    
    

    注:

    • exports 与module.exports 的区别:exports 是对 module.exports 的引用,不能直接给exports 赋值,直接赋值无效,结果是一个空对象, module.exports 可以直接赋值
    • 一个文件不能写多个module.exports ,如果写多个,对外暴露的接口是最后一个module.exports
    • 模块如果没有指定使用module.exports 或者exports 对外暴露接口时,在其他文件就引用该模块,得到的是一个空对象{}
    • 浏览器不兼容CommonJS,在于缺少四个Node.js环境的变量。(module,exports,require,global)

    UMD

    UMD 叫做通用模块定义规范(Universal Module Definition)。也是随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。未来同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了。是集结了 CommonJs、CMD、AMD 的规范于一身。

    // UMD的实现
    ((root, factory) => {
        if (typeof define === 'function' && define.amd) {
            //AMD
            define(['jquery'], factory);
        } else if (typeof exports === 'object') {
            //CommonJS
            var $ = requie('jquery');
            module.exports = factory($);
        } else {
            root.testModule = factory(root.jQuery);
        }
    })(this, ($) => {
        //todo
    });
    

    不难发现,它在定义模块的时候回检测当前使用环境和模块的定义方式,将各种模块化定义方式转化为同样一种写法。它的出现也是前端技术发展的产物,前端在实现跨平台的道路上不断的前进,UMD 规范将浏览器端、服务器端甚至是 APP 端都大统一了

    webpack(require.ensure)

    webpack 2.x 版本中的代码分割

    ES Module

    ES6 在语言标准的层面上,实现了模块功能,而且非常简单,ES6到来,完全可以取代 CommonJS 和 AMD规范,成为浏览器和服务器通用的模块解决方案。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
    注:由于ES6目前在部分版本的浏览器中无法执行,所以,我们需要通过babel将不被支持的import编译为当前受到广泛支持的 require。

    // 定义
    
    // 变量    es3.js
    export var m = 1;
    //函数
    export function fn(x, y) {
      return x * y;
    };
    //类class
    class Hello{
      test(){
        console.log("hello")
      }
    }
    
    
    // 也可以合并为一个出口暴露
    var m = 1;
    function fn(x, y) {
      return x * y;
    };
    class Hello{
      test(){
        console.log("hello")
      }
    }
    export {
      m,
      fn,
      Hello
    }
    
    // 在暴露模块时,可以通过 as 来进行重命名
    export{
      num as m,
      foo as fn,
      Test as Hello
    }
    
    
    // 引用
    
    //静态加载,只加载es3.js 文件中三个变量,其他不加载
    import {m, fn, Hello} from './es3.js';
    //import命令要使用as关键字,将输入的变量重命名。
    import {fn as fn1} from './es3.js';
    //整体加载模块
    improt * as all from './es3.js'
    console.log(all.m)              // 1
    console.log(all.fn(3,4))        // 12
    all.Hello.test()                // hello
    

    特点:

    • ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
    • 自动采用严格模式"use strict"。须遵循严格模式的要求
    • ES6 模块的设计思想是尽量的静态化,编译时加载”或者静态加载,编译时输出接口
    • ES6 模块export、import命令可以出现在模块的任何位置,但是必须处于模块顶层。如果处于块级作用域内,就会报错
    • ES6 模块输出的是值的引用

    模块化的好处

    • 避免命名冲突(减少命名空间污染)
    • 更好的分离, 按需加载
    • 更高复用性
    • 高可维护性

    模块化的作用

    • 减少JS文件的请求次数,通过模块化将JS文件整合为一个入口,然后引入页面可以有效的减少对JS文件的请求;

    • 使各JS文件的依赖关系清晰,在模块化中可以清晰的分析各模块的引用关系,明确JS代码的结构;

    • 降低项目的维护成本,当有某个模块需要添加或减少某个功能使,不需要将整个代码重构,只需要在相应的模块进行修改就可以。

    相关文章

      网友评论

          本文标题:模块化发展历程

          本文链接:https://www.haomeiwen.com/subject/ucnybctx.html