说在前面
注意,本文讲的比较通俗,比较适合初学者阅读。本文所说的模块化主要讲解将常用,通用的组件提炼封装成模块在项目中使用,或者发布到npm网站上给他人使用,这与业务中将庞大的应用划分成各个业务模块有些区别。或许应该叫‘提炼封装’更恰当,恕我没找到更贴切的词汇来形容,请读者正确理解。
本文会涉及的相关内容如下:
(1)CommonJS简单介绍
(2)AMD简单介绍
(3)Javascript ECMAScript6.0 的模块化
(4)mocha
(5)npm模块介绍,发布,并在node.js,前端中使用
如果你只想了解如何发布npm模块请忽略其他直接看(5);

一,模块化的发展和重要性
模块化在其他语言如java中,早已经在语言层面实现。然而在javascript中并没有;但是要构建大的应用就必须利用这种思想来划分模块,开发人员最初通过把要模块化的代码放在一个对象中来实现,后来开发人员通过在js框架层面来弥补,如CommonJS规范的sea.js,AMD规范的require.js;这些思想被采纳到后来的Javascript ECMAScript6.0(后面简称es6)中,将模块化在语言层面实现。
模块化(again,这里指的是提炼封装成通用模块)有需要好处:
(1)提炼封装成模块化,代码复用,可维护,可扩展,类似代码不用复制粘贴代码来完成。
(2)提炼封装的代码往往有经过测试,经过线上考验,稳定可靠,不容易出错;
(3)提炼的代码可以提供给他人使用,不断演进,代码质量高,兼容性强;
(4)方便使用,团队协作分工,提高生产效率,提高开发水平;
(5)促进javascript生态更加繁荣,促进前端技术发展,自身开发应用更加容易;
提炼封装成模块或者成为一个js框架,都需要支持某种规范,比如CommonJS,AMD等,下面会详细介绍;

二,CommonJS(更适合服务端的模块化规范)
CommonJS是一种比较简单的规范,与AMD不同的是他是同步加载的,更偏向服务端;在前端领域就有一个非常大的平台使用这种规范,就是前端开发者不能不知道的Node.js;他是使用CommonJS规范的极佳案例。
那么如果前端强袭使用可不可以呢?答案是肯定的。只是他提供不了某些功能,比如异步加载,IO等。
看个简单的例子

  
 var yitala = function(){
    };

    yitala.square = function(x){
        return x*x;
    };

    module.exports = yitala;

module是一个加载器,他是公共的;可以node.js环境中打印看一看,console.log(module)

  {
        id: '/Users/yitala/Documents/lww_workspace/front-end/node_express_learn/app.js',
        exports: {},
        parent: [Module],
        filename: '/Users/yitala/Documents/lww_workspace/front-end/node_express_learn/app.js',
        loaded: false,
        children: [Array],
        paths: [Array] }

其中包含exports属性,凡是在exports中定义的变量,方法,对象等任何内容,都暴露给外部访问;没有导出的也就是没有在exports中的内容都是私有的,外部不开可访问;
你可能见过以下几种导出的写法

(1) 导出对象

  function init(){
    ...
  }
  module.exports = {
    init:init
  }
  

(2)直接导出方法

 module.exports = function(){
    ...
  }

(3)省略module

exports.square = function(){
  
  }

(4)很多exports

 exports.constant = 999;
  exports.square = function(){
  
  }

或者前面加有module

  module.exports.constant = 999;
  module.exports.square = function(){
  
  }

随便他花样怎么变,始终都是exports对象中的属性或者方法,或者对象,或者其他;
如何使用也根据他的花样来,记住一个口诀,exports出来的是什么,引用的就是什么,拿上面的(2)和(4)举例(我感觉有代表性):
(2)

var myModule = require('js路径');
      myModule();//方法,直接调用

(4)

var myModule = require('js路径');
     myModule.constant;//通过属性访问
     myModule.square();

还可以这样
var {constant,square} = require(‘js路径’);
根据口诀,万变不离其宗,commonJS基本算是掌握了!
来看看实际中的使用,举个‘栗子‘!underscore.js框架的CommonJS骨架

  //------ underscore.js ------
    var _ = function (obj) {
        ···
    };
    var each = _.each = _.forEach =
        function (obj, iterator, context) {
            ···
        };
    module.exports = _;

    //------ main.js ------
    var _ = require('underscore');
    var each = _.each;

果然,差不多!
那么,相信你现在已经知道如何让自己的模块遵循CommonJS规范了,你的js模块可以在node.js中运行做服务端开发了,也可以在sea.js等框架中引用了放浏览器运行了,
也可以在支持在webpack,parcel等构建工具中编译下在前端浏览器运行了。
一个标准的框架不仅要支持CommonJS,也需要支持AMD规范,接下来让你的程序更进一步,支持AMD规范

三,AMD规范
与CommonJS规范不同,AMD更偏向在前端也就是浏览器端使用,他可以异步加载,实现IO,system等(具体不再展开,有兴趣的读者可自行查阅);
AMD中有个明显的词汇就是define,下面举个简单的’栗子’!

  define('myModule',[],function(){

        return {
            //请平方
            square: function(x){
                return x*x;
            }
        }

    });
    

define的第一个参数是自身的模块名,给外部使用;第二个参数是引用其他模块的数组,[‘jQuery’,’undercore’]等等;这里高能!!给其他模块用,用其他模块的配置都在这里
第三个参数是第二个参数的别名,加入依赖模块是[‘jQuery’,’undercore’],第三个函数的参数应该为funcction(jQuery,undercore){…},这里仅仅是名字,你大可以取你喜欢的名字,如function($,_){…}

如何使用呢?

 require(['./myModule.js'],function($){
    myModule.square(2);
})

加载依赖包,然后就是调用了
顺便加载其他模块

 require(['./myModule.js','jquery'],function(yitalautil,$){
    myModule.square(2);
    $("body").append('

jquery

') })

如你所见,AMD规范写起来也比CommonJS麻烦,是的没错!

现在你又会了AMD的规范了,但是注意,AMD规范在node.js中无法使用,在parcel构建工具中也无法识别,他只适合配合Require.js和经过支持AMD的webpack构建工具编译后在前端使用;

四,CommonJS和AMD在常见js框架的身影
在封装模块时最好两种规范都支持,现在高版本的js框架中都能看到兼容二者的身影。如非常常用的jQuery,underscore等,下面去他们的源码里面找找看
1,jQuery
(1)jQuery支持CommonJS规范的代码

(2)jQuery支持AMD规范的代码

当然,jQuery也支持闭包方式的模块化,jQuery对象直接挂在window对象的属性中;
2,undercore
(1)undercore支持CommonJS规范的代码

(2)undercore支持AMD规范的代码

当然,同样undercore也支持闭包方式的模块化,类似jQuery;

五,横空出示的ES6,在语言层面实现模块化
ES6的出现是一个时代性的划分,至此,前端开发人员逐渐使用ES6来实现模块化,经过构建工具如wepback,parcel,gulp等构建之后在前端使用。特别注意,Node.js不支持ES6的模块化,只支持CommonJS规范。
主流的SPA框架React,Vue,Angular等,都是利用上述的原理来实现模块化。通过require.js和来实现模块化的情况将会不断减少
它的语法较CommonJS规范比较相近,举个’栗子’!

 export const LABEL = 'Hello word!';
 export function square(x){
    return x*x
 }
 

导出常量,函数,对象,也是均可;
还能导出默认

 export default function square(x){
    return x*x
 }

但是一个文件只能到导出一个

如何使用?

 import { square } from 'js路径'
 还可以
 import square from 'js路径';
 还可以
 import * as lib from 'js路径';

太多花样,具体可参考这个文档,非常全面,弄懂es6模块看这本书就够了
http://exploringjs.com/es6/ch_modules.html

大家都知道es6在浏览器中还不能直接使用,必须经过babel编译转换成低版本es5;

现有的npm上的模块既有CommonJS的,也有ES6的,(AMD的不常见);
是否都支持CommonJS,AMD,ES6取决你的使用场景,如果你的模块是服务端的封装模块,如node.js中间件,希望只在node.js中使用,那边用CommonJS即可
如果你的是在前端中使用,你的项目又经过构建工具编译,使用es6即可。如果你的模块需要兼容各种常见,像jQuery一样,可以在node中,需要或者不需要构建工具编译都能在前端使用,那边你还要用ES5并提供CommonJS,AMD等方案;

好了是时候了,现在你对模块化的规范都已经了解了,前方高能!请提高注意力!

六,封装一个你的模块发布到npm上
1,先注册一个npm账号,并记住账号密码,后面发布的时候会使用
2,初始化一个npm包
npm init 填写信息,信息不是很重要,如果直接使用默认,执行npm init -y,会填好一些默认信息,需要写的后面编辑package.json即可
看package.json中,有以下几个信息非常重要
(1)name模块名?你见过require(‘模块名’)类似的引用吧,是的没错,’模块名’就是这个name;
(2)version信息也非常重要,你每次发布都不能相同,相同也发布不了,每次版本都需要更新
(3)main,你的模块主入口,引用require(‘模块名’),会引用这个主入口,可参考其他模块
举例:
module.exports = require(‘./js/yitalautil-c’);
3.根据上述写你的模块代码
4.写好模块代码需要测试,专业的人都这么做
使用mocha模块来测试
npm i mocha –save-d

  // test.js
  var assert = require('assert');
    var yitala = require('../js/yitalautil-c');

    describe('yitala',function(){

        describe('square',function(){
            it('测试求平方',function(){
                assert.equal(4,yitala.square(2));
            });
        })

    });

package.json文件中增加命令,”test”: “mocha ./test/test.js”
运行npm run test;
测试通过,可以发布了
5. npm run publish;
填写账号密码,发布成功

如果你npm设置了淘宝镜像,需要切回npm才能发布
设置淘宝镜像
npm config set registry https://registry.npm.taobao.org
还原npm
npm config set registry http://registry.npmjs.org
6. 使用
var yitala = require(‘yitala’)

Categories: node.js

发表评论

电子邮件地址不会被公开。 必填项已用*标注