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