最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript 模块化的发展,你了解多少呢?

    正文概述 掘金(Lipo ?)   2021-01-10   518

    模块化对于现在的开发人员来说并不陌生,可以说是相当熟悉了。但是如果问 JavaScript 中的模块发展过程,相信有挺多人都是不太了解的。接下来我们就来回顾一下 JavaScript 的发展历程。

    1.什么是模块化?

    模块化其实是一种规范、约束。这种规范约束能让我们的代码更具可观性和后续维护性。这种方式会大大的提高我们的工作效率,同时减少了后面维护的时间。

    2.为什么要模块化?

    • 我们的前端代码越来越庞大
    • 代码的复杂性越来越高
    • 文件之间的耦合性太高

    显然这些问题随着前端项目的持续迭代与发展,都是会遇到的。这时候就需要我们使用模块化来进行管理与约束了。

    3.怎么实现模块化?

    1、一开始

    最早我们写的JS代码就是一个函数一个函数的往下写...

    function foo(){
        //...
    }
    function bar(){
        //...
    }
    

    坏处:直接是写在全局中,变量容易被污染

    2、Namespace 命名空间模式

    Namespace 模式就是用个对象封装起来

    var test = {
        foo: function() {},
        bar: function() {}
    }
    
    test.foo()
    test.bar()
    

    这种模式虽然减少了全局的变量数,但是本质上还是全局范围内的一个对象,不安全。

    3、匿名闭包:IIFE 模式
    var Module = (function(){
        var test = "test";
        var foo = function(){
            console.log(test)
        }
        return {
            foo: foo
        }
    })()
    
    Module.foo();
    Module.test; // undefined
    

    每个闭包都是单独一个文件,利用闭包的方式来解决变量重名和环境污染问题

    但是闭包同样会带来许多的问题:如内存泄露、this指向问题等等...

    4、引入依赖
    var Module = (function($){
        var _$body = $("body");     // we can use jQuery now!
        var foo = function(){
            console.log(_$body);    // 特权方法
        }
    
        // Revelation Pattern
        return {
            foo: foo
        }
    })(jQuery)
    
    Module.foo();
    

    这就是模块模式,也是现代模块实现的基石

    5、LABjs - Script Loader

    “ LABjs是一种动态脚本加载器,旨在以灵活且性能优化的替代API代替丑陋,性能不佳的

    够以浏览器允许的速度并行加载所有JavaScript文件的功能

    $LAB
        .script("script1.js")
        .script("script2.js")
        .script("script3.js")
        .wait(function(){ // 等待所有script加载完再执行这个代码块
            script1Func();
            script2Func();
            script3Func();
        });
    
    6、YUI3 Loader

    YUI的轻量级内核和模块化体系结构使其可扩展,快速且强大

    结构上分四大类:种子、核心、组件框架、组件

    最大特点: 动态按需加载、细粒度化设计

    // YUI - 编写模块
    YUI.add('dom', function(Y) {
      Y.DOM = { ... }
    })
    
    // YUI - 使用模块
    YUI().use('dom', function(Y) {
      Y.DOM.doSomeThing();
      // use some methods DOM attach to Y
    })
    

    创建自定义模块

    // hello.js
    YUI.add('hello', function(Y){
        Y.sayHello = function(msg){
            Y.DOM.set(el, 'innerHTML', 'Hello!');
        }
    },'3.0.0',{
        requires:['dom']
    })
    
    // main.js
    YUI().use('hello', function(Y){
        Y.sayHello("hey yui loader");
    })
    

    你不必按固定顺序来包含脚本标签,加载和执行是分离的

    script(src="/path/to/yui-min.js")       // YUI seed
    script(src="/path/to/my/module1.js")    // add('module1')
    script(src="/path/to/my/module2.js")    // add('module2')
    script(src="/path/to/my/module3.js")    // add('module3')
    
    YUI().use('module1', 'module2', 'module3', function(Y) {
        // you can use all this module now
    });
    

    但是这样还有一个问题:Too much HTTP calls

    YUI 组合

    在单个请求中处理多个文件,但是需要服务器支持(alibaba/nginx-http-concat)

    script(src="http://yui.yahooapis.com/3.0.0/build/yui/yui-min.js")
    script(src="http://yui.yahooapis.com/3.0.0/build/dom/dom-min.js")
    
    ⬇️
    
    script(src="http://yui.yahooapis.com/combo?
        3.0.0/build/yui/yui-min.js&
        3.0.0/build/dom/dom-min.js")
    
    7、CommonJS

    特点:

    • 所有代码都运行带模块作用域、不会污染全局作用域
    • 模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存了。想要模块再次运行,需要清除缓存
    • 模块加载的顺序是按照其在代码中出现的顺序
    module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
    
    node 为每一个模块提供了一个exports变量(可以说是一个对象),指向 module.exports。这相当于每个模块中都有一句这样的命令 var exports = module.exports
    
    既然两个不好区分,那就放弃 exports, 只用 module.exports 就好。
    
    // math.js
    exports.add = function(a, b){
        return a + b;
    }
    
    // main.js
    var math = require('math')      // ./math in node
    console.log(math.add(1, 2));    // 3
    
    8、AMD / CMD
    RequireJS - AMD

    AMD 规范是非同步加载模块,允许指定回调函数

    定义

    define(['package/lib'],function(lib){
    
      function foo(){
    
        lib.log('hello world!')
    
      }
      
      return{foo:foo}
    
    })
    

    Async 异步

    //CommonJS Syntax
    var Employee = require("types/Employee");
    
    function Programmer (){
        //do something
    }
    
    Programmer.prototype = new Employee();
    
    //如果 require call 是异步的,那么肯定 error
    //因为在执行这句前 Employee 模块根本来不及加载进来
    

    Function Wrapping 功能封装

    //AMD Wrapper
    define(
        ["types/Employee"],  //依赖
        function(Employee){  //这个回调会在所有依赖都被加载后才执行
            function Programmer(){
                //do something
            };
    
            Programmer.prototype = new Employee();
            return Programmer;  //return Constructor
        }
    )
    

    AMD vs CommonJS

    1、相同点

    都是为了模块化
    

    2、不同点

    加载上

    AMD 规范是非同步加载模块,允许指定回调函数
    
    CommonJS 规范加载模块是同步的
    

    书写

    // Module/1.0
    var a = require("./a");  // 依赖就近
    a.doSomething();
    
    var b = require("./b")
    b.doSomething();
    
    // AMD recommended style
    define(["a", "b"], function(a, b){ // 依赖前置
        a.doSomething();
        b.doSomething();
    })
    

    执行时机

    // Module/1.0
    var a = require("./a");  // 执行到此时,a.js 同步下载并执行
    
    // AMD with CommonJS sugar
    define(["require"], function(require){
        // 在这里, a.js 已经下载并且执行好了
        var a = require("./a")
    })
    
    SeaJS - CMD

    SeaJS 遵循 CMD 规范模块化开发,依赖的自动加载、配置的简介清晰

    非常像 CommonJS 的格式

    加载单个依赖
    //加载模块 main,并在加载完成时,执行指定回调
    seajs.use('./main', function(main) {
      main.init();
    })
    
    
    加载多个依赖
    //并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
    seajs.use(['./a', './b'], function(a, b) {
      a.init();
      b.init();
    })
    
    定义
    // 所有模块都通过 define 来定义
    define(function(require, exports, module) {
    
      // 通过 require 引入依赖
      var $ = require('jquery');
      var Spinning = require('./spinning');
    
      // 通过 exports 对外提供接口
      exports.doSomething = ...
    
      // 或者通过 module.exports 提供整个接口
      module.exports = ...
      
      // 模块是通过define()方法包装的,然后内部痛过require()方法引入需要的依赖文件(模块)。(也可以引入.css文件哦~)
    
    });
    

    1、AMD 是提前执行, CMD 是延迟执行

    2、CMD 推崇依赖就近,AMD 推崇依赖前置

    // CMD
    define(function(require, exports, module) {
      var a = require('./a')
      a.doSomething()
      // ...
      var b = require('./b') // 依赖可以就近书写
      b.doSomething()
      // ...
    })
    
    // AMD
    define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
      a.doSomething()
      // ...
      b.doSomething()
      ...
    })
    

    3、AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一

    9、Browserify / Webpack

    1、Browserify

    browserify 是用于为浏览器编译节点风格的 commonjs 模块的工具 。

    您可以使用browserify来组织代码并使用第三方库,即使您不以其他任何身份使用node本身,除了使用npm捆绑和安装软件包之外。

    browserify使用的模块系统与节点相同,因此发布到npm的程序包最初打算在节点中使用,但不适用于浏览器,也可以在浏览器中正常工作。

    2、Webpack

    引用 Webpack 官网的一句话:“webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。”

    简单的CLI

    # make sure your directory contains webpack.config.js
    
    # Development: debug + devtool + source-map + pathinfo
    webpack main.js bundle.js -d
    
    # Production: minimize + occurence-order
    webpack main.js bundle.js -p
    
    # Watch Mode
    webpack main.js bundle.js --watch
    

    具体的 Webpack 知识,可以移步 这里

    10、ES6 Module

    ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。 如果想了解更多的 ES6 Module 知识,可以点击 这里

    // CommonJS模块
    let { stat, exists, readFile } = require('fs');
    
    // 等同于
    let _fs = require('fs');
    let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
    

    ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

    // ES6模块
    import { stat, exists, readFile } from 'fs';
    

    除了静态加载带来的各种好处,ES6 模块还有以下好处。

    • 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
    • 将来浏览器的新 API 就能用模块格式提供,不再必要做成全局变量或者navigator对象的属性。
    • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"

    严格模式主要有以下限制。

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 不能对只读属性赋值,否则报错
    • ...

    模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

    // profile.js
    export var firstName = 'Michael';
    export var lastName = 'Jackson';
    export var year = 1958;
    

    export的写法,除了像上面这样,还有另外一种

    // profile.js
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    
    export {firstName, lastName, year};
    

    4.总结思考

    本文着重介绍了 JavaScript 模块发展的历程,并简单的罗列了各个时期模块化解决方案中的一些小例子。

    不同的模块化手段其实都是为了

    • 为了让日后的代码更具有迭代性和维护性
    • 解决文件依赖加载存在的问题
    • 更方便服务于我们的日常业务开发...

    如今到了 ES6 Module,虽然比起以前模块化的发展和规范已经日渐完善了,但并不会止步于此。随着业务的发展与迭代,后续肯定会继续出现更加符合业务开发维护的模块化管理方案。

    我个人觉得:没有更完美,只有更符合

    参考

    Webpack 官网

    ES6 module

    JavaScript 模块化七日谈

    浅谈模块化开发

    快速上手seajs——简单易用Seajs


    起源地下载网 » JavaScript 模块化的发展,你了解多少呢?

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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