作者:肖光宇
野狗科技联合创始人,先后在猫扑、百度、搜狗任职,爱折腾的前端工程师。
野狗官博: https://blog.wilddog.com/
野狗官网: https://www.wilddog.com/
公众订阅号: wilddogbaas
转载请保留以上信息。
模块化这个问题并非一开始就存在, WWW 刚刚问世的时候, html , JavaScript , CSS ( JS 和 CSS 都是后来在网景被引进浏览器的)都是极其简单的存在,不需要模块化。
模块化的需求是规模的产物,当 web page 进化到 web application ,浏览器端处理的逻辑越来越复杂,展现的样式和动画越来多,对于工程的要求也就越来越高。于是模块化的需求也就产生了。模块化的意义:
JavaScript 长久以来被认为是简单的脚本语言,实际上情况早就发生来变化,在最新版的 ECMA-262 ( ES6 )文档中强调 JavaScript 是通用编程语言而不是脚本语言。脚本语言,比如 shell 并不是用来完成复杂功能的,只是用来做一些自动化控制,是不需要模块化的。而用于构建复杂系统通用编程语言(比如 Java )一般都有模块的实现。
ES6 之前, JavaScript 并没有原生的模块机制,好在 JavaScript 非常灵活,有很多种写法可以将代码天然隔离,起到模块化的功能:
//define
var modules = {}
modules.mod1 = {
foo : function(){...},
bar : function(){...}
...
}
//call
modules.mod1.foo()
在客户端这种方式基本是够用的,然而问题依然存在:你无法管理依赖,所有的代码都必须 load 到内存中,需要哪些模块必须由人工处理。分模块是工程化的产物,也是自然发展的结果,自然有很多尝试。很显然,模块之间互相依赖需要编写模块的时候遵循一定的规范。现存的规范还真不少,不知道 ES6 能否终结这场混战:
AMD 和 CMD 分别是 requireJS 和 seaJS 定义的标准。使用纯原生的 ES5 语法意味者其只能使用闭包,书写和阅读都很怪异。值得一提的是 AngularJS 也使用类似的方式,以至于 Angular 的作者们都受不了,决定在 AngularJS 2 使用新的语言 AtScript ,前端轮子太多,又造了一个,好在这个轮子造的比较好,兼容 ES6 TypeScript 规范,扯的远了,看看 AMD 长得啥样:
AMD:
define(['./a', './b'], function(a, b) {
...
})
Closure 是 google 出品的前端工具, Closure 提供了一系列工具和库,谷歌自己的多个项目都是使用 Closure 开发的。 closure compiler 通过模块间依赖的声明把所有被依赖的文件打包到一起,而且 Closure 的一大优势是如果采用破坏性压缩( ADVANCED )压缩率极高。
//文件 A
goog.provide('module1')
com.foo.bar = {
...
}
....
//文件 B
goog.require('module1')
var a = com.foo.bar;
然而 Closure 并不完美,不同的文件共享同一个全局对象,所以你不得不这样写 a.b.c=...。
CommonJS 是 Node.js 使用的模块化标准。 Node.js 对于前端开发者来说不仅仅可以提供一个 Server ,还是一个完美的开发平台,在 Node 上使用 Grunt/gulp 构建 web 项目是件很爽的事情。 Node 的模块化声明的方式与 Closure 类似,只是更进一步,天然隔离了命名空间。上面的代码如果使用 CommonJS 的模块化规范可以这么写:
//文件 A
module.exports = {...}
....
//文件 B
var a = require('./foo/bar')
browserify 让使用 CommonJS 模块化规范的代码可以运行在客户端上。
ES6 之前我们先看模块加载的两种方式:
AMD 标准是动态加载的代表,而 CommonJS 是静态加载的代表。 AMD 的目的是用在浏览器上,所以是异步加载的。而 NodeJS 是运行在服务器上的,同步加载的方式显然更容易被人接收,所以使用了 CommonJS 。同样的道理,如果静态加载,那就使用同步的加载方式,如果动态加载就必须用异步的加载方式。
那么 ES6 采用何种加载机制?
ES6 既希望用简单的声明方式来完成静态加载,又不愿放弃动态加载的特性,而这两种方式几乎不可能简单的同时实现,所以 ES6 提供了两种独立的模块加载方法。
2.1 声明的方式
import {foo} from module1
2.2 通过 System.import API 的方式
System.import('some_module')
.then(some_module => {
// Use some_module
})
.catch(error => {
...
});
再看下 export 的语法,与 CommonJS 很像,只不过没有了 module 这个对象,而直接调用 export 。 可以 export 任何一个 函数,变量,对象
//expt.js
export function abc(){}//export 一个命名的 function
export default function(){} //export default function
export num=123 //export 一个数值
export obj={}
export { obj as default };
//import
import expt from 'expt'//default export
import {default as myModule} from 'expt' //rename
import {abc,num,obj} from 'expt'
更多细节可以看这篇文章: http://www.2ality.com/2014/09/es6-modules-final.html
目前来看,使用预编译的方式显然要好于使用动态加载,浏览器对 ES6 语法支持还很差,如果使用动态加载 ES6 ,在浏览器端要做 ES6 到 ES5 的翻译工作,这个显然是重复低效的。但是随着浏览器对 ES6 支持增强,尤其是浏览器实现了动态加载 API 后,动态加载的优势就会展现:
如果现在使用 ES6 ,可以选择动态加载模块 system.js 或者 browserify 的预编译方法。
使用 system.js+babel 动态加载依赖。 system.js 是 ES6 动态模块加载的一个实现。写了一个小 DEMO :
项目初始化
bower install babel system.js --save
index.html
...
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
baseURL : "/scripts",
transpiler : 'babel',
map : {
babel:'/bower_components/babel/browser.js'
}
}
)
System.import('main.js').then(function(m){
m.default.sayHello()
})
</script>
...
main.js
export default {
sayHello : function(){
console.log('hello')
}
}
项目的地址在: https://github.com/stackOverMind/demo-system.js
使用 gulp+browserify+babel 预编译。 gulp 是一个 Node.js 平台上的任务管理平台。预编译要做很多配置,非常繁琐,推荐使用 yeoman 来生成项目骨架。比如使用 generator-es6-webapp 。
生成非常简单,在项目目录中执行
yo es6-webapp
缺少依赖的化安装依赖就好。
ES6 模块化意味着什么?
更强大的前端, Web 技术整体前移。 HTML5 的发展和某些优秀浏览器的支持让 web 技术整体前移,以前像渲染这种工作在后端进行是由于浏览器薄弱,且有老 IE 这种拖后腿捣乱的选手。
简化编程模型,人工管理 JS 依赖和将多个 JS 打包这种工作可以不需要了,而配合 WebComponents 标准,开发 Web 将不再借助模板引擎和预编译引擎。
前端化还有更深远的影响--在过去浏览器是个工具,现在浏览器是个重要的工具,在未来浏览器就是用户唯一的操作系统。
1
hronro 2015-11-10 18:11:01 +08:00
好文,收藏了
|
2
wilddog OP 貌似选择了一个不太好的节点...
|
5
wizardforcel 2015-11-10 22:04:49 +08:00 via Android
。。。前端好复杂
|
6
chemzqm 2015-11-10 22:25:47 +08:00
前端模块化至少 8 年了吧,我最开始用的 Ext1 就是完全模块化,只不过是依赖全局变量,后面的 YUI2 YUI3 实现了沙箱,动态 combo 之类的机制,再过了几年才出来 commonjs , AMD 这些更加标准化的规范。
最后只不过换了下 API ,实现也从原来的框架挪动到了浏览器罢了 |
7
oott123 2015-11-10 22:49:09 +08:00 via Android
_(:з」∠)_ 为啥我感觉楼主的文真是越来越…湿了
|
8
zhangv 2015-11-11 11:07:28 +08:00
看到标题还是很有感触的,虽然并不是前端。
|
9
liuzhen 2015-11-11 13:52:44 +08:00
前端越来越复杂了
|