资讯专栏INFORMATION COLUMN

兼容多种模块规范(AMD,CMD,Node)的代码

Shonim / 1676人阅读

摘要:主要区别是需要在声明模块时指定所有的依赖,通过形参传递依赖到模块内容中。

前言

昨天,公司同事问了我如下一个问题:

说他在看一个插件时,看到了源码结构如截图所示,他知道(function(){})()是一种立即执行函数,但是在截图中,最后的那个圆括号里又写了一个函数function($,ChineseDistricts){...},这个函数暂且称为“匿名函数1”,function (factory){...}暂且称为“”匿名函数2”,意思是不是:把匿名函数1传入到匿名函数2的参数factory中,然后检测当前环境。如果检测到了全局环境中存在exports对象,则证明是node环境,如果是node环境,则用factory(require("jquery"), require("ChineseDistricts"))这个方法来执行匿名函数1,因为Node是基于模块的,所以在Node中要使用这个插件的话,必须用require()方法把匿名函数2中需要的参数"$"和"ChineseDistricts"以模块的方式给引用进来?

看到截图的代码和它的疑问,请接着往下看......

一、兼容多种模块规范(AMD,CMD,Node)的代码

在JavaScript模块化开发中,为了让同一个模块可以运行在前后端,以及兼容多种模块规范(AMD,CMD,Node),类库开发者需要将类库代码包装在一个闭包内。

1.AMD规范

AMD,即“异步模块定义”。主要实现比如: RequireJS。

其模块引用方式如下:define(id?,dependencies?,factory);

其中,id及依赖是可选的。其与CommonJS方式相似的地方在于factory的内容就是实际代码的内容,下面是一个简单的例子:

define(function(){
  var exports = {};
  exports.say = function(){
    alert("hello");
  };
  return exports;
});

2.CMD规范

CMD规范,与AMD类似,区别主要在于定义模块和依赖引入的地方。主要实现比如: SeaJS。

主要区别是:AMD需要在声明模块时指定所有的依赖,通过形参传递依赖到模块内容中。

define(["dep1","dep2"],function(dep1,dep2){
  return function(){};
});

与AMD相比,CMD更接近Node对CommonJS规范的定义:

define(factory);

在依赖部分,CMD支持动态引入,如下:

define(function(require,exports,module){
  // Todo
});

require、exports、module通过形参传递给模块,在需要依赖模块时,随时调用require引入即可。

3.CommonJS的模块实现

CommonJS的模块引用使用require,如下:

var http = require("http");

4.Node的模块实现

在Node中引入模块,需要经过下面3个步骤:路径分析;文件定位;编译执行。主要用require()方法来查找模块。

5.兼容多种模块规范

为了让同一个模块可以运行在前后端,在开发过程中需要考虑兼容前后端问题,以下代码演示如何将hello()方法定义到不同的运行环境中,它能够兼容AMD、CMD、Node以及常见的浏览器环境中:

;(function (name, definition) {
  // 检测上下文环境是否为AMD或CMD
  var hasDefine = typeof define === "function",
    // 检查上下文环境是否为Node
    hasExports = typeof module !== "undefined" && module.exports;
 
  if (hasDefine) {
    // AMD环境或CMD环境
    define(definition);
  } else if (hasExports) {
    // 定义为普通Node模块
    module.exports = definition();
  } else {
    // 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
    this[name] = definition();
  }
})("hello", function () {
  var hello = function () {};
  return hello;
});
二、如何封装Node.js和前端通用的模块

在Node.js中对模块载入和执行进行了包装,使得模块文件中的变量在一个闭包中,不会污染全局变量,和他人冲突。

前端模块通常是我们开发人员为了避免和他人冲突才把模块代码放置在一个闭包中。

如何封装Node.js和前端通用的模块,我们可以参考Underscore.js 实现,他就是一个Node.js和前端通用的功能函数模块,查看代码:

// Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };
 
  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we"re in
  // the browser, add `_` as a global object via a string identifier,
  // for Closure Compiler "advanced" mode.
  if (typeof exports !== "undefined") {
    if (typeof module !== "undefined" && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

通过判断exports是否存在来决定将局部变量 _ 赋值给exports,向后兼容旧的require() API,如果在浏览器中,通过一个字符串标识符“_”作为一个全局对象;完整的闭包如下:

(function() {
 
  // Baseline setup
  // --------------
 
  // Establish the root object, `window` in the browser, or `exports` on the server.
  var root = this;
 
  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };
 
  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we"re in
  // the browser, add `_` as a global object via a string identifier,
  // for Closure Compiler "advanced" mode.
  if (typeof exports !== "undefined") {
    if (typeof module !== "undefined" && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }
}).call(this);

通过function定义构建了一个闭包,call(this)是将function在this对象下调用,以避免内部变量污染到全局作用域。浏览器中,this指向的是全局对象(window对象),将“_”变量赋在全局对象上“root._”,以供外部调用。

和Underscore.js 类似的Lo-Dash,也是使用了类似的方案,只是兼容了AMD模块载入的兼容:

;(function() {
 
  /** Used as a safe reference for `undefined` in pre ES5 environments */
  var undefined;
    /** Used to determine if values are of the language type Object */
      var objectTypes = {
        "boolean": false,
        "function": true,
        "object": true,
        "number": false,
        "string": false,
        "undefined": false
      };
  /** Used as a reference to the global object */
  var root = (objectTypes[typeof window] && window) || this;
 
  /** Detect free variable `exports` */
  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
 
  /** Detect free variable `module` */
  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
 
  /** Detect the popular CommonJS extension `module.exports` */
  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
 
/*--------------------------------------------------------------------------*/
 
  // expose Lo-Dash
  var _ = runInContext();
 
  // some AMD build optimizers, like r.js, check for condition patterns like the following:
  if (typeof define == "function" && typeof define.amd == "object" && define.amd) {
    // Expose Lo-Dash to the global object even when an AMD loader is present in
    // case Lo-Dash was injected by a third-party script and not intended to be
    // loaded as a module. The global assignment can be reverted in the Lo-Dash
    // module by its `noConflict()` method.
    root._ = _;
 
    // define as an anonymous module so, through path mapping, it can be
    // referenced as the "underscore" module
    define(function() {
      return _;
    });
  }
  // check for `exports` after `define` in case a build optimizer adds an `exports` object
  else if (freeExports && freeModule) {
    // in Node.js or RingoJS
    if (moduleExports) {
      (freeModule.exports = _)._ = _;
    }
    // in Narwhal or Rhino -require
    else {
      freeExports._ = _;
    }
  }
  else {
    // in a browser or Rhino
    root._ = _;
  }
}.call(this));

再来看看Moment.js的封装闭包主要代码:

(function (undefined) {
    var moment;
    // check for nodeJS
    var hasModule = (typeof module !== "undefined" && module.exports);
/************************************
        Exposing Moment
    ************************************/
 
    function makeGlobal(deprecate) {
        var warned = false, local_moment = moment;
        /*global ender:false */
        if (typeof ender !== "undefined") {
            return;
        }
        // here, `this` means `window` in the browser, or `global` on the server
        // add `moment` as a global object via a string identifier,
        // for Closure Compiler "advanced" mode
        if (deprecate) {
            this.moment = function () {
                if (!warned && console && console.warn) {
                    warned = true;
                    console.warn(
                            "Accessing Moment through the global scope is " +
                            "deprecated, and will be removed in an upcoming " +
                            "release.");
                }
                return local_moment.apply(null, arguments);
            };
        } else {
            this["moment"] = moment;
        }
    }
 
    // CommonJS module is defined
    if (hasModule) {
        module.exports = moment;
        makeGlobal(true);
    } else if (typeof define === "function" && define.amd) {
        define("moment", function (require, exports, module) {
            if (module.config().noGlobal !== true) {
                // If user provided noGlobal, he is aware of global
                makeGlobal(module.config().noGlobal === undefined);
            }
 
            return moment;
        });
    } else {
        makeGlobal();
    }
}).call(this);

从上面的几个例子可以看出,在封装Node.js和前端通用的模块时,可以使用以下逻辑:

if (typeof exports !== "undefined") {
    exports.** = **;
} else {
    this.** = **;
}

即,如果exports对象存在,则将局部变量装载在exports对象上,如果不存在,则装载在全局对象上。如果加上ADM规范的兼容性,那么多加一句判断:

if (typeof define === "function" && define.amd){}

参考链接:
1.http://www.css88.com/archives...
2.http://www.css88.com/archives...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89165.html

相关文章

  • 前端模块化进程,commonJS,AMDCMD对比

    摘要:是另一种模块化方案,它与很类似,不同点在于推崇依赖前置提前执行,推崇依赖就近延迟执行。 commonJS规范 随着前端技术的不断发展,项目越来越大,越来越不好管理,多人开发越来让开发者头疼,于是出现了命名空间,这没有办法的办法,但是所有变量都是全局的话,管理非常混乱,而且在Node 出现前,javascript 在服务器端基本没有市场,经过javascript 不断努力,社区为 jav...

    firim 评论0 收藏0
  • nodejs笔记-模块机制

    摘要:模块中定义的全局变量只作用于该文件内部,不污染其他模块。由纯编写的部分称为内建模块,例等模块部分使用编写。兼容多种模块规范检测是否为或者检测是否为或环境定义为普通模块将模块执行结果挂载在对象下 1.为什么要CommonJS规范 javascript存在的缺点 没有模块系统 标准库比较少 没有标准接口 缺乏包管理系统 CommonJS规范的提出,弥补了javascript没有标准的缺...

    lscho 评论0 收藏0
  • OMD: javascript模块化开发兼容CommonJS, AMD, CMD 以及 原生 JS

    摘要:它就是一套兼容方案,目前兼容的有以及原生支持。返回值问题在第一次使用时,。具体是什么意义呢的返回值,其实就是插件提供的对外接口,而实际上,就是一个对象。而在环境下,只需要将这个返回值赋予即可完成该模块的接口。 有更新,请到github上看源码 什么是OMD 在node.js流行起来之前,javascript的开发方式都是函数式的顺序依赖关系,直到node火起来。CommonJS其实首先...

    lavor 评论0 收藏0
  • 关于JavaScript模块规范之CommonJSAMDCMD

    摘要:所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。也采用语句加载模块,但是不同于,它要求两个参数第一个参数,是一个数组,里面的成员就是要加载的模块第二个参数,则是加载成功之后的回调函数。 本篇文章来自对文章《js模块化编程之彻底弄懂CommonJS和AMD/CMD!》的总结,大部分摘自文章原话,本人只是为了学习方便做的笔记,之后有新的体会会及时补充...

    binaryTree 评论0 收藏0
  • 前端工程师必备:前端模块

    摘要:规范则是非同步加载模块,允许指定回调函数,可以实现异步加载依赖模块,并且会提前加载由于主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以规范比较适用。 JS模块化 模块化的理解 什么是模块? 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起; 块的内部数据/实现是私有的, 只是向外部暴露一些接口(...

    Render 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<