在复杂的逻辑下
JavaScript 需要被模块化
模块需要封装起来
只留下供外界调用的接口
闭包是 JavaScript 中实现模块封装的关键
也是很多初学者难以理解的要点
虽然 JavaScript 天生就是一副随随便便的样子但是随着浏览器能够完成的事情越来越多这门语言也也越来越经常地摆出正襟危坐的架势在复杂的逻辑下 JavaScript 需要被模块化模块需要封装起来只留下供外界调用的接口闭包是 JavaScript 中实现模块封装的关键也是很多初学者难以理解的要点最初我也陷入迷惑之中现在我自信对这个概念已经有了比较深入的理解为了便于理解文中试图 封装一个比较简单的对象
我们试图在页面上维护一个计数器对象 ticker 这个对象维护一个数值 n 随着用户的操作我们可以增加一次计数(将数值 n 加上 )但不能减少 n 或直接改变 n 而且我们需要时不时查询这个数值
门户大开的 JSON 风格模块化
一种门户大开的方式是
复制代码 代码如下:
var ticker = {
n:
tick:function(){
this
n++;
}
};
这种方式书写自然而且确实有效我们需要增加一次计数时就调用 tickertick() 方法需要查询次数时就访问 tickern 变量但是其缺点也是显而易见的模块的使用者被允许自由地改变 n 比如调用 tickern 或者 tickern= 我们并没有对 ticker 进行封装 n 和 tick() 看上去是 ticker 的“成员”但是它们的可访问性和 ticker 一样都是全局性的(如果 ticker 是全局变量的话)在封装性上这种模块化的方式比下面这种更加可笑的方式只好那么一点点(虽然对有些简单的应用来说这一点点也足够了)
复制代码 代码如下:
var ticker = {};
var tickerN =
;
var tickerTick = function(){
tickerN++;
}
tickerTick();
值得注意的是在 tick() 中我访问的是 thisn ——这并不是因为 n 是 ticker 的成员而是因为调用 tick() 的是 ticker 事实上这里写成 tickern 会更好因为如果调用 tick() 的不是 ticker 而是其他什么东西比如
复制代码 代码如下:
var func = ticker
tick;
func();
这时调用 tick() 的其实是 window 而函数执行时会试图访问 windown 而出错
事实上这种“门户大开”型的模块化方式往往用来组织 JSON 风格的数据而不是程序比如我们可以将下面这个 JSON 对象传给 ticker 的某个函数来确定 ticker 从 开始计数每次递进
复制代码 代码如下:
var config = {
nStart:
step:
}
作用域链和闭包
来看下面的代码注意我们已经实现了传入 config 对 ticker 进行自定义
复制代码 代码如下:
function ticker(config){
var n = config
nStart;
function tick(){
n += config
step;
}
}
console
log(ticker
n); //
>undefined
你也许会疑惑怎么 ticker 从对象变成了函数了?这是因为 JavaScript 中只有函数具有作用域从函数体外无法访问函数内部的变量 ticker() 外访问 tickern 获得 undefined 而 tick() 内访问 n 却没有问题从 tick() 到 ticker() 再到全局这就是 JavaScript 中的“作用域链”
可是还有问题那就是——怎么调用 tick() ? ticker() 的作用域将 tick() 也掩盖了起来解决方法有两种
•)将需要调用方法作为返回值正如我们将递增 n 的方法作为 ticker() 的返回值
•)设定外层作用域的变量正如我们在 ticker() 中设置 getN
复制代码 代码如下:
var getN;
function ticker(config){
var n = config
nStart;
getN = function(){
return n;
};
return function(){
n += config
step;
};
}
var tick = ticker({nStart:step:});
tick();
consolelog(getN()); // >
请看这时变量 n 就处在“闭包”之中在 ticker() 外部无法直接访问它但是却可以通过两个方法来观察或操纵它
在本节第一段代码中 ticker() 方法执行之后 n 和 tick() 就被销毁了直到下一次调用该函数时再创建但是在第二段代码中 ticker() 执行之后 n 不会被销毁因为 tick() 和 getN() 可能访问它或改变它浏览器会负责维持n我对“闭包”的理解就是用以保证 n 这种处在函数作用域内函数执行结束后仍需维持可能被通过其他方式访问的变量 不被销毁的机制
可是我还是觉得不大对劲?如果我需要维持两个具有相同功能的对象 ticker 和 ticker 那该怎么办? ticker() 只有一个总不能再写一遍吧?
new 运算符与构造函数
如果通过 new 运算符调用一个函数就会创建一个新的对象并使用该对象调用这个函数在我的理解中下面的代码中 t 和 t 的构造过程是一样的
复制代码 代码如下:
function myClass(){}
var t
= new myClass();
var t
= {};
t
func = myClass;
t
func();
t
func = undefined;
t 和 t 都是新构造的对象 myClass() 就是构造函数了类似的 ticker() 可以重新写成
复制代码 代码如下:
function TICKER(config){
var n = config
nStart;
this
getN = function(){
return n;
};
this
tick = function(){
n += config
step;
}
}
var ticker = new TICKER({nStart:step:});
tickertick();
consolelog(tickergetN()); // >
var ticker = new TICKER({nStart:step:});
tickertick();
tickertick();
consolelog(tickergetN()); // >
习惯上构造函数采用大写注意 TICKER() 仍然是个函数而不是个纯粹的对象(之所以说“纯粹”是因为函数实际上也是对象 TICKER() 是函数对象)闭包依旧有效我们无法访问 tickern
原型 prototype 与继承
上面这个 TICKER() 还是有缺陷那就是 tickertick() 和 tickertick() 是互相独立的!请看每使用 new 运算符调用 TICKER() 就会生成一个新的对象并生成一个新的函数绑定在这个新的对象上每构造一个新的对象浏览器就要开辟一块空间存储 tick() 本身和 tick() 中的变量这不是我们所期望的我们期望 tickertick 和 tickertick 指向同一个函数对象
这就需要引入原型
JavaScript 中除了 Object 对象其他对象都有一个 prototype 属性这个属性指向另一个对象这“另一个对象”依旧有其原型对象并形成原型链最终指向 Object 对象在某个对象上调用某方法时如果发现这个对象没有指定的方法那就在原型链上一次查找这个方法直到 Object 对象
函数也是对象因此函数也有原型对象当一个函数被声明出来时(也就是当函数对象被定义出来时)就会生成一个新的对象这个对象的 prototype 属性指向 Object 对象而且这个对象的 constructor 属性指向函数对象
通过构造函数构造出的新对象其原型指向构造函数的原型对象所以我们可以在构造函数的原型对象上添加函数这些函数就不是依赖于 ticker 或 ticker 而是依赖于 TICKER 了
你也许会这样做
复制代码 代码如下:
function TICKER(config){
var n = config
nStart;
}
TICKER
prototype
getN = function{
// attention : invalid implementation
return n;
};
TICKER
prototype
tick = function{
// attention : invalid implementation
n += config
step;
};
请注意这是无效的实现因为原型对象的方法不能访问闭包中的内容也就是变量 n TICK() 方法运行之后无法再访问到 n 浏览器会将 n 销毁为了访问闭包中的内容对象必须有一些简洁的依赖于实例的方法来访问闭包中的内容然后在其 prototype 上定义复杂的公有方法来实现逻辑实际上例子中的 tick() 方法就已经足够简洁了我们还是把它放回到 TICKER 中吧下面实现一个复杂些的方法 tickTimes() 它将允许调用者指定调用 tick() 的次数
复制代码 代码如下:
function TICKER(config){
var n = config
nStart;
this
getN = function(){
return n;
};
this
tick = function(){
n += config
step;
};
}
TICKER
prototype
tickTimes = function(n){
while(n>
){
this
tick();
n
;
}
};
var ticker
= new TICKER({nStart:
step:
});
ticker
tick();
console
log(ticker
getN()); //
>
var ticker
= new TICKER({nStart:
step:
});
ticker
tickTimes(
);
console
log(ticker
getN()); //
>
这个 TICKER 就很好了它封装了 n 从对象外部无法直接改变它而复杂的函数 tickTimes() 被定义在原型上这个函数通过调用实例的小函数来操作对象中的数据
所以为了维持对象的封装性我的建议是将对数据的操作解耦为尽可能小的单元函数在构造函数中定义为依赖于实例的(很多地方也称之为“私有”的)而将复杂的逻辑实现在原型上(即“公有”的)
最后再说一些关于继承的话实际上当我们在原型上定义函数时我们就已经用到了继承! JavaScript 中的继承比 C++ 中的更……呃……简单或者说简陋在 C++ 中我们可能会定义一个 animal 类表示动物然后再定义 bird 类继承 animal 类表示鸟类但我想讨论的不是这样的继承(虽然这样的继承在 JavaScript 中也可以实现)我想讨论的继承在 C++ 中将是定义一个 animal 类然后实例化了一个 myAnimal 对象对这在 C++ 里就是实例化但在 JavaScript 中是作为继承来对待的
JavaScript 并不支持类浏览器只管当前有哪些对象而不会额外费心思地去管这些对象是什么 class 的应该具有怎样的结构在我们的例子中 TICKER() 是个函数对象我们可以对其赋值(TICKER=)将其删掉(TICKER=undefined)但是正因为当前有 ticker 和 ticker 两个对象是通过 new 运算符调用它而来的 TICKER() 就充当了构造函数的作用而 TICKERprototype 对象也就充当了类的作用
以上就是我所了解的 JavaScript 模块化的方法如果您也是初学者希望能对您有所帮助如果有不对的地方也劳驾您指出