在JavaScript编程中我们用的很多的一个场景就是写模块。可以看成一个简单的封装或者是一个类库的开始,有哪些形式呢,先来一个简单的模块。

简单模块

  1.        var foo = (function() {
  2. var name = "foo";
  3. function hello() {
  4. console.log("hello "+name);
  5. }
  6. function doWork() {
  7. console.log("do work");
  8. }
  9. return {
  10. hello: hello,
  11. doWork: doWork
  12. };
  13. })();
  14. foo.hello(); // hello foo
  15. foo.doWork(); // do work

用IIFE创建一个闭包,隔离作用域,避免变量相互干扰。得到foo对象可以直接用了。这种适合小的模块,比如在ag中的写Service。

  1. (function () {
  2. angular
  3. .module('readApp')
  4. .service('authentication', authentication);
  5.  
  6. authentication.$inject = ['$window','$http'];
  7. function authentication($window, $http) {
  8. var saveToken = function (token) {
  9. $window.localStorage['read-token'] = token;
  10. };
  11. var getToken = function () {
  12. return $window.localStorage['read-token'];
  13. };
  14. var register = function(user) {
  15. return $http.post('/api/register', user).success(function(data) {
  16. saveToken(data.token);
  17. });
  18. };
  19. var login = function(user) {
  20. return $http.post('/api/login', user).success(function(data) {
  21. saveToken(data.token);
  22. });
  23. };
  24. var logout = function() {
  25. $window.localStorage.removeItem('read-token');
  26. };
  27.  
  28. var isLoggedIn = function() {
  29. var token = getToken();
  30. if (token) {
  31. var payload = JSON.parse($window.atob(token.split('.')[1]));
  32. return payload.exp > Date.now() / 1000;
  33. } else {
  34. return false;
  35. }
  36. };
  37. var currentUser = function() {
  38. if (isLoggedIn()) {
  39. var token = getToken();
  40. var payload = JSON.parse($window.atob(token.split('.')[1]));
  41. return {
  42. email: payload.email,
  43. name: payload.name,
  44. };
  45. }
  46. };
  47.  
  48. return {
  49. saveToken: saveToken,
  50. getToken: getToken,
  51. register: register,
  52. login: login,
  53. logout: logout,
  54. isLoggedIn: isLoggedIn,
  55. currentUser: currentUser,
  56. };
  57. }
  58. })();

也可以直接一个大括号的:

  1. var foo = {
  2. other: function () {
  3. console.log("do other");
  4. },
  5. hello:function() {
  6. console.log("working");
  7. },
  8. doWork:function() {
  9. console.log("working");
  10. foo.other();
  11. }
  12. }

这个形式我们在jquery内部或者一些工具类js中见过。简单直接。如果有内部相互调用,建议直接用对象名。这样不必在每一个方法里面写一个 var self=this 。缺点就是太长了不好维护,多增加一个变量都要加个key:value的形式。只适合简单场景。但如果模块内部方法比较多,还是建议在内部创建对象。

扩展模块

  1. var foo = (function () {
  2. var self = {},name = "foo";
  3. function _wroking() {
  4. console.log("working");
  5. }
  6. self.hello = function () {
  7. console.log("hello " + name);
  8. }
  9. self.doWork = function () {
  10. _wroking();
  11. }
  12. return self;
  13. })();

这样内部方法和外部方法就有明确的区分,可以看下Zepto的大体结构:

  1. var Zepto = (function() {
  2. var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
  3. zepto = {}, camelize, uniq;
  4. //...........
  5. function isObject(obj) { return type(obj) == "object" }
  6. //..........
  7. zepto.matches = function (element, selector) {
  8. //...
  9. }
  10. zepto.init = function(selector, context) {
  11. //......
  12. }
  13. $ = function (selector, context) {
  14. return zepto.init(selector, context)
  15. }
  16. function extend(target, source, deep) {
  17. //...
  18. }
  19. $.extend = function (target) {
  20. }
  21. })()
  22.  
  23. window.Zepto = Zepto
  24. window.$ === undefined && (window.$ = Zepto)

内部定义了zepot对象,并注意到有一个extend方法,便于后面扩展zepot的这个模块,当然也可以扩展其他的模块。target是指定的。

  1. function extend(target, source, deep) {
  2. for (key in source)
  3. if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
  4. if (isPlainObject(source[key]) && !isPlainObject(target[key]))
  5. target[key] = {}
  6. if (isArray(source[key]) && !isArray(target[key]))
  7. target[key] = []
  8. extend(target[key], source[key], deep)
  9. }
  10. else if (source[key] !== undefined) target[key] = source[key]
  11. }
  12.  
  13. // Copy all but undefined properties from one or more
  14. // objects to the `target` object.
  15. $.extend = function(target){
  16. var deep, args = slice.call(arguments, 1)
  17. if (typeof target == 'boolean') {
  18. deep = target
  19. target = args.shift()
  20. }
  21. args.forEach(function(arg){ extend(target, arg, deep) })
  22. return target
  23. }

再扩展子模块的时候就方便了:

  1. ;(function ($) {
  2. //...
  3. $.event = { add: add, remove: remove }
  4. $.proxy = function(fn, context) {
  5. //...
  6. }
  7. })(Zepto);

现代的模块机制

AMDCMD都是将模块的定义封装进了一个友好的API。就是require.js和sea.js中的define方法。先看一个简单的现代模块实现:

  1. var MyModules = (function () {
  2. var modules = {};
  3.  
  4. function define(name, deps, impl) {
  5. for (var i = 0; i < deps.length; i++) {
  6. deps[i] = modules[deps[i]];
  7. }
  8. modules[name] = impl.apply(impl, deps);
  9. }
  10.  
  11. function get(name) {
  12. return modules[name];
  13. }
  14.  
  15. return {
  16. define: define,
  17. get: get
  18. }
  19. })();
  20. MyModules.define("bar", [], function () {
  21. function hello(who) {
  22. return "Let me introduce: " + who;
  23. }
  24. return {
  25. hello: hello
  26. };
  27. });
  28. MyModules.define("foo", ["bar"], function (bar) {
  29. var hungry = "jazz";
  30.  
  31. function awsome() {
  32. console.log(bar.hello(hungry));
  33. }
  34.  
  35. return {
  36. awsome: awsome
  37. }
  38. });
  39. var bar = MyModules.get("bar");
  40. var foo = MyModules.get("foo");
  41.  
  42. console.log(bar.hello("hippo"));//Let me introduce: hippo
  43. foo.awsome();//Let me introduce: jazz

MyModules的define方法包含name,deps,impl三个参数,name表示是模块名称,deps表示是依赖项,impl表示实现。关键是modules[name] = impl.apply( impl, deps );这一句,上面的for循环将一个模块名称数组先转成一个包含具体模块的数组,然后apply给具体的实现方法。相当于是注入了依赖项。注意到定义foo模块的时候,依赖了bar模块,只需要在deps这个参数加入[“bar”]即可。

在sea.js中,模块的状态做了区分:

  1. var STATUS = Module.STATUS = {
  2. // 1 - The `module.uri` is being fetched 相当于初始化
  3. FETCHING: 1,
  4. // 2 - The meta data has been saved to cachedMods 缓存在cacheMods
  5. SAVED: 2,
  6. // 3 - The `module.dependencies` are being loaded
  7. LOADING: 3,
  8. // 4 - The module are ready to execute
  9. LOADED: 4,
  10. // 5 - The module is being executed
  11. EXECUTING: 5,
  12. // 6 - The `module.exports` is available
  13. EXECUTED: 6
  14. }

看下define方法:

  1. Module.define = function (id, deps, factory) {
  2. var argsLen = arguments.length
  3.  
  4. // define(factory)
  5. if (argsLen === 1) {
  6. factory = id
  7. id = undefined
  8. }
  9. else if (argsLen === 2) {
  10. factory = deps
  11.  
  12. // define(deps, factory)
  13. if (isArray(id)) {
  14. deps = id
  15. id = undefined
  16. }
  17. // define(id, factory)
  18. else {
  19. deps = undefined
  20. }
  21. }
  22.  
  23. // Parse dependencies according to the module factory code
  24. if (!isArray(deps) && isFunction(factory)) {
  25. deps = parseDependencies(factory.toString())
  26. }
  27.  
  28. var meta = {
  29. id: id,
  30. uri: resolve(id),
  31. deps: deps,
  32. factory: factory
  33. }
  34.  
  35. // Try to derive uri in IE6-9 for anonymous modules
  36. if (!meta.uri && doc.attachEvent) {
  37. var script = getCurrentScript()
  38.  
  39. if (script) {
  40. meta.uri = script.src
  41. }
  42.  
  43. // NOTE: If the id-deriving methods above is failed, then falls back
  44. // to use onload event to get the uri
  45. }
  46.  
  47. // Emit `define` event, used in nocache plugin, seajs node version etc
  48. emit("define", meta)
  49.  
  50. meta.uri ? save(meta.uri, meta) :
  51. // Save information for "saving" work in the script onload event
  52. anonymousMeta = meta
  53. }

save的内部是通过Module.get 缓存起uri和deps(依赖项)构建的Module对象。

  1. Module.get = function(uri, deps) {
  2. return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
  3. }

require也是通Module.get来获取模块:

  1. seajs.require = function(id) {
  2. return (cachedMods[resolve(id)] || {}).exports
  3. }

不像例子中是一次性加载模块,sea.js可以在需要的地方再加载对应的模块。因为我们在很多js框架中看到下面这一块:

  1. (function (root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. define(factory);
  4. } else if (typeof exports === 'object') {
  5. module.exports = factory();
  6. } else {
  7. root.xxx= factory();
  8. }
  9. })(this, function () {
  10. //。。。
  11. }

支持amd规范和node模块。

ES6中的模块

以上的模块都是基于函数的,API在运行时都可以被修改。ES6中为模块增加了一级语法支持,通过模块系统进行加载时,ES6会将文件独立的模块来处理。使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

  1. // ES6模块
  2. import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

下面是一个circle.js文件,它输出两个方法areacircumference

  1. // circle.js
  2.  
  3. export function area(radius) {
  4. return Math.PI * radius * radius;
  5. }
  6.  
  7. export function circumference(radius) {
  8. return 2 * Math.PI * radius;
  9. }

现在,加载这个模块。

  1. // main.js
  2.  
  3. import { area, circumference } from './circle';
  4.  
  5. console.log('圆面积:' + area(4));
  6. console.log('圆周长:' + circumference(14));

上面写法是逐一指定要加载的方法,整体加载的写法如下。

  1. import * as circle from './circle';

这语法让人有点激动。而且是不允许修改的,这样就很大的确保了接口的稳定性。

  1. import * as circle from './circle';
  2.  
  3. // 下面两行都是不允许的
  4. circle.foo = 'hello';
  5. circle.area = function () {};

小结:模块在JavaScript运用很广泛,好的模块构建能确定一个清晰的结构,有助于分工和维护。

参考文档:

ES6 Module: http://es6.ruanyifeng.com/#docs/module

CMD规范:https://github.com/seajs/seajs/issues/242

阅读书籍:

资源在读书群中。

【读书笔记】-- JavaScript模块的更多相关文章

  1. JavaScript语言精粹读书笔记 - JavaScript函数

    JavaScript是披着C族语言外衣的LISP,除了词法上与C族语言相似以外,其他几乎没有相似之处. JavaScript 函数: 函数包含一组语句,他们是JavaScript的基础模块单元,用于代 ...

  2. [读书笔记]javascript语言精粹'

    人比较笨,以前只做项目,案例,然而一些javascript的很多理论不知道该怎么描述,所以最近开启一波读书之旅: 标识符 1.定义 标识符以字母开头,可能后面跟上一个或多个字母.数字或者下划线. 2. ...

  3. [读书笔记]JavaScript 闭包(Closures)

    1. 什么是闭包? 参考MDN. 2. 闭包的使用示例 2.1 示例1 <div>1</div> <div>2</div> <div>3&l ...

  4. 读书笔记-----javascript基本数据类型

    由于js基础差, 记性也不好,准备一边读书一边做记录,希望这样能加深一下记忆 /*   第一天     */ javascript 基本数据类型 js一共只有五种数据类型 Undefined,  Nu ...

  5. 读书笔记-JavaScript面向对象编程(一)

    PDF下载链接: http://pan.baidu.com/s/1eSDSTVW 密码: 75jr 第1章 引言 1.1 回顾历史 1.2 变革之风 1.3 分析现状 1.4 展望未来 1.5 面向对 ...

  6. 读书笔记-JavaScript中的全局对象

    对于任何JavaScript程序,当程序开始运行时,JavaScript解释器都会初始化一个全局对象以供程序使用.这个JavaScript自身提供的全局对象的功能包括: 1.全局对象拥有一些常用的属性 ...

  7. 读书笔记-JavaScript面向对象编程(三)

    第7章 浏览器环境 7.1 在HTML页面中引入JavaScript代码 7.2概述BOM与DOM(页面以外事物对象和当前页面对象) 7.3 BOM 7.3.1 window对象再探(所以JavaSc ...

  8. 《JavaScript权威指南》读书笔记——JavaScript核心

    前言 这本由David Flanagan著作,并由淘宝前端团队译的<JavaScript权威指南>,也就是我们俗称的“犀牛书”,算是JS界公认的“圣经”了.本书较厚(有1004页),读起来 ...

  9. 读书笔记-JavaScript高级程序设计(1)

    1.组合继承 (JavaScript 中最常用的继承模式 ) (position: page168) (书中定义了两个变量名 SuperType   SubType  乍一看 感觉不太能区分,我将改为 ...

  10. JavaScript语言精粹读书笔记- JavaScript对象

    JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collecti ...

随机推荐

  1. 重启机器解决SSL都要输入密码问题

    在Nginx或Apache设置了SSL加密后,发现每次重启服务器后都要输入证书设置的密码,比较麻烦,不然Nginx或Apache无法使用,这时可以用私钥来做这件事.生成一个解密的key文件,替代原来k ...

  2. Linux SSL 双向认证 浅解

    请求方的操作:此步骤是为了验证CA的发证过程. 1.生成私钥:     Openssl genrsa 1024 > private.key  生成私钥并保存到private.key文件中    ...

  3. UVa 311 - Packets

    题目大意:有1X1,2X2 ... 5X5,6X6六种类型的物品,把他们装进6X6的盒子里,求使用的最少盒子数. 贪心吧,其实一看就知道思路了,算是常识吧,装物品时通常都是先装大的,再在其余空间放小的 ...

  4. (转载)HTML、CSS、JavaScript、PHP、MySQL 的学习顺序是什么?

    文章转载自 鸟巢 - 技术分享的社区 http://t.runoob.com/question/13 1.HTML.CSS.JavaScript 前端学习三部曲,照着这个顺序依次学习 HTML教程.C ...

  5. Spark中的键值对操作-scala

    1.PairRDD介绍     Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为PairRDD.PairRDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口.例如,Pa ...

  6. Bootstrap3写的红色警告框样式组件

    用的是BT3的类和fa的图标 <!DOCTYPE html><html><head lang="en">    <meta charset ...

  7. DELPHI加密字串(异或运算加密)

    首先有两个自定的转换函数: function myStrToHex(s:string):string; //字串转16进制 var TmpStr:string; i:integer; begin Tm ...

  8. php5.4下配置zend guard loader

    前些日子的时候,zend官网下还没有支持PHP5.4的zend guard loader,今天再上去看的时候居然发现了,看来是我好久不关注它的缘故了... zend guard loader 干什么的 ...

  9. origin中把多个拟合曲线放在一张图

    双击其中一个.或者New一个graph.这里直接双击其中一个图. 右键,找到layer contents. 可以看到,一个scatter配一个polynomial fit line.把剩下的B,C,D ...

  10. js原生设计模式——9外观模式封装2(小型代码库YJ)

    <script type="text/javascript">    //小型代码库YJ封装    var YJ = {        //根据id获取元素       ...