V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
wilddog
V2EX  ›  问与答

写了十年 JS 却不知道模块化为何物?

  •  2
     
  •   wilddog · 2015-11-10 18:03:32 +08:00 · 2713 次点击
    这是一个创建于 3302 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作者:肖光宇
    野狗科技联合创始人,先后在猫扑、百度、搜狗任职,爱折腾的前端工程师。
    野狗官博: 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 将不再借助模板引擎和预编译引擎。

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

    9 条回复    2015-11-11 13:52:44 +08:00
    hronro
        1
    hronro  
       2015-11-10 18:11:01 +08:00
    好文,收藏了
    wilddog
        2
    wilddog  
    OP
       2015-11-10 18:54:35 +08:00
    貌似选择了一个不太好的节点...
    EchoChan
        3
    EchoChan  
       2015-11-10 18:59:17 +08:00
    @livid 麻烦移到 JS 节点。
    wilddog
        4
    wilddog  
    OP
       2015-11-10 19:03:18 +08:00
    @EchoChan 我移动节点的时候系统提示我,不存在 JavaScript 这个节点... T.T
    wizardforcel
        5
    wizardforcel  
       2015-11-10 22:04:49 +08:00 via Android
    。。。前端好复杂
    chemzqm
        6
    chemzqm  
       2015-11-10 22:25:47 +08:00
    前端模块化至少 8 年了吧,我最开始用的 Ext1 就是完全模块化,只不过是依赖全局变量,后面的 YUI2 YUI3 实现了沙箱,动态 combo 之类的机制,再过了几年才出来 commonjs , AMD 这些更加标准化的规范。
    最后只不过换了下 API ,实现也从原来的框架挪动到了浏览器罢了
    oott123
        7
    oott123  
       2015-11-10 22:49:09 +08:00 via Android
    _(:з」∠)_ 为啥我感觉楼主的文真是越来越…湿了
    zhangv
        8
    zhangv  
       2015-11-11 11:07:28 +08:00
    看到标题还是很有感触的,虽然并不是前端。
    liuzhen
        9
    liuzhen  
       2015-11-11 13:52:44 +08:00
    前端越来越复杂了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5588 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 08:17 · PVG 16:17 · LAX 00:17 · JFK 03:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.