资讯专栏INFORMATION COLUMN

JavaScript模块化开发的演进历程

anonymoussf / 3101人阅读

摘要:参考精读模块化发展模块化七日谈前端模块化开发那点历史本文先发布于我的个人博客模块化开发的演进历程,后续如有更新,可以查看原文。

Brendan Eich用了10天就创造了JavaScript,因为当时的需求定位,导致了在设计之初,在语言层就不包含很多高级语言的特性,其中就包括模块这个特性,但是经过了这么多年的发展,如今对JavaScript的需求已经远远超出了Brendan Eich的预期,其中模块化开发更是其中最大的需求之一。

尤其是2009年Node.js出现以后,CommonJS规范的落地极大的推动了整个社区的模块化开发氛围,并且随之出现了AMD、CMD、UMD等等一系列可以在浏览器等终端实现的异步加载的模块化方案。

此前,虽然自己也一直在推进模块化开发,但是没有深入了解过模块化演进的历史,直到最近看到了一篇文章《精读JS模块化发展》,文章总结了History of JavaScript这个开源项目中关于JavaScript模块化演进的部分,细读几次之后,对于一些以前模棱两可的东西,顿时清晰了不少,下面就以时间线总结一下自己的理解:

1999年的时候,绝大部分工程师做JS开发的时候就直接将变量定义在全局,做的好一些的或许会做一些文件目录规划,将资源归类整理,这种方式被称为直接定义依赖,举个例子:

// greeting.js
var helloInLang = {
  en: "Hello world!",
  es: "¡Hola mundo!",
  ru: "Привет мир!"
};
function writeHello(lang) {
  document.write(helloInLang[lang]);
}

// third_party_script.js
function writeHello() {
  document.write("The script is broken");
}

// index.html



  
  Basic example
  
  



但是,即使有规范的目录结构,也不能避免由此而产生的大量全局变量,这就导致了一不小心就会有变量冲突的问题,就好比上面这个例子中的writeHello

于是在2002年左右,有人提出了命名空间模式的思路,用于解决遍地的全局变量,将需要定义的部分归属到一个对象的属性上,简单修改上面的例子,就能实现这种模式:

// greeting.js
var app = {};
app.helloInLang = {
  en: "Hello world!",
  es: "¡Hola mundo!",
  ru: "Привет мир!"
};
app.writeHello = function (lang) {
  document.write(helloInLang[lang]);
}

// third_party_script.js
function writeHello() {
  document.write("The script is broken");
}

不过这种方式,毫无隐私可言,本质上就是全局对象,谁都可以来访问并且操作,一点都不安全。

所以在2003年左右就有人提出利用IIFE结合Closures特性,以此解决私有变量的问题,这种模式被称为闭包模块化模式

// greeting.js
var greeting = (function() {
  var module = {};
  var helloInLang = {
    en: "Hello world!",
    es: "¡Hola mundo!",
    ru: "Привет мир!",
  };

  module.getHello = function(lang) {
    return helloInLang[lang];
  };

  module.writeHello = function(lang) {
    document.write(module.getHello(lang));
  };

  return module;
})();

IIFE可以形成一个独立的作用域,其中声明的变量,仅在该作用域下,从而达到实现私有变量的目的,就如上面例子中的helloInLang,在该IIFE外是不能直接访问和操作的,可以通过暴露一些方法来访问和操作,比如说上面例子里面的getHellowriteHello2个方法,这就是所谓的Closures。

同时,不同模块之间的引用也可以通过参数的形式来传递:

// x.js
// @require greeting.js
var x = (function(greeting) {
  var module = {};

  module.writeHello = function(lang) {
    document.write(greeting.getHello(lang));
  };

  return module;
})(greeting);

此外使用IIFE,还有2个好处:

提高性能:通过IIFE的参数传递常用全局对象window、document,在作用域内引用这些全局对象。JavaScript解释器首先在作用域内查找属性,然后一直沿着链向上查找,直到全局范围,因此将全局对象放在IIFE作用域内可以提升js解释器的查找速度和性能;

压缩空间:通过参数传递全局对象,压缩时可以将这些全局对象匿名为一个更加精简的变量名;

在那个年代,除了这种解决思路以外,还有通过其它语言的协助来完成模块化的解决思路,比如说模版依赖定义注释依赖定义外部依赖定义等等,不过不常见,所以就不细说了,究其本源,它们想最终实现的方式都差不多。

不过,这些方案,虽然解决了依赖关系的问题,但是没有解决如何管理这些模块,或者说在使用时清晰描述出依赖关系,这点还是没有被解决,可以说是少了一个管理者。

没有管理者的时候,在实际项目中,得手动管理第三方的库和项目封装的模块,就像下面这样把所有需要的JS文件一个个按照依赖的顺序加载进来:












如果页面中使用的模块数量越来越多,恐怕再有经验的工程师也很难维护好它们之间的依赖关系了。

于是如LABjs之类的加载工具就横空出世了,通过使用它的API,动态创建

阅读需要支付1元查看
<