【读书笔记】-- JavaScript模块
在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);
现代的模块机制
AMD和CMD都是将模块的定义封装进了一个友好的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
文件,它输出两个方法area
和circumference
。
- // 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模块的更多相关文章
- JavaScript语言精粹读书笔记 - JavaScript函数
JavaScript是披着C族语言外衣的LISP,除了词法上与C族语言相似以外,其他几乎没有相似之处. JavaScript 函数: 函数包含一组语句,他们是JavaScript的基础模块单元,用于代 ...
- [读书笔记]javascript语言精粹'
人比较笨,以前只做项目,案例,然而一些javascript的很多理论不知道该怎么描述,所以最近开启一波读书之旅: 标识符 1.定义 标识符以字母开头,可能后面跟上一个或多个字母.数字或者下划线. 2. ...
- [读书笔记]JavaScript 闭包(Closures)
1. 什么是闭包? 参考MDN. 2. 闭包的使用示例 2.1 示例1 <div>1</div> <div>2</div> <div>3&l ...
- 读书笔记-----javascript基本数据类型
由于js基础差, 记性也不好,准备一边读书一边做记录,希望这样能加深一下记忆 /* 第一天 */ javascript 基本数据类型 js一共只有五种数据类型 Undefined, Nu ...
- 读书笔记-JavaScript面向对象编程(一)
PDF下载链接: http://pan.baidu.com/s/1eSDSTVW 密码: 75jr 第1章 引言 1.1 回顾历史 1.2 变革之风 1.3 分析现状 1.4 展望未来 1.5 面向对 ...
- 读书笔记-JavaScript中的全局对象
对于任何JavaScript程序,当程序开始运行时,JavaScript解释器都会初始化一个全局对象以供程序使用.这个JavaScript自身提供的全局对象的功能包括: 1.全局对象拥有一些常用的属性 ...
- 读书笔记-JavaScript面向对象编程(三)
第7章 浏览器环境 7.1 在HTML页面中引入JavaScript代码 7.2概述BOM与DOM(页面以外事物对象和当前页面对象) 7.3 BOM 7.3.1 window对象再探(所以JavaSc ...
- 《JavaScript权威指南》读书笔记——JavaScript核心
前言 这本由David Flanagan著作,并由淘宝前端团队译的<JavaScript权威指南>,也就是我们俗称的“犀牛书”,算是JS界公认的“圣经”了.本书较厚(有1004页),读起来 ...
- 读书笔记-JavaScript高级程序设计(1)
1.组合继承 (JavaScript 中最常用的继承模式 ) (position: page168) (书中定义了两个变量名 SuperType SubType 乍一看 感觉不太能区分,我将改为 ...
- JavaScript语言精粹读书笔记- JavaScript对象
JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collecti ...
随机推荐
- 重启机器解决SSL都要输入密码问题
在Nginx或Apache设置了SSL加密后,发现每次重启服务器后都要输入证书设置的密码,比较麻烦,不然Nginx或Apache无法使用,这时可以用私钥来做这件事.生成一个解密的key文件,替代原来k ...
- Linux SSL 双向认证 浅解
请求方的操作:此步骤是为了验证CA的发证过程. 1.生成私钥: Openssl genrsa 1024 > private.key 生成私钥并保存到private.key文件中 ...
- UVa 311 - Packets
题目大意:有1X1,2X2 ... 5X5,6X6六种类型的物品,把他们装进6X6的盒子里,求使用的最少盒子数. 贪心吧,其实一看就知道思路了,算是常识吧,装物品时通常都是先装大的,再在其余空间放小的 ...
- (转载)HTML、CSS、JavaScript、PHP、MySQL 的学习顺序是什么?
文章转载自 鸟巢 - 技术分享的社区 http://t.runoob.com/question/13 1.HTML.CSS.JavaScript 前端学习三部曲,照着这个顺序依次学习 HTML教程.C ...
- Spark中的键值对操作-scala
1.PairRDD介绍 Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为PairRDD.PairRDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口.例如,Pa ...
- Bootstrap3写的红色警告框样式组件
用的是BT3的类和fa的图标 <!DOCTYPE html><html><head lang="en"> <meta charset ...
- DELPHI加密字串(异或运算加密)
首先有两个自定的转换函数: function myStrToHex(s:string):string; //字串转16进制 var TmpStr:string; i:integer; begin Tm ...
- php5.4下配置zend guard loader
前些日子的时候,zend官网下还没有支持PHP5.4的zend guard loader,今天再上去看的时候居然发现了,看来是我好久不关注它的缘故了... zend guard loader 干什么的 ...
- origin中把多个拟合曲线放在一张图
双击其中一个.或者New一个graph.这里直接双击其中一个图. 右键,找到layer contents. 可以看到,一个scatter配一个polynomial fit line.把剩下的B,C,D ...
- js原生设计模式——9外观模式封装2(小型代码库YJ)
<script type="text/javascript"> //小型代码库YJ封装 var YJ = { //根据id获取元素 ...