一、模块

function foo() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
}

正如在这段代码中所看到的,这里并没有明显的闭包,只有两个私有数据变量something和another,以及doSomething() 和doAnother() 两个内部函数,它们的词法作用域(而这就是闭包)也就是foo() 的内部作用域。

什么是闭包?当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。

接下来考虑以下代码:

function CoolModule() {
var something = "cool"; var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

这个模式在JavaScript 中被称为模块。最常见的实现模块模式的方法通常被称为模块暴露,这里展示的是其变体。我们仔细研究一下这些代码。

首先,CoolModule() 只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行外部函数,内部作用域和闭包都无法被创建。
其次,CoolModule() 返回一个用对象字面量语法{ key: value, ... } 来表示的对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。我们保持内部数据变量是隐藏且私有的状态。可以将这个对象类型的返回值看作本质上是模块的公共API。这个对象类型的返回值最终被赋值给外部的变量foo,然后就可以通过它来访问API 中的属性方法,比如foo.doSomething()。从模块中返回一个实际的对象并不是必须的,也可以直接返回一个内部函数。jQuery 就是一个很好的例子。jQuery 和$ 标识符就是jQuery 模块的公共API,但它们本身都是函数(由于函数也是对象,它们本身也可以拥有属性)。doSomething() 和doAnother() 函数具有涵盖模块实例内部作用域的闭包( 通过调用CoolModule() 实现)。当通过返回一个含有属性引用的对象的方式来将函数传递到词法作用域外部时,我们已经创造了可以观察和实践闭包的条件。如果要更简单的描述,模块模式需要具备两个必要条件。

1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

一个具有函数属性的对象本身并不是真正的模块。从方便观察的角度看,一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块。上一个示例代码中有一个叫作CoolModule() 的独立的模块创建器,可以被调用任意多次,每次调用都会创建一个新的模块实例。当只需要一个实例时,可以对这个模式进行简单的改进来实现单例模式:

var foo = (function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

我们将模块函数转换成了IIFE(立即执行函数),立即调用这个函数并将返回值直接赋值给单例的模块实例标识符foo。
模块也是普通的函数,因此可以接受参数:

function CoolModule(id) {
function identify() {
console.log( id );
}
return {
identify: identify
};
}
var foo1 = CoolModule( "foo 1" );
var foo2 = CoolModule( "foo 2" ); foo1.identify(); // "foo 1"
foo2.identify(); // "foo 2"

模块模式另一个简单但强大的变化用法是,命名将要作为公共API 返回的对象:

var foo = (function CoolModule(id) {
function change() {
// 修改公共API
publicAPI.identify = identify2;
}
function identify1() {
console.log( id );
}
function identify2() {
console.log( id.toUpperCase() );
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})( "foo module" );
foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

通过在模块实例的内部保留对公共API 对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改它们的值。

二、现代的模块机制
大多数模块依赖加载器/ 管理器本质上都是将这种模块定义封装进一个友好的API。这里并不会研究某个具体的库,为了宏观了解我会简单地介绍一些核心概念:

var MyModules = (function Manager() {
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
};
})();

这段代码的核心是modules[name] = impl.apply(impl, deps)。为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,也就是模块的API,储存在一个根据名字来管理的模块列表中。
下面展示了如何使用它来定义模块:

MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO

"foo" 和"bar" 模块都是通过一个返回公共API 的函数来定义的。"foo" 甚至接受"bar" 的示例作为依赖参数,并能相应地使用它。为我们自己着想,应该多花一点时间来研究这些示例代码并完全理解闭包的作用吧。最重要的是要理解模块管理器没有任何特殊的“魔力”。它们符合前面列出的模块模式的两个特点:为函数定义引入包装函数,并保证它的返回值和模块的API 保持一致。换句话说,模块就是模块,即使在它们外层加上一个友好的包装工具也不会发生任何变化

三、未来的模块机制

ES6 中为模块增加了一级语法支持。但通过模块系统进行加载时,ES6 会将文件当作独立的模块来处理。每个模块都可以导入其他模块或特定的API 成员,同样也可以导出自己的API 成员。基于函数的模块并不是一个能被稳定识别的模式(编译器无法识别),它们
的API 语义只有在运行时才会被考虑进来。因此可以在运行时修改一个模块的API。相比之下,ES6 模块API 更加稳定(API 不会在运行时改变)。由于编辑器知道这一点,因此可以在(的确也这样做了)编译期检查对导入模块的API 成员的引用是否真实存在。如果API 引用并不存在,编译器会在运行时抛出一个或多个“早期”错误,而不会像往常一样在运行期采用动态的解决方案。ES6 的模块没有“行内”格式,必须被定义在独立的文件中(一个文件一个模块)。浏览器或引擎有一个默认的“模块加载器”(可以被重载,但这远超出了我们的讨论范围)可以在导入模块时异步地加载模块文件。
考虑以下代码:

bar.js
function hello(who) {
return "Let me introduce: " + who;
}
export hello;
foo.js
// 仅从"bar" 模块导入hello()
import hello from "bar";
var hungry = "hippo";
function awesome() {
console.log(
hello( hungry ).toUpperCase()
);
}
export awesome;
baz.js
// 导入完整的"foo" 和"bar" 模块 module foo from "foo";
module bar from "bar";
console.log(
bar.hello( "rhino" )
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO

需要用前面两个代码片段中的内容分别创建文件foo.js 和bar.js。然后如第三个代码片段中展示的那样,bar.js 中的程序会加载或导入这两个模块并使用它们。
import 可以将一个模块中的一个或多个API 导入到当前作用域中,并分别绑定在一个变量上(在我们的例子里是hello)。module 会将整个模块的API 导入并绑定到一个变量上(在我们的例子里是foo 和bar)。export 会将当前模块的一个标识符(变量、函数)导出为公共API。这些操作可以在模块定义中根据需要使用任意多次。模块文件中的内容会被当作好像包含在作用域闭包中一样来处理,就和前面介绍的函数闭包模块一样。

javascript 模块的更多相关文章

  1. 关于javascript模块加载技术的一些思考

    前不久有个网友问我在前端使用requireJs和seajs的问题,我当时问他你们公司以前有没有自己编写的javascript库,或者javascript框架,他的回答是什么都没有,他只是听说像requ ...

  2. (转)深入理解JavaScript 模块模式

    深入理解JavaScript 模块模式 (原文)http://www.cnblogs.com/starweb/archive/2013/02/17/2914023.html 英文:http://www ...

  3. Javascript模块规范(CommonJS规范&&AMD规范)

    Javascript模块化编程(AMD&CommonJS) 前端模块化开发的价值:https://github.com/seajs/seajs/issues/547 模块的写法 查看 AMD规 ...

  4. 深入理解JavaScript 模块模式

    http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html 模块模式是JavaScript一种常用的编码模式.这是一般的 ...

  5. 转: javascript模块加载框架seajs详解

    javascript模块加载框架seajs详解 SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加 ...

  6. javascript模块加载框架seajs详解

    SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加载).SeaJS可以和jQuery完美集成,使用 ...

  7. 推荐好用的JavaScript模块

    译者按: 作者将自己常用的JavaScript模块分享给大家. 原文:

  8. 小矮人Javascript模块加载器

    https://github.com/miniflycn/webkit-dwarf 短小精悍的webkit浏览器Javascript模块加载器 Why 我们有许多仅基于webkit浏览器开发的应用 无 ...

  9. 使用Babel和ES7创建JavaScript模块

    [编者按]本文主要介绍通过 ES7 与 Babel 建立 JavaScript 模块.文章系国内 ITOM 管理平台 OneAPM 工程师编译呈现,以下为正文. 去年,新版的JavaScript发布了 ...

  10. RequireJS 是一个JavaScript模块加载器

    RequireJS 是一个JavaScript模块加载器.它非常适合在浏览器中使用, 它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino and Node. 使用RequireJ ...

随机推荐

  1. phpmyadmin设置密码,不用登录直接进入

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1.config.sample.inc.PHP改为config.inc.php 2.加入或更改代码: [php] view plain copy ...

  2. zabbix入门知识

    zabbix入门知识 zabbix中文手册 https://www.zabbix.com/documentation/3.4/manual/ 1.zabbix介绍 Zabbix 是一个企业级的分布式开 ...

  3. python服务端多进程压测工具

    本文描述一个python实现的多进程压测工具,这个压测工具的特点如下: 多进程 在大多数情况下,压测一般适用于IO密集型场景(如访问接口并等待返回),在这种场景下多线程多进程的区分并不明显(详情请参见 ...

  4. CCF系列之相反数(201403-1)

    试题名称: 相反数 试题编号: 201403-1时间限制: 1.0s 内存限制: 256.0MB 问题描述 有 N 个非零且各不相同的整数.请你编一个程序求出它们中有多少对相反数(a 和 -a 为一对 ...

  5. 分布式CAP原理

    根据维基百科定义[CAP] 根据定理,一个分布式系统最多只能满足其中两项, 不可能同时满则C-A-P三项 首先说一下对各项原则的理解 (1)一致性C: 单机环境下, 数据只有一份,所有的客户端访问的是 ...

  6. mybatis-自定义缓存-redis二级缓存

    在mybatis一级缓存二级缓存中已经介绍过了二级缓存的大致原理.下面我们用redis来实现一下二级缓存.环境是springmvc+mybatis+redis 步骤一.引入redis相关的maven依 ...

  7. java —— 内部类

    _ 普通内部类 静态内部类 局部内部类 匿名内部类 内部类 内部类是定义在另一个类中的类,定义内部类会起到的作用有以下三点: 1.内部类方法访问该类定义所在的作用域中的数据,包括私有的数据. 2.内部 ...

  8. 关键字final整理

    关键字final整理 由于语境(应用环境)不同,final 关键字的含义可能会稍微产生一些差异.但它最一般的意思就是声明"这个东西不能改变".之所以要禁止改变,可能是考虑到两方面的 ...

  9. js中的监听事件总结

    javascript事件与功能说明大全:http://tools.jb51.net/table/javascript_event 1.滚动条监听事件 例1:监听滚动条距离页面顶端距离 <scri ...

  10. TCP全连接队列和半连接队列已满之后的连接建立过程抓包分析[转]

    最近项目需要做单机100万长连接与高并发的服务器,我们开发完服务器以后,通过自己搭的高速压测框架压测服务端的时候,发生了奇怪的现象,就是服务端莫名其妙的少接收了连接,造成了数据包的丢失,通过网上查资料 ...