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

简单模块

       var foo = (function() {
var name = "foo";
function hello() {
console.log("hello "+name);
}
function doWork() {
console.log("do work");
}
return {
hello: hello,
doWork: doWork
};
})();
foo.hello(); // hello foo
foo.doWork(); // do work

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

(function () {
angular
.module('readApp')
.service('authentication', authentication); authentication.$inject = ['$window','$http'];
function authentication($window, $http) {
var saveToken = function (token) {
$window.localStorage['read-token'] = token;
};
var getToken = function () {
return $window.localStorage['read-token'];
};
var register = function(user) {
return $http.post('/api/register', user).success(function(data) {
saveToken(data.token);
});
};
var login = function(user) {
return $http.post('/api/login', user).success(function(data) {
saveToken(data.token);
});
};
var logout = function() {
$window.localStorage.removeItem('read-token');
}; var isLoggedIn = function() {
var token = getToken();
if (token) {
var payload = JSON.parse($window.atob(token.split('.')[1]));
return payload.exp > Date.now() / 1000;
} else {
return false;
}
};
var currentUser = function() {
if (isLoggedIn()) {
var token = getToken();
var payload = JSON.parse($window.atob(token.split('.')[1]));
return {
email: payload.email,
name: payload.name,
};
}
}; return {
saveToken: saveToken,
getToken: getToken,
register: register,
login: login,
logout: logout,
isLoggedIn: isLoggedIn,
currentUser: currentUser,
};
}
})();

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

   var foo = {
other: function () {
console.log("do other");
},
hello:function() {
console.log("working");
},
doWork:function() {
console.log("working");
foo.other();
}
}

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

扩展模块

  var foo = (function () {
var self = {},name = "foo";
function _wroking() {
console.log("working");
}
self.hello = function () {
console.log("hello " + name);
}
self.doWork = function () {
_wroking();
}
return self;
})();

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

var Zepto = (function() {
var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
zepto = {}, camelize, uniq;
//...........
function isObject(obj) { return type(obj) == "object" }
//..........
zepto.matches = function (element, selector) {
//...
}
zepto.init = function(selector, context) {
//......
}
$ = function (selector, context) {
return zepto.init(selector, context)
}
function extend(target, source, deep) {
//...
}
$.extend = function (target) {
}
})() window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

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

  function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
extend(target[key], source[key], deep)
}
else if (source[key] !== undefined) target[key] = source[key]
} // Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}

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

            ;(function ($) {
//...
$.event = { add: add, remove: remove }
$.proxy = function(fn, context) {
//...
}
})(Zepto);

现代的模块机制

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

var MyModules = (function () {
var modules = {}; function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
} function get(name) {
return modules[name];
} return {
define: define,
get: get
}
})();
MyModules.define("bar", [], function () {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
});
MyModules.define("foo", ["bar"], function (bar) {
var hungry = "jazz"; function awsome() {
console.log(bar.hello(hungry));
} return {
awsome: awsome
}
});
var bar = MyModules.get("bar");
var foo = MyModules.get("foo"); console.log(bar.hello("hippo"));//Let me introduce: hippo
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中,模块的状态做了区分:

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

看下define方法:

Module.define = function (id, deps, factory) {
var argsLen = arguments.length // define(factory)
if (argsLen === 1) {
factory = id
id = undefined
}
else if (argsLen === 2) {
factory = deps // define(deps, factory)
if (isArray(id)) {
deps = id
id = undefined
}
// define(id, factory)
else {
deps = undefined
}
} // Parse dependencies according to the module factory code
if (!isArray(deps) && isFunction(factory)) {
deps = parseDependencies(factory.toString())
} var meta = {
id: id,
uri: resolve(id),
deps: deps,
factory: factory
} // Try to derive uri in IE6-9 for anonymous modules
if (!meta.uri && doc.attachEvent) {
var script = getCurrentScript() if (script) {
meta.uri = script.src
} // NOTE: If the id-deriving methods above is failed, then falls back
// to use onload event to get the uri
} // Emit `define` event, used in nocache plugin, seajs node version etc
emit("define", meta) meta.uri ? save(meta.uri, meta) :
// Save information for "saving" work in the script onload event
anonymousMeta = meta
}

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

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

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

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

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

(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.xxx= factory();
}
})(this, function () {
//。。。
}

支持amd规范和node模块。

ES6中的模块

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

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

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

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

// circle.js

export function area(radius) {
return Math.PI * radius * radius;
} export function circumference(radius) {
return 2 * Math.PI * radius;
}

现在,加载这个模块。

// main.js

import { area, circumference } from './circle';

console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));

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

import * as circle from './circle';

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

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
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. Scala中function的理解

    在函数式语言中,函数是和value一样地位的一等公民,他可以作为变量,或者作为参数传递给另一个函数 ##作为变量 val f=(x:Int)=>x+1 为啥可以这样写? 由于scala是OOP, ...

  2. Spring自学教程-IOC、DI、AOP(二)

    一.spring的IOC-就是怎样使用spring来创建对象 二.springDI(依赖注入)-就是怎样给属性赋值 通过set方式赋值 以下我们只需要记住两点的赋值,基本类型和引用类型的赋值 基本类型 ...

  3. 51nod贪心算法教程

    51nod确实是一个好oj,题目质量不错,wa了还放数据,学习算法来说挺好的,这次我做了几个水的贪心,虽然水,但是确实都很典型. 教程链接:http://www.51nod.com/tutorial/ ...

  4. AFNetWorking发送post请求,Code=-1016错误

    使用AFNetWorking发送post请求时,可能会出现下面Code=-1016问题.打印的error如下: Error:Error Domain=com.alamofire.error.seria ...

  5. STM32-USB那点事

    STM32 USB那点事1 USB那点事2 - Custom HID例子程序解疑 USB那点事3 -使用端口2作为custom HID的传输 USB那点事5之USB通信出错 USB那点事6传输要素 S ...

  6. UVa 299 - Train Swapping

    题目大意:给n个数的一个序列,通过交换相邻的两个数使得这n个数按照从小到大的顺序排列. Inversion index problem: count how many swaps are needed ...

  7. Prism之使用EventAggregation进行模块间通信

    在开发Silverlight程序的时候,经常需要在不同的组件间进行通信.比如点击一个button,可能就需要改变另一个控件的内容.比较直接的办法是使用事件,当然使用MVVM的时候也可以使用comman ...

  8. Leetcode 181. Employees Earning More Than Their Managers

    The Employee table holds all employees including their managers. Every employee has an Id, and there ...

  9. Python使用Selenium/PhantomJS

    安装selenium: 1 pip install selenium 安装PhantomJS: 1 2 3 4 https://bitbucket.org/ariya/phantomjs/downlo ...

  10. windows 7 忘記密碼,用“带命令行的安全模式”

    net user administrator /active:yes net user tester /add net localgroup administrators tester /add