作者:肖光宇 野狗科技联合创始人,先后在猫扑、百度、搜狗任职,爱折腾的前端工程师。 野狗官博: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)一般都有模块的实现。

1.模块化标准

ES6之前,JavaScript并没有原生的模块机制,好在JavaScript非常灵活,有很多种写法可以将代码天然隔离,起到模块化的功能:

//define
var modules = {}
modules.mod1 = {
foo : function(){...},
bar : function(){...}
...
}
//call
modules.mod1.foo()

在客户端这种方式基本是够用的,然而问题依然存在:你无法管理依赖,所有的代码都必须load到内存中,需要哪些模块必须由人工处理。分模块是工程化的产物,也是自然发展的结果,自然有很多尝试。很显然,模块之间互相依赖需要编写模块的时候遵循一定的规范。现存的规范还真不少,不知道ES6 能否终结这场混战:

  • AMD
  • CMD
  • closure
  • CommonJS
  • 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模块化规范的代码可以运行在客户端上。

2.静态加载与动态加载

在看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后,动态加载的优势就会展现:

  • 更流畅的用户体验,动态加载可以实现类似lazyload的加载方式,将download的时间分散
  • 更简洁的项目,无需预编译,项目可以少配置很多工具
  • HTTP/2的普及更倾向于使用多个小的请求,适合动态加载

3.实践

如果现在使用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  

缺少依赖的化安装依赖就好。

4.其他,关于前端化趋势

ES6模块化意味着什么?

更强大的前端,Web技术整体前移。HTML5的发展和某些优秀浏览器的支持让web技术整体前移,以前像渲染这种工作在后端进行是由于浏览器薄弱,且有老IE这种拖后腿捣乱的选手。

简化编程模型,人工管理JS依赖和将多个JS打包这种工作可以不需要了,而配合WebComponents标准,开发Web将不再借助模板引擎和预编译引擎。

前端化还有更深远的影响–在过去浏览器是个工具,现在浏览器是个重要的工具,在未来浏览器就是用户唯一的操作系统。

写了十年JS却不知道模块化为何物?的更多相关文章

  1. Python第十四天 序列化 pickle模块 cPickle模块 JSON模块 API的两种格式

    Python第十四天 序列化  pickle模块  cPickle模块  JSON模块  API的两种格式 目录 Pycharm使用技巧(转载) Python第一天  安装  shell  文件 Py ...

  2. Python基础总结之第十天开始【认识模块、包和库】(新手可相互督促)

    每天都有一种备课的赶脚~~~ 什么是模块? 在实际的开发过程中,代码量肯定有成千上万行的代码,甚至十几万行代码也很正常吧... 那么这么多的代码如果放在一个文件中,肯定是很不合适的,为了以后程序的编写 ...

  3. 浅析JS中的模块规范(CommonJS,AMD,CMD)////////////////////////zzzzzz

    浅析JS中的模块规范(CommonJS,AMD,CMD)   如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已.     ...

  4. JS中的模块规范(CommonJS,AMD,CMD)

    JS中的模块规范(CommonJS,AMD,CMD) 如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已. 现在就看看吧, ...

  5. node.js使用mysql模块的坑

      之前用node.js写的订餐系统,很容易挂掉,一直也没想去解决它.今天看了一下,试了试,原因是在连接数据库的时候没有对error事件进行处理,导致程序一直挂在那里,需要重启服务才能正常使用.   ...

  6. node.js入门(二) 模块 事件驱动

    模块化结构 node.js 使用了 CommonJS 定义的模块系统.不同的功能组件被划分成不同的模块.应用可以根据自己的需要来选择使用合适的模块.每个模块都会暴露一些公共的方法或属性.模块使用者直接 ...

  7. 理解JS中的模块规范(CommonJS,AMD,CMD)

    随着互联网的飞速发展,前端开发越来越复杂.本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及如何使用 Sea.js 进行前端的模块化开发. 恼人的命名冲突 我们从一个简单的习惯出发.我做 ...

  8. 孤荷凌寒自学python第二十六天python的time模块的相关方法

    孤荷凌寒自学python第二十六天python的time模块的相关方法 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 要使用time模块的相关方法,必须在文件顶端引用: import tim ...

  9. 浅析 Node.js 的 vm 模块以及运行不信任代码

    在一些系统中,我们希望给用户提供插入自定义逻辑的能力,除了 RPC 和 REST 之外,运行客户提供的代码也是比较常用的方法,好处是可以极大地减少在网络上的耗时.JavaScript 是一种非常流行而 ...

随机推荐

  1. EXCEL中去掉撇号的操作方法

    ▲数字前带撇号 选定想去掉撇号的列,然后选“数据”→“分列”在弹出的 对话框中单击“下一步” ,在“文本标识符号”处选择“'” 单击 “完成”即可

  2. swift - VC添加手势返回

    1.需要添加手势的界面 (1)addBackGesture() (2) 设置手势返回代理 // MARK: - 添加返回手势 extension JYRTSShopDetialConteoller:U ...

  3. 关于MYSQL字符集问题(一)

    MySQL的字符集支持(Character Set Support)有两个方面: 字符集(Character set)和排序方式(Collation). 对于字符集的支持细化到四个层次: 服务器(se ...

  4. android项目安装报错:INSTALL_FAILED_CONFLICTING_PROVIDER

    这主要是由于调试的环境中已有一个同名的Provider存在. 解决方法是修改AndroidManifest.xml中的 <provider android:name="applockP ...

  5. 31-mysql 代码建立数据库

    给个例子,模仿即可: drop database if exists tt; create database tt default character set utf8; use tt; create ...

  6. 快速排序中BUG int 与 int *

    #include <iostream>using namespace std;int QKPass(int* , int , int);  //若声明为 int QKPass(int, i ...

  7. java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet解决

    spring配置之后启动报错,如下: java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServl ...

  8. Ubuntu1.6安装Go【小白版】

    [安装golang,并配置环境变量]1.将go下载到Home目录并解压 一键解压会 自动会解压到 Home/go目录. 2.设置环境变量 nano是一种文本编辑器,也可以用其他的编辑器. 输入以下命令 ...

  9. java命令行调用本地文件协议hikvideoclient://

    最近在做一个视频项目,项目中需要通过调用海康本地协议打开视频播放器,起初尝试通过Process/ProcessBuilder无解,因为这个是调用本地应用程序的. 我要调用的是本地伪协议,最终通过一些研 ...

  10. Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools"

    https://blog.csdn.net/saucyj/article/details/79043443