Javascript『模块化』(二)- 现代标准

阅读 479
收藏 48
2017-07-06
原文链接:larabase.com

现代JS模块化介绍

上一篇文章我们介绍了JS基本的作用域分隔的办法,它们有一个问题,就是命名空间还是在全局作用域里。这篇文章我们来说说Modern Javascript开发中常用到的 CommonJS,AMD,UMD,Native JS 等模块化规范,这些规范都是成熟的规范,可以解决之前的问题。

WTF?不就是搞个Class出来么,JS你为啥搞这么多规范,至于么,而且每个规范都对应一个厚厚的文档。

没事,看完本文你就秒懂了。

CommonJS

CommonJS是一个JS自愿者组织搞出来的标准。比较适合在服务器端运行的场景,所以在node.js 里用得最多。

它的概念很简单,就是把不同的模块(class)写在不同的文件里,每个文件就相当于一个闭包作用域,最后在主程序里引用这个文件使用。这太容易理解了,PHP不就是这样的么。

//name.js

   var first_name = 'Tom';
           var last_name = ' Cruise';
           var fullname = function (){
                return first_name + last_name;
           }
            module.exports.first_name = first_name;
            module.exports.last_name = last_name;
            module.exports.fullname = fullname;

//app.js

  var myName = require('./name.js');
          //注意看这里,怎么调用模块的属性和方法
          console.log(myName.first_name); // Tom
          console.log(myName.last_name); // Cruise
          console.log(myName.fullname); // Tom Cruise

这个定义调用的过程倒是很清楚,但是你去写就会发现行不通,module.exports,require这个是从哪里来的?
你需要使用这种模块化加载方式,得先加载一个库 requirejs.org;
如果是在 node.js环境下,可以直接使用。
还有就是,这里有2个文件,实际使用可能会有n个文件,这在服务器端使用没问题的(就像PHP),但我怎么在 浏览器端使用? CommonJS对模块文件的加载,是同步的,也就是说,浏览器得把所有模块文件加载完,程序才能运行。一个一个加载?太不靠谱了吧。
在本次教程的后期,我们会介绍一个概念,打包(bundle), 就是如果有多个文件,打包工具会把它们合并成一个文件,再在浏览器端使用。

学个小JS真费劲啊,麻烦死了。

上面的那个模块文件,我们稍微改一下,让它更容易编写://name.js

    function myName(){
            this.first_name = 'Tom';
           this.last_name = ' Cruise';
           this.fullname = function (){
                return this.first_name + this.last_name;
           }
            }
            module.exports = myName;

意思是先做成一个对象,然后整体导出,这样写得简单点,在外面使用是一样的。

AMD

CommonJS加载模块是同步的,AMD就是对CommonJS加载方式修改了一下,使它支持异步加载,主要是在浏览器端使用,所以AMD的全称是”Asynchronous Module Definition”,意思就是”异步模块定义”。

异步加载的好处就是,等模块下载好了我再执行逻辑,没下载好就不执行。

我们看看怎么写。首先,AMD模块也是在require.js的环境下运行的。

由于在浏览器端使用,所以所有代码写一个文件里:

define(['myName'], function() {
           return {
                   first_name:'Tom',
                   last_name:' Cruise',
                   fullname: function (){
                        return this.first_name + this.last_name;
                       }
           }
           });

我们看到,AMD在定义的模块的时候,写得还是很清晰的,define关键字,然后在数组里起一个模块的名字,最后在闭包里返回一个对象就可以了。

然后我们看看在主程序中如何调用,这里是关键,调用方式的不同,让AMD具备了异步能力:

//使用

require(['myName'], function (myName) {
            console.log(myName.fullname());
          });

和CommonJS相比,同样是用require方法,但是:

  1. 模块是从数组里拿,而不是从文件;
  2. 多了一个回调参数,回调里面写具体的业务逻辑;

写在回调里面的逻辑只有当模块myName加载完毕后才会执行,所以它可以实现不同模块异步加载,异步执行。

另外,CommonJS 只能输出对象,而AMD方式支持对象,函数,构造器,字符串,json等数据形式作为模块输出。

但是AMD不支持io和文件形式的模块,那是服务器端才有的特性。

UMD

UMD(Universal Module Definition)的发明是为了让代码即能在浏览器端使用,也能在服务器端使用,它其实就是把CommonJS和AMD标准合并了。

我们来看下代码体会一下:

(function (root, factory) {
          if (typeof define === 'function' && define.amd) {
              // AMD
            define(['myModule'], factory);
          } else if (typeof exports === 'object') {
              // CommonJS
            module.exports = factory(require('myModule'));
          } else {
            // 全局变量( root就是window对象)
            root.returnExports = factory(root.myModule);
          }
          }(this, function (myModule) {
          // 模块内部定义
           var first_name = 'Tom';
           var last_name = ' Cruise';
           var fullname = function (){
                return first_name + last_name;
           }
          // 暴露在外面的部分
          return {
              first_name: first_name,
              fullname: fullname
          }
          }));

我们看到整个逻辑无非是做了一个判断,在不同的环境执行不同的标准,但模块输出部分是差不多的。

CMD

国内有一个叫做玉伯的大神,搞了一个和require.js差不多的sea.js库,并推出了一个和AMD差不多的标准,叫CMD。这个项目现在已经关闭了,我就不做过多介绍了。

Native JS

你如果可以看到这里还没放弃,我真的很佩服你。

这篇文章到这里才是关键,前面介绍的内容或多或少都有点过时了,到了现在,js发生了一些重大的变化趋势:

  • JavaScript新的模块标准导致了SeaJS和RequireJS的过时
  • 原生选择器的良好支持,导致人们对jQuery不再那么依赖
  • Array和Object上面一些新特性的出现,导致underscore和lodash的作用减弱

所以,现在学习ES6及更高级的标准,还有Node.js才是王道。

ES6 就对之前介绍的复杂模块化方法进行了整合和优化,具备诸多优点,它是原生的。

我们快速的看一下ES6模块化的写法:

// lib/counter.js
        var counter = 1;
        function increment() {
          counter++;
          }
        function decrement() {
          counter--;
          }
        module.exports = {
          counter: counter,
          increment: increment,
          decrement: decrement
          };
        // src/main.js
        var counter = require('../../lib/counter');
        counter.increment();
        console.log(counter.counter); // 1

仔细观察它输出的方式,counter,increment,decrement都是独立的,互不影响,所以counter.increment(),它不会给属性counter加1。
这种写法保证了足够的灵活性。

当然,如果你想让counter,increment,decrement这几个元素组合成一个类,像一个Class运作,可以这样写:

// lib/counter.js
        export let counter = 1;
        export function increment() {
          counter++;
          }
        export function decrement() {
          counter--;
          }
        // src/main.js
        import * as counter from '../../counter';
        console.log(counter.counter); // 1
        counter.increment();
        console.log(counter.counter); // 2

这样counter.increment()就会和counter.counter联动了。
是不是超级强大。

更深入的内容,请看ES6文档。

最后

模块化的事情基本讲完了,但是蛋疼的事情并未结束。真正愉快的玩耍JS,你还需要了解打包(bundle),我会在下一次文章中介绍如下内容:

  • 为什么需要打包
  • 打包有哪些方法
  • ECMAScript的模块加载 API
  • 以及更多
    敬请关注。

最后吐槽一下,js是一个蛋疼的语言,PHP是世界上最好的语言。

评论