打⼀个通⽤UMD包
有这样⼀个场景,客户端运⾏很久,但是法务部和数据部需要收集⽤户的⼀些信息,这些信息收集好之后需要进⾏相应的数据处理,之后上报到服务端。客户端提供⼀个纯粹的 js 执⾏引擎,不需要 WebView 容器。iOS 端有成熟的 JavaScriptCore、Android 可以使⽤ V8 引擎。这样⼀个引擎配套有⼀个 SDK,访问 Native 的基础能⼒和数据运算能⼒,可以看成是⼀个阉割版的 Hybrid SDK 额外增加了⼀些数据处理能⼒。
问题结束了吗?处理逻辑的时候还需要⽤到2个库:cheerio 和 sql。因为都是 Node ⼯程,所以纯粹的 js 环境是没办法直接执⾏。所以需求就进⾏了转变 ———— 将 Node 项⽬打包成 UMD 规范。这样就可以在纯粹的 JS 环境下运⾏。接下来的⽂章就分析下各种规范。其实也就是前端模块化的⼏种规范。
前端模块化开发的价值
随着互联⽹的飞速发展,前端开发越来越复杂。本⽂将从实际项⽬中遇到的问题出发,讲述模块化能解决哪些问题,以及以 Sea.js 为例讲解如何进⾏前端的模块化开发。
1. 恼⼈的命名冲突
我们从⼀个简单的习惯出发。我做项⽬时,常常会将⼀些通⽤的、底层的功能抽象出来,独⽴成⼀个个函
数,⽐如
function  each(arr) {
// 实现代码
}
function  log(str) {
// 实现代码
}
并像模像样的将这些代码抽取出来并统⼀到 util.js 中,在需要使⽤的地⽅引⼊该⽂件,看起来很棒,团队内的同事很感激我提供了这么便利的⼯具包。
直到团队越来越⼤,开始有⼈抱怨
⼩杨:我定义了⼀个 each ⽅法遍历对象,但是 util.js 中已经存在⼀个 each ⽅法,每次都需要改⽅法名,我只能叫 eachObject ⽅法。<br>张三:我定义了⼀个 log ⽅法,可是王武的代码出问题了,谁来
看看?
抱怨越来越多,最后参照 Java 的⽅式,引⼊命名空间解决问题。于是 util.js 代码变成了nodejs字符串转数组
var  org = {};
org.Utils = {};
org.Utils.each = function (arr) {
// 实现代码
};
org.Utils.log = function (str) {
// 实现代码
};
可能看上去的代码很 low,其实命名空间在前端领域的布道者是 Yahoo!的 YUI2 项⽬,看看下⾯的代码,是 Yahoo!的⼀个开源项⽬
if (orgetd.Utils.isString(response)) {
return  orgetd.JSON.fromJSON(response);
}
if (orgetd.Utils.isArray(response)) {
return  response;
}
通过命名空间虽然可以极⼤的解决冲突问题,但是每次在调⽤⼀个⽅法时都需要写⼀⼤堆命名空间相关的代码,剥夺了编码乐趣。
另⼀种⽅式是⼀个⾃执⾏函数来实现。
(function (args) {
//...
})(this);
2. 繁琐的⽂件依赖
继续上述场景,很多情况下都需要开发 UI 层通⽤组件,这样项⽬组就不需要重复造轮⼦。其中有⼀个⾼频使⽤的组件就是 dialog.js
<script  src="util.js"></script>
<script  src="dialog.js"></script>
<script>
org.Dialog.init({  /* 传⼊配置 */  });
</script>
虽然公共组做项⽬都会编写使⽤⽂档、发送邮件告知全员(项⽬地址、使⽤⽅式等),但是还是有⼈问「为什么 dialog.js 有问题」,最后排查的结果基本都是没有引⼊ util.js
<script  src="dialog.js"></script>
<script>
org.Dialog.init({  /* 传⼊配置 */  });
</script>
命名冲突和⽂件依赖是前端开发中2个经典问题,经过开发者不断的思考和研究,诞⽣了模块化的解决⽅案,以 CMD 为例
define(function(require, exports) {
exports.each = function (array) {
// ...
};
exports.log = function(message) {
// ...
};
});
通过 exports 就可以向外提供接⼝, dialog.js 代码变成
define(function(require, exports) {
var  util = require('./util.js')
exports.init = function () {
// ...
};
});
使⽤的时候可以通过 require('./util.js') 获取到 util.js 中通过 exports 暴露的接⼝。 require 的⽅式在其他很多语⾔中都有解决⽅案:include、模块化的好处
1. 模块的版本管理:通过别名等配置,配合构建⼯具,可以轻松实现模块的版本管理
2. 提⾼可维护性:模块化可以实现每个⽂件的职责单⼀,⾮常有利于代码的维护。
3. 前端性能优化:对于前端开发来说,异步加载模块对于页⾯性能⾮常有益。
4. 跨环境共享模块: CMD 模块定义规范与 NodeJS 的模块规范⾮常相近,所以通过 Sea.JS 的 NodeJS 版本,可以⽅便的实现模块的
跨服务器和浏览器共享。
CommonJS 规范
CommonJS 是服务器端模块的规范。NodeJS 采⽤了这个规范。CommonJS 加载模块是同步的,所以只有加载完成后才能执⾏后⾯的操作。
因为服务器的特点,加载的模块⽂件⼀般都存在在本地硬盘,所以加载起来⽐较快,不⽤考虑异步的⽅式。
CommonJS 模块化的饿规范中,每个⽂件都是⼀个模块,拥有独⽴的作⽤域、变量、以及⽅法等,对其他模块不可见。 CommonJS 规范规定,每个模块内部, module 变量表⽰当前模块,它是⼀个对象,它的 exports 属性是对外的接⼝,加载某个模块,其实是加载该模块的ports 属性,require ⽅法⽤于加载模块。
// Person.js
function  Person () {
this.eat = function () {
console.log('eat something')
}
this.sleep = function () {
console.log('sleep')
}
}
var  person = new  Person();
exports.person = person;
exports.name = name;
// index.js
let  person = require('./Person').person;
person.eat()
CommonJS 与 ES6 模块的差异
1. CommonJS 模块输出的是值的拷贝,ES6 模块输出的是值的引⽤
2. CommonJS 模块是运⾏时加载,ES6 模块是编译时输出接⼝
CommonJS 模块导出的是⼀个对象(ports 属性),该对象只在脚本运⾏完才会⽣成。
ES6 的模块机制是 JS 引擎对脚本进⾏静态分析的时候,遇到模块加载命令 import,就会⽣成⼀个只读引⽤,等到脚本真正执⾏时,再根据这个只读引⽤到被加载的模块中取值,
AMD 规范
AMD(Asynchronous Module Definition)是在 Require.JS 推⼴的过程中对模块定义的规范化产出。AMD 推崇依赖前置。它是CommonJS 模块化规范的超集,作⽤在浏览器上。它的特点是异步,利⽤了浏览器的并发能⼒,让模块的依赖阻塞变少。
AMD 的 API
define(id?, dependencyies?, factory);
id 是模块的名字,是可选参数。 dependencies 指定了该模块所依赖的模块列表,是⼀个数组,也是可选参数。每个依赖的模块的输出都将作为参数依次传⼊ factory 中。
require([module], callback)
AMD 规范允许输出模块兼容 CommonJS 规范,这时 define ⽅法如下
define(['module1', 'module2'], function(module1, module2) {
function  foo () {
// ...
}
return { foo:  foo };
});
define(function(require, exports, module) {
var  requestedModule1 = require('./module1')
var  requestedModule2 = require('./module2')
function  foo () {
// ...
}
return { foo:  foo };
});
优点:适合在浏览器环境中加载模块,可以实现并⾏加载多个模块
缺点:提⾼了开发成本,并不能按需加载,⽽是提前加载所有的依赖
CMD 规范
CMD 是 Sea.JS 推⼴的过程中对模块定义的规范化产出。CMD 推崇依赖就近。
CMD 规范尽量保持简单,并与 CommonJS 规范中的 Module 保持兼容,通过 CMD 规范编写的模块,可以在 NodeJS 中运⾏。
CMD 模块定义规范
CMD 中 require 依赖的描述⽤数组,则是异步加载,如果是单个依赖使⽤字符串,则是同步加载。
AMD 是 RequireJS 在推⼴过程中对模块定义的规范化产出,CMD是SeaJS 在推⼴过程中被⼴泛认知。SeaJS 出⾃国内蚂蚁⾦服⽟伯。⼆者的区别,⽟伯在12年如是说:
RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:
1. 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于
Web 浏览器端,同时通过 Node 扩展的⽅式可以很⽅便跑在 Node 服务器端
2. 两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通⽤模块定义)规范。规范的不
同,导致了两者API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
3. 两者社区理念有差异。RequireJS 在尝试让第三⽅类库修改⾃⾝来⽀持 RequireJS,⽬前只有少数社区采纳。SeaJS 不强推,⽽采⽤
⾃主封装的⽅式来“海纳百川”,⽬前已有较成熟的封装策略。
4. 两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。
5. 两者对调试等的⽀持有差异。SeaJS 通过插件,可以实现 Fiddler 中⾃动映射的功能,还可以实现⾃动 combo 等功能,⾮常⽅便便
捷。RequireJS⽆这⽅⾯的⽀持。
6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接⼝的形式,源码中留有为插件⽽写的代码。SeaJS 采取的插件机制则与
Node 的⽅式⼀致开放⾃⾝,让插件开发者可直接访问或修改,从⽽⾮常灵活,可以实现各种类型的插件。
UMD 规范
UMD(Universal Module Definition)是随着⼤前端的趋势产⽣,希望提供⼀个前后端跨平台的解决⽅案(⽀持 AMD、CMD、CommonJS 模块⽅式)。
实现原理:
1. 先判断是否⽀持 Node.js 模块格式(exports 是否存在),存在则使⽤ Node.js 模块格式
2. 再判断是否⽀持 AMD 模块格式(define 是否存在),存在则使⽤ AMD 模块格式
3. 前2个都不存在则将模块公开到全局(window 或 global)
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (typeof  define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else  if (typeof  exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that ports,
// like Node.
} else {
// Browser globals (root is window)
}
}(this, function () {
// Just return a value to define the module export.
/
/ This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
可能有些⼈就要问了,为什么在上⾯的判断中写了 AMD,怎么没有 CMD?因为前端构建⼯具 webpack 不可识别 CMD 规范,使⽤ CMD 就需要引⽤⼯具,⽐如 Sea.JS
讲道理,如果想判断 CMD,那 UMD 代码如何写?
(function(root, factory) {
if (typeof  define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else  if (typeof  define === 'function' && d) {
// CMD
define(function(require, exports, module) {
})
} else  if (typeof  exports === 'object') {
// Node. Does not work with strict CommonJS, but

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。