JavaScript模块化发展史

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

(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等新工具的出现,既然如此,那就拥抱未来吧,不过有一点,需要在了解原理的前提下,不然,倘若有一天,真的需要我们来封装一个小小的模块的时候,没有了那些工具,我们该从何下手呢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容