美文网首页Web前端之路我爱编程
JavaScript模块化发展史

JavaScript模块化发展史

作者: 误入理工科的疯子 | 来源:发表于2018-01-23 17:11 被阅读78次

    前两天有朋友拿了这样一段代码来问我,“我想把一段代码写成模块化的样子,你帮我看看是不是这样的。”,代码大概是这样的:

    (function(global) {
        var myModules = {
            name: 'xxx',
            location: 'chengdu',
            intro: function() {
                    return `his name is ${myModules.name} and come from ${myModules.location}`
                },
            }
            // some other code...
            if(typeof module === 'undefined')
                global.myModules = myModules
            else
                module.exports = myModules
    })(this)
    

    “可是我这段代码在全局还是能myModules这个属性啊?”
    我一脸懵,还有这种操作,为什么你的立即执行函数要把this传进去呢,这样不久将里面的内容挂在到了window上了吗,他似懂非懂,我只好说,你可能需要回头去看看AMDCMD规范了,我大概能够理解这其中的缘由,毕竟这段时间前端发展的速度飞快,加上webpack也不需要自己配,包括vuereactangular在内的框架类库都有一键生成项目的工具,从而只需要使用下import * from '*'export default {},而这种便利会让新人不再需要去学习基本原理就能快速上手,毕竟现在都ES2018了呀。

    • 对象形式

    最开始的时候,为了减少全局变量,一般会使用一个对象的方式来对所有的变量方法进行一个包装:

        var obj = {
            a: 1,
            b: 2,
            sum: function(val) {
               return obj.a + obj.b + val
            },
            rest: function(val) {
               return val - obj.a - obj.b
            }
       }
    

    以上代码似乎解决了全局变量的问题,但是其中的ab两个变量还是可能被修改,其中包含的进化有限。

    • 立即执行函数

    回过头来看文章开头的代码,姑且不论以上代码的错误之处,稍作修改,这算是最初的一种关于JavaScript模块化的开端,立即执行函数:

    var add = (function(){
        var a = 1
        var b = 2
        function sum(count){
            return a + b + count
        }
        function rest(count){
            return count - a - b
        }
        return {
            rest: rest,
            sum: sum
        }
    })()
    

    这就是一种最简单的模块化的方式,利用闭包的特性,设置了两个只有在被暴露出来的addreduce方法内部才能访问到的两个变量,从而保证了函数的局部变量不可被修改的特性,这次的进化用到了闭包,从而实现了部分有效的目的。

    • 放大模式

    其实开头的代码更符合另外一种叫做放大模式的方法,不过一般来说不会讲window作为放大模式中被传入的对象

        var globalObject = {
          fn1: function() {
            // todo...
          },
          // ...
        }
        globalObject = (function(obj) {
          var name = 'xxx'
          var location = 'xxx'
          function sum(val) { /* todo... */ }
          function rest(val) { /* todo... */ }
          obj.sum = sum
          obj.rest = rest
          return obj
        })(globalObject)
    

    以这种形式来写,可以让全局变量尽量的减少,同时让一个立即执行函数中的代码尽量做到精简。

    • 但是这种放大模式也存在着问题,比如当globalObject内容足够多的时候,很可能会造成命名重复的情况,并且以上所有的方式都不可以减少script标签的数量,所以,我们还是会被模块的加载顺序,命名空间冲突等问题所困扰,这时候,我们应该跨入新时代了。

    • CMD规范

    CMD规范来自阿里的框架seajs,当初确实有挺多人使用,不过现阶段已经不再维护了,我也不会,就暂时不说了,只列出来。

    • commonjs

    同时,从2009年开始,JavaScript就不再只是一种浏览器端的脚本语言了,nodejs的出现让使用js开发服务端变成了可能,随着node出现的东西还有一个叫做commonjs的规范,在这个规范中,每个文件都是一个模块,有着自己的作用域。

    譬如,如下代码

        // 文件a.js
        var a = 1
        // 文件b.js
        console.log(a) // a is not defined.
    

    在这样的特性下,a.jsb.js都有着自己独有的作用域,要在b中对a进行访问,就需要一种加载机制,一般来说,有两种方法能够做到:

    方法1

        // 文件a.js
        global.a = 1
        // 文件b.js
        console.log(a) // 1
    

    这种方法挂载在global上,当然是不可取的。

    方法2

        // 文件a.js
        exports.a = 1
        // 文件b.js
        var moduleA = require('./a')
        console.log(moduleA.a)
    
    • AMD规范

    requirejs的出现让script标签的减少变成了可能,在requirejs的时代,我们一般会使用jQueryunderscore这类的类库,如果按照往常的样子我们会将代码写成下面这副模样:

    <script src="/js/lib/jquery.min.js"></script>
    <script src="/js/lib/underscore.min.js"></script>
    <script src="/js/app/index.js"></script>
    <script src="/js/app/app.js"></script>
    <!-- and so on... -->
    

    这样的代码乍一看似乎没什么问题,但是当一个项目的代码量上了一个量级,一切就变得不是这么回事儿了,你会被困在加载顺序,加载时间的问题上,这也就是requirejs能够出现的原因了。

    requirejs中,你可以如此改写以上代码:

        // `index.js`
       require(['js/lib/jquery.min', 'js/lib/underscore.min', 'js/app/app'], function($, _, app) {  /*  todo...  */  })
        // `app.js`
       define(['js/lib/jquery.min', 'js/lib/underscore.min'], function($, _) {  /*  todo...  */  })
    
    <script data-main="/index.js" src="/js/require.js"></script>
    

    这里当然显得更加优雅了,在requirejs的推广过程中,AMD规范也就应运而生了,那么,requirejs或者说AMD规范到底解决了什么样的问题呢,主要有几点:

    1. AMD是“异步模块定义”的缩写,也就是说,其中内容是异步加载的,从而让页面不被js的加载阻塞,最大程度上的避免了页面假死等情况的产生。
    2. AMD的一个好处在与依赖前置,所有被使用到的模块都会被提前加载好,从而加快运行速度。

    那么,commonjs规范和AMD规范有什么区别呢

    1. 运行环境不同,commonjs规范只能运行在node端,而AMD规范则被用到浏览器端
    2. 由于运行环境的不同,二者的加载机制也不同,commonjs中的require是同步执行的,而AMD中则是异步的。
    • ES2015模块化

    ES2015中,可以使用export, export default, import import * as 等操作符来作模块化的功能,但是这个规范现在尚未被任何浏览器加入规范中,我目前的Chrome版本为63.0.3239.132,也无法原生支持,不过现阶段我们几乎都用上了这个规范,这一切都只能归功于babel,webpackrollup等新工具的出现,既然如此,那就拥抱未来吧,不过有一点,需要在了解原理的前提下,不然,倘若有一天,真的需要我们来封装一个小小的模块的时候,没有了那些工具,我们该从何下手呢。

    相关文章

      网友评论

        本文标题:JavaScript模块化发展史

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