高级面向对象写法.docx
《高级面向对象写法.docx》由会员分享,可在线阅读,更多相关《高级面向对象写法.docx(12页珍藏版)》请在冰豆网上搜索。
高级面向对象写法
寻找最好的JavaScript面向对象模式和封装结构
作者:
admin发布时间:
2010-08-2015:
31:
13
好久不见,这次发的不是笔记啦,是我在公司内部的前端wiki上更新的文档……这个抛弃所有wiki语法要求用户直接手写语义化html用json配置导航的wiki排版相当漂亮,让我这样的懒人也有了码字的欲望,发起人小麦实在系功德无量……
这篇文章去年就准备写,想用循序渐进的形式推演出一个ModulePattern的最佳实践,不过想法越多,归纳总结表达出来的成本就越高,所以一直拖延……这次发的文档是一个简化版,去掉了各式各样乱七八糟的写法,只包含几个常用的,说明文字也不多主要看代码-__-b……初衷是作为给土豆前端team里新来的同事看的提纲(对了由于某人叛逃到产品设计部门,现在又空出一个名额,有兴趣的同学抓紧时间投简历,这次是魔都总部的职位,不是成都的),所以要解释一下,文档中提到的TUI是一个js库(名字是很俗,不过我上次发现某年纪一大把的人也跟我一样俗),土豆一直采用双库并行(不要看成双工并行…)的形式,在紧跟开源社区发展的同时自己掌控所有环节和基础架构,没有使用jQueryUI和那套基于DOM的插件结构,而jQuery自己几乎不提供OOP工具(这是好事),实际上自己创建这类工具非常简单快捷,相关的代码我提取了一下直接帖在末尾了,仅供参考。
另外,为了符合NicholasZakas在最近的国际会议上传达的精神,我修改了若干变量名跟他ppt里的例子保持一致——这件事教育我们,平时多上对保持先进性是多么重要。
Tudou’sJavaScriptGuideline—OOPandModule
介绍土豆在面向对象和模块化设计方面的工具和实践
“Don’tRepeatYourself.”(DRY)
“Ratherthanconstruction,programmingismorelikegardening.”
Quotefrom:
AndyHuntandDaveThomas,ThePragmaticProgrammer
索引
1.创建类,继承,混入,实例化
2.模块化ModulePattern
3.沙盒,模块间的解耦,与外部通信
4.按需加载模块On-demandLazyLoad
5.总结
创建类,继承,混入,实例化
我们依赖的核心工具是TUI.clone
简洁的,支持私有属性,不需要prototype的写法:
JS是基于原型而不是基于类的面向对象语言,JS是“无类型”的,类是仿造出来的概念,实质只有对象。
new只是用来复制对象,构造函数只是用来返回对象,两者对JS的OOP来说并不是必须的。
1.var dog = function(options){
2. var privateAttr = 1; //私有属性
3. var private_method = function(){}; //私有方法
4. return {
5. option:
options, //实例属性
6. method1:
function(){}
7. };
8.};
9.
10.var xiaobai = dog({});
∙对私有属性/方法的支持比较好
∙最适合单例模式(Singleton)
∙延迟单例的初始化,提高页面初始化的速度
∙缺点:
对继承的支持不佳
∙缺点:
在需要频繁创建大量对象,而方法非常多的场合,浪费资源(因为每个实例的方法指向的都是不同的函数对象,每次实例化都要重新生成所有函数)
在第2个缺点的场合,传统的prototype写法效率更高,支持继承,但是代码分散,不易读,TUI.clone提供了更好的写法——
传统的、支持继承的、仿Ruby风格的Class写法:
1.//创建新类
2.var Dog = TUI.newClass({
3. initialize:
function(options){ //构造函数
4. this.option = options;
5. }
6.});
7.
8.//实例化
9.var xiaobai = new Dog();
10.
11.//继承
12.var Cat = TUI.newClass(dog, { //只允许单继承
13. mixin:
[TUI.event], //混入其他方法
14. initialize:
function(options){
15. this.superClass.call(this, arguments); //调用父类构造函数
16. }
17.});
∙风格类似mootools的newClass
∙TUI.newClass只是TUI.clone的封装
∙mixin相当于在构造函数里$.extend(this.prototype,TUI.event)
∙实际上应该少用继承,多用mixin和组合模式,后者更符合JS的特点
∙缺点:
封装效果不好,不支持私有属性和方法
TUI.clone既可以复制构造函数的prototype,也可以直接复制对象(跟jQuery.extend不同,依靠原型继承),所以利用它直接生成实例。
这样可以在不引入prototype和语法糖的情况下把第一种方法改造的同样高效——
对象克隆工厂的写法:
1.var dog = (function(jQuery, TUI){ //传入需要访问的全局命名空间
2. var a = "xxx"; //不作为状态的私有属性
3. var private_method = function(){ //私有方法
4. alert(this.option);
5. };
6.
7. var PublicObj = { //相当于prototype对象
8. method1:
function(){
9. private_method.call(this); //访问私有方法,共享状态
10. }
11. };
12.
13. return function(options){ //对象工厂
14. var obj = TUI.clone(PublicObj); //克隆
15. obj.aption = options; //构造函数中的常规任务
16. $.extend(obj, cat); //mixin其他对象的方法
17. return obj;
18. };
19.})(jQuery, TUI);
20.
21.var xiaobai = dog({});
∙效率高,封装好,耦合少易于修改和扩展
∙缺点:
私有属性不能作为实例状态
把以上第一种方法和第三种方法结合互补,为module模式——
模块化ModulePattern
核心工具是TUI.module
1.TUI.widget.dog = (function($, TUI){ //除了库的命名空间,尽量不访问全局变量
2. //所有模块代码都封闭在这个区域内
3.
4. var a = "xxx"; //不作为状态的私有属性
5. var private_method = function(){ //私有方法
6. alert(this.option);
7. };
8. var privateObj = {}; //可作为状态的私有属性
9. var privateAttr = function(name, value){ //读写私有属性的方法,只能在内部使用
10. var p = private[this.objectId];
11. if (!
p)
12. p = private[this.objectId] = {};
13. if (value)
14. p[name] = value;
15. return p[name];
16. };
17.
18. TUI.dog = TUI.newClass(parentClass, {
19. mixin:
[TUI.event], //混入其他方法
20. initialize:
function(options){
21. this.superClass.call(this, arguments); //访问父类构造函数
22. },
23. public_method:
function(){
24. var b = a + privateAttr.call(this, "c"); //访问私有属性
25. private_method.call(this); //访问私有方法
26. }
27. });
28.
29.
30. //工厂方法,给别人使用的接口
31. return function(options){
32. var obj = new TUI.dog(options)
33. //实例加上唯一的ID,类似Ruby,注意只用+newDate()不能避免重复
34. obj.objectId = "tui_object" + ( +new Date()*10000 + Math.random
(1)*10000 );
35. return obj;
36. };
37.})(jQuery, TUI);
为了便于理解,以上代码是集中在一起的简单实现,进一步封装之后,有些步骤可以省略:
∙objectId的初始化已经由TUI.clone实现
∙privateAttr方法和私有属性map由TUI.newModule实现
∙TUI.newModule是TUI.namespace和TUI.module.create的封装
基于TUI.clone和TUI.newModule的写法
1.TUI.newModule("TUI.widget.dog", function(sandbox,$, TUI){
2. TUI.dog = TUI.newClass(parentClass, {
3. mixin:
[TUI.event],
4. sandbox:
sandbox, //有sandbox属性传入时,attr属性会被转化为外部无法访问的私有属性
5. attr:
{ //初始化私有属性的默认值
6. a:
1,
7. b:
2
8. },
9. initialize:
function(options){
10. this.superClass.call(this, arguments); //访问父类构造函数
11. },
12. public_method:
function(){
13. var b = this.attr(sandbox, "a") + this.attr(sandbox, "c"); //访问私有属性,通过sandbox参数来验证身份
14. }
15. });
16.
17. return function(options){
18. var obj = new TUI.dog(options)
19. return obj;
20. };
21.}, [jQuery, TUI]);
22.
23.var xiaobai = TUI.widget.dog({});
24.console.log(xiaobai.sandbox); //undefined
25.console.log(xiaobai.attr); //undefined
这里的sandbox其实还可以做很多事——
沙盒,模块间的解耦,与外部通信
TUI.module.create方法其实来自TUI.moduleClass的实例,其他独立应用同样可以继承TUI.moduleClass,构造自己的沙盒对象
1.var Douwan = TUI.newClass(TUI.moduleClass, {
2. notify:
TUI.clone(TUI.event), //事件在这里可以理解为通信器
3. initialize:
function(options){
4. var me = this;
5.
6. this._sandbox.notify = this.notify; //把通信器指向自己,避免跟全局的事件命名冲突
7.
8. //给沙盒增加ajax方法,让module内的代码通过沙盒来通信,屏蔽url路径和响应格式之类的细节
9. var url = "
10. this._sandbox.getJSON = function(param, fn){ //模块只需要传url参数
11. me.getJSON(url, function(text){
12. var jsondata = me.toJSON(text);
13. fn(jsondata); //无论返回格式是什么,都传json给模块
14. });
15. };
16. },
17. toJSON:
function(){},
18. getJSON:
function(){}
19.});
让应用的不同模块之间解耦,避免在模块内部直接使用外部的命名(除了库的api)
1.var douwanObj = new Douwan({});
2.
3.var module1 = douwanObj.module.create(function(sandbox,$, TUI){
4. sandbox.getJSON({ iid:
10000 }, function(json){
5. //通过通信器调用module2.update
6. sondbox.notify.fire("module2-update", [json.date]);
7. });
8.}, [JQuery, TUI]);
9.
10.var module2 = douwanObj.module.create(function(sandbox,$, TUI){
11. var obj = {
12. update:
function(){}
13. };
14. //注册一个update消息的接收器
15. sondbox.notify.bind("module2-update", function(date){
16. obj.update(date);
17. });
18.
19. return obj;
20.}, [JQuery, TUI]);
页面初始化时并不一定需要渲染或操作所有模块,因此有些模块的代码可以放在外部文件里,需要的时候再注入到页面里,类似Python的import
按需加载模块On-demandLazyLoad
自动管理各个模块之间的依赖关系,根据代码内容加载不同的文件,这样做的成本太高,适合大型企业应用,我们的设计原则是“恰到好处”,所以通过文件名来管理模块
注册模块
1.TUI.module.join(" { domain:
"", version:
0 });
∙配置从url里获取,第二个配置参数是可选的,优先级更高,用于调试(指向开发环境)
∙域名其实可以省略,js文件的域名通过TUI.domain和autodomain.js脚本来自动配置
∙版本号很重要,支持a_10.js/a.v10.js/a_v10.js等写法
使用模块
1.TUI.module.use("/fn/saleloader", function(){
2. adExtension.load(); //这个是saleloader.js内的方法,必须等js加载完后执行
3.
4. //这个区域是执行saleloader.js内代码的安全空间
5.});
∙类似YUI3的Y().use,省去了版本冲突之类无用的特性
∙第一次use时会向页面里加载对应的script
∙函数区域内的代码是异步执行的,会等到saleloader.js加载完后依次执行,如果已经加载过了,直接执行,类似jQuery.ready
总结
∙以上方法虽然有递进关系,但并不是要表示最后面的才是最好的方法
∙最好的方法不是唯一的,要根据场合选择最适合的方法,以上方法都有适用场合
∙本文档涉及到的API:
TUI.clone, TUI.newClass, TUI.moduleClass, TUI.module.create, TUI.newModule,TUI.module.join, TUI.module.use, TUI.event
=============开始帖源码的分割线=============
TUI.clone
/**
*@public继承一个类或复制一个对象
*@note
*@param{object|function}oldone是需要继承的构造函数或需要复制的对象
*@param{object|function}ex为函数时,是子类的构造函数,或者用来加工新对象
*ex为对象时,为子类的方法,其中initialize方法为构造函数,mixin为混入的超类
*@return{object|function}
*/
clone:
function(oldone,ex){
varnewobj,
isClass=!
oldone||$.isFunction(oldone),//继承操作
constructorFn=ex&&!
$.isFunction(ex)&&ex.initialize||ex;//子类构造函数
if(!
isClass){
newobj=function(){
if(constructorFn)
constructorFn.apply(this,arguments);
};
newobj.prototype=oldone;
returnnewnewobj();
}else{
//为module内部定义的类提供相关方法
varc={_sandbox:
ex.sandbox,_default:
ex.attr};
newobj=function(){//构造函数
if(this.constructor===newobj){//如果this指向子类实例,已经执行过以下的初始化代码
this.objectId="TUI-object-"+++obj_uuid;//实例的唯一ID
varp=c;
if(p._sandbox&&p._default)
this.attr(p._sandbox,p._default);//初始化私有属性的默认值
}
if(constructorFn)//执行构造函数的自定义部分
constructorFn.apply(this,arguments);
};
//原型继承,子类构造函数里需要显示调用父类构造函数
varnewproto=oldone?
this.clone(oldone.prototype):
{};
//混入其他超类方法
if(ex.mixin)
this.mix.apply(this,([newproto]).concat(ex.mixin));
//加入子类方法,覆盖混入和继承
this.mix(newproto,ex,{
constructor:
newobj,//恢复
superClass:
oldone||Object//在子类的构造函数中可以用this.superClass访问父类
});
deletenewproto.initialize;
if(c._sandbox){
deletenewproto.sandbox;//沙盒一定要删除,不能暴露出去
newproto.attr=function(sandbox,attrname,value){//通过sandbox参数杜绝来自外部的访问
returnsandbox.attr.call(this,attrname,value);
};
}
newobj.prototype=newproto;
returnnewobj;
}
}
TUI.moduleClass
/**
*存放实例的私有状态
*@private
*/
varprivateAttr={};
/**
*module的抽象类
*@note可以继承到其他应用上,构造独有的sandbox
*/
TUI.moduleClass=TUI.newClass({
initialize:
function(){
this.notify=newTUI