JavaScript与设计模式
这篇文章主要是记录自己的一些想法,同时也能够让刚刚接触编程的人对如何构建健壮易用的代码有所了解,而有丰富编程经验的高手能够给予意见和建议.这里打算把这个议题写成一个系列文章,这篇就算做一个引子,废话可能多一点,见谅:P
模式实际上是生活中很常见的概念,它并不神奇.举个例子,我们迈过一个台阶,这里很自然的你就使用了一种模式,要先抬高一条腿,然后将脚落到前面的台阶上,然后再抬高另外一条腿,放到另外一个台阶上,这个过程很自然的完成了.当然你也可以选择两条腿同时蹦上去,或者加上手,爬上去,但这不是首选的方式.我们迈过台阶的时候实际就是一个迈台阶的模式,你不用考虑很久直接就会迈过去,事实上如果你连迈台阶都要考虑很久的话…可能你的日子就没法正常过了.
程序设计中的模式也是对程序设计中可能碰到的问题的解决方法进行抽象和总结得出的一些范式.你可以在解决典型问题的时候使用相关的模式,相当于站在巨人的肩上,只有基础打好了,后面才会有更好的发展.合理的使用设计模式能够使你构建出健壮,易扩展,易维护的程序.
设计模式方面的资料非常多,但是绝大多数都是针对强类型的语言实现写的,如Java等,而JavaScript则是弱类型的语言,它没有类,接口,抽象类,继承,重载,多态等概念.所以在实现的时候有更多的变通余地,更加灵活,但是随之而来的就是不利于控制.所以编写优秀的JavaScript代码对编程人员的要求反而更高,因为很多东西编译器无法替你检查,你需要自己去控制.
先说类,JavaScript中没有类的概念,但是Function对象可以实现类的大部分功能,它可以作为一个完整的对象来使用,其实我理解的类就是一个有自己属性的,有自己行为的一个抽象,我们没必要拘泥于类的这个概念.这方面的资料很多,这里就不多说了.
再说接口,这个概念JavaScript确实没有,也没类似的代替内容,接口在我理解中其实是签订的一个契约,继承接口的类必须尊重契约做事,至于这个契约是实质的契约还是口头的约定,其形式更大于内容.当然有接口,编译器会在编译的时候发现不遵守契约的坏分子,让其无所遁形,给开发者带来很大的便利.如果所有的类都是君子的话,这个检查就没什么必要了是吧,我相信每个写JavaScript的程序员都是君子:)这里的接口可以是我们的一段注释,我们约定这里必须存在哪些属性,哪些方法,实现的时候就要做到这些,当然我们也可以设计一个额外的功能用于检测目标对象是否真正的实现了相关的方法.
一下是一个简单的实例,我们来定义一个电器类的接口:
code-1:
/* Interface IElectrialEquipment { Number Voltage; //额定电压 Number Current; //额定电流 function void TurnOn(); //打开 function void TurnOff(); //关闭 } */
我们把这段注释拿来当我们的契约,这里我们不提接口.我们要实现一个实现这个契约的电视机,或者电冰箱.
那我的实现代码应该是这个样子的:
var TV = function(voltage, current){ //Implement IElectrialEquipment othis.Voltage = voltage; othis.Current = current; othis.TurnOn = function(){ o//打开电视机 oconsole.log('电视启动了'); o}; othis.TurnOff = function(){ o//关闭电视机 oconsole.log('电视关闭了'); o}; othis.TurnDown = function(){ o//调低音量 o} othis.TurnUp = function(){ o//调高音量 o} } var Fridge = function(voltage, current){ othis.Voltage = voltage; othis.Current = current; othis.TurnOn = function(){ o//打开电冰箱 oconsole.log('冰箱启动了'); o}; othis.TurnOff = function(){ o//关闭电冰箱 oconsole.log('冰箱关闭了'); o}; othis.OpenDoor = function(){ o//打开冰箱门 o}; othis.CloseDoor = function(){ o//关闭冰箱门 o} } //现在我们再来做一个万能遥控器 var PowerfulRemoteController = function(IElectrialEquipment){ othis.TurnOn = function(){ oIElectrialEquipment.TurnOn(); o} othis.TurnOn = function(){ oIElectrialEquipment.TurnOff(); o} othis.ShowInfo = function(){ oconsole.log('额定电压是: ' + IElectrialEquipment.Voltage + ' 额定电流是: ' + IElectrialEquipment.Current); o} } //现在我要打开冰箱,不然东西全坏了 new PowerfulRemoteController(new Fridge(225, 5)).TurnOn(); //现在我要打开电视看电视,看点新闻联播吧 new PowerfulRemoteController(new TV(225, 2)).TurnOn();
这里如果我们要继续控制我们的微波炉或者电脑,我们只需要按照我们定义好的契约实现相关的对象就可以使用我们的万能遥控器控制了,而且我们的万能遥控器完全不需要修改任何内容,这也就是我们常说的对修改关闭,对扩展开放原则.大家可以在Fx中的命令行里面试一下.但是弊端也有,就是如果某位程序员在实现一个电器的时候没有实现TurnOff或者TurnOn方法,编译器是不会报错的.当然我们的万能遥控器也就失效了.所以这种方法对团队里面的开发人员要求比较高,大家都要是君子,做事情都要按契约来.
事实上我们也可以定义一个检测对象,用检测对象强制检测一下实现的对象中是否含有我们需要的属性或方法,如果没有则报错.
其他的重载多态JavaScript中也没有相应的实现,只能通过一些手段来实现类似的功能,比如重载,JavaScript中后定义的方法会完全覆盖先前定义的方法,这个可以拿来凑合一下当作重载。JavaScript对传入实参的类型和数目没有强制的要求,所以可以通过判断传入的实参的数目和类型来做不同的操作实现类似多态的功能。
转载请注明原文出处《JavaScript与设计模式》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
表单验证工具V0.2
是的,我又把那个表单验证工具重构了一遍。事实证明上一个版本的表单验证工具API设计的还算是比较成功,起码这个版本完全重构后,基本的API没有改动。只是对实现部分的代码进行了彻底的重构。由于使用了jQuery的ajax相关功能,所以需要本控件需要jQuery支持。
上一个版本中是把所有的规则存储到一个缓存中,然后通过统一的方法进行节点的验证,这样带来的不便就是需要在全局对所有的节点进行控制,现在是将检查的节点封装一个对象,让数据和行为联系更加紧密,每个人只干自己的事情,逻辑上更加清晰。全局validate
对象只负责初始化和最终表单整体的验证,以及提供一些工具方法。另外将提示信息也封装成为一个对象,便于提示信息样式的更改。
validate
对象现在只包含两个对外开放的方法,一个是init
,一个是check
。init
方法用于初始化,传入相关的对象id,规则,事件等信息,check
方法用于检查整个表单是否通过验证,当表单中含有ajax验证时,会在验证通过后调用回调函数。
check
方法有个可选的回调函数参数,当验证规则中存在ajax验证时,控件并不能即时给出验证结果,具体的验证结果会在ajax回调完成后得出。所以这里只要存在尚未验证过的ajax验证,check
方法总是会返回false
。如果通过调用后想执行一些操作,就需要将需要操作的步骤写到check
方法的回调函数中。这样在validate
在进行完ajax检查并且通过验证后会调用方法。
VNode
对象代码的主体结构如下:
var VNode = function(rule){ oself.check = function(isLocal){ o//local check o//ajax check o}; oself.checkAjax = function(fn){}; oself.reset = function(){}; oself.checkManul = function(isLocal){}; oself.checkEvent = function(e){}; oself.resetEvent = function(){}; oif(rule.trigger){ orule.trigger.call(self); o}else{ o$(self.node).bind(validate.config.trigger, self.checkEvent); o} o$(self.node).bind(validate.config.reset, self.resetEvent); };
这里VNode
包含了所有的数据,规则,以及验证的逻辑,验证的时候要做的就是新建一个VNode
对象,剩下的工作交个VNode
来做就好了。VNode
对象主要是根据提供的规则信息获取要验证的元素,然后对要验证的元素按照一定的次序进行验证,包括自定义的验证方法,长度,自定义的正则表达式,系统提供的正则验证,ajax验证。所有的验证都是验证规则存在是才进行的。
VNode
对象也提供了两个钩子,一个是自定义验证函数,一个是自定义触发验证函数。自定义验证函数用于处理复杂逻辑的验证,自定义触发验证函数默认取代blur
方法,用于日历控件等等选择方式的验证。
还有一个重要的对象就是HintMessage
对象,这个对象其实就是根据给出的引用节点,自动计算出提示信息该显示的位置。当然它有相应的参数控制显示的偏移量,默认是与给出的节点位置重合的。当然新实例化一个HintMessage
对象并不能显示出提示信息,要想显示提示信息还需要调用HintMessage.show(message, messageType)
方法来显示相应的信息以及相应的信息类型。信息类型包含以下几种:
//错误 HintMessage.ERROR = 0; //警告 HintMessage.WARNING = 1; //消息 HintMessage.MESSAGE = 2; //成功 HintMessage.SUCCESS = 3;
每种消息类型对应不同的样式名称,你可以通过自定义样式来显示不同样式的提示信息。当然HintMessage还有一点要注意就是给window
对象绑定onresize
事件,当窗口改变大小的时候需要重新计算提示信息显示的位置。绝对定位带来方便的同时也有些副作用。
相关文章:
o
- 表单验证工具
转载请注明原文出处《表单验证工具V0.2》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
Raphaël-JavaScript Library-javascript矢量绘图
你是不是经常究竟在用到javascript绘图时纠结于IE的不支持canvas,纠结于canvas绘图API的繁琐纠结?现在你可以从这种纠结中摆脱出来了。
对,Raphaël是一款轻量的用于web的矢量绘图javascript库,用它你可以很简单的创建各类矢量图形,而且它本身还支持丰富的接口以及各种事件处理。更关键的是它绘出的图像都是矢量的。当然你也可以创建一个包含位图的图像。
Raphaël是使用SVG和VML进行绘图的,所以不要担心兼容性,它支持FireFox3.0+,Chrome5.0+,Opera9.0+以及Internet Explorer6.0+,对你没看错,它完美支持IE6+。而且Sencha Labs也使用了这个库。
怎么样心动了吧?心动不如行动,赶快拿起手中的电话订购吧,呃,赶快用你的鼠标点击下载使用吧!
简单的使用方法:
// Creates canvas 320 × 200 at 10, 50 var paper = Raphael(10, 50, 320, 200); // Creates circle at x = 50, y = 40, with radius 10 var< circle = paper.circle(50, 40, 10); // Sets the fill attribute of the circle to red (#f00) circle.attr("fill", "#f00"); // Sets the stroke attribute of the circle to white circle.attr("stroke", "#fff");
转载请注明原文出处《Raphaël-JavaScript Library-javascript矢量绘图》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
commonjs-module1.1.1-draft-中文版
Contract 约定
Require
-
o
- requrie是一个函数
-
o
- require函数接受一个模块标识符
- require返回外部模块的输出API
- 如果有一个依赖循环,那么外部模块在它被另外一个依赖请求时,可能并没有完成执行;在这种情况下,在调用require请求当前模块执行之前,require返回的对象必须至少包含外部模块准备好的输出接口。
- 如果请求的模块无法获取,require必须抛出一个错误
- require函数可以有一个main属性
-
o
- 这个属性可行时,应该是只读的且不能被删除
- main属性必须是undefined或者与一个已加载的模块上下文中的module相等
o
- require函数可以有一个paths属性,它是一个优化过的路径字符串数组,从高到低,到最顶层的模块路径。
-
o
- paths属性不能存在在沙箱(sandbox,一种安全的模块系统)中
- paths属性必须在所有模块中的引用必须一致
- 使用替换对象取代paths属性可能没有任何作用
- 如果paths属性存在,paths属性内容的修改必须被相应模块的搜索行为所体现
- 如果paths属性存在,它可能不是一个搜索路径的详细清单,加载器可能在提及的路径之前或之后在内部查找其他位置
- 如果paths属性存在,它是加载器解决,标准化及规范化提供的路径的优先选择。
o
o
o
o
o
o
o
o
o
o
Module Context 模块上下文
-
o
- 在模块中有一个符合上述定义的自由变量require
- 在模块中有一个自由变量exports,模块可以向其添加自己的API作为自己的执行方法。
-
o
- 模块必须使用exports对象作为对外的唯一途径
- 在模块中必须要有个自由变量,module对象
-
o
- module对象必须有一个id属性作为顶层的模块id。这个id属性必须能够通过使用require(module.id)返回module.id来源所对应的导出对象。
- module对象可以有一个uri字符串,它应该是一个合格的指向模块被创建的资源的URI(统一资源标示符).uir属性不应该出现在沙箱中。
o
o
o
Module Identifiers 模块标示符
-
o
- 模块的标示符(id)是一个以斜线分割的协议字符串
- 一个协议必须是驼峰式的标示符,“.”或者“..”
- 模块标示符可以没有文件名后缀例如“.js”
- 模块标示符可以是相对路径,也可以是绝对路径。如果一个模块标示符是以“.”或者“..”开始的,那么它是相对的。
- 绝对标示符用于解决概念模块命名空间的根。
- 相对标示符被解决相对于requrie被写和调用的模块标示符
o
o
o
o
o
Unspecified 未指明
标准中留下了一下未作说明的几个要点:
-
o
- 模块是否存储在数据库中,文件系统中,或者工厂方法,或是互换链接库
- 是否支持的路径是为解决模块加载器模块标示符
o
转载请注明原文出处《commonjs-module1.1.1-draft-中文版》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
关于JavaScript加载的思考-建设高性能网站
JavaScript加载很多时候能够决定用户从打开页面看到页面的时间,JavaScript的加载与样式表和DOM以及图片等资源的加载既有相同之处,又有很大的不同之处。最大的不同就是JavaScript的加载完成后会立即执行并阻塞页面的渲染,这就让脚本的加载与其他资源的加载有了很大的不同。
在页面中直接引用的JavaScript会阻塞整个页面的加载,这就意味着每加载一个script标签就会有两种副作用:
-
o
- 多占用一个Http的请求资源;
- 加载完成后阻塞页面,直到JavaScript执行完毕。
o
HTTP/1.1的标准建议浏览器对同一个主机名只能并行下载不超过2个文件(事实上现在的浏览器的限制的数目都超过2个),这就意味着你引用的文件越多页面加载的速度将会变得越慢,因为很多文件都处于等待下载的状态中。要解决HTTP请求这个问题可以从两个方面着手:
-
o
- 从多个主机名加载资源,这个可以使用多个二级域名的办法来实现,但是增加的引用文件的混乱程度;
- 减少引用的资源数目。
o
具体到JavaScript的载入,当前比较常用的方法是使用压缩工具将多个JavaScript合并成一个文件,同时也能减小JavaScript文件的体积。另外一个是通过统一的接口加载Script资源,后台程序将请求的文件合并成一个文件输出,并按照版本号进行缓存,后台判断如果有缓存的版本且缓存未过期将直接输出缓存文件。后一种方法在wordpress中有类似的使用,但是未见wordpress进行文件的缓存,我曾经写过一个类似的实现,将在后续的文章中放出。但是这个方法也有一个问题,因为要对文件进行合并,读写操作,另外考虑到后台语言的运行效率,所以对服务器也将增加不少压力。
至于JavaScript加载过程中会阻塞页面,这个问题可以通过把JavaScript放到页面代码的最底部进行加载,这样会在页面显示完成之后在执行JavaScript。当然script标签有个defer属性,可以让script标签在页面DOM加载完成之后执行,但是由于浏览器的支持程度不同,所以使用defer属性不是一个稳妥的解决方案。
事实上当前还有一种比较流行的解决方案,就是使用JavaScript动态生成script节点,然后将其插入到文档中。动态的生成script节点插入文档的加载方式可以实现无阻塞的加载脚本资源,提高了下载的效率。通过两幅图对比一下:
通过一次的测试并不能证明载入速度的快慢,所以载入时间在这里是无意义的。但是,通过查看载入的方式可以了解到,动态加载脚本的模式下,脚本的加载未阻塞图片的加载,而传统的方式则会在脚本加载完成之后再加载下面的内容。
动态载入script节点能够解决并行下载的问题,但是并无法解决JavaScript执行阻塞的问题,上面两图时间线中土黄色部分即为脚本执行的阻塞时间,可以看到阻塞的时间甚至比加载的时间要长。上面提到了,解决这个问题的办法是把JavaScript放到文档的结尾处。如果使用script标签动态加载的话这里就很方便,可以在DOM Ready或者Page onload的时候加载脚本,或者按需加载即什么时候用到什么时候加载,这样就不会阻塞页面的渲染。当然这个加载的时机还是要慎重考虑,根据脚本的作用进行不同的调整。
抛砖引玉,如有谬误还请不吝赐教。
转载请注明原文出处《关于JavaScript加载的思考-建设高性能网站》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
JavaScript弹出窗口控件
JavaScript实现的弹出窗口是我们常用的控件之一,当然实现也有很多,这里把我写的控件整理一下发出来。
这里实现了五种弹出窗口,继承自BasePopBox。分别为DialogBox,FrameBox, AlertBox, ConfirmBox,ToastBox。
-
o
- DialogBox主要用于显示一般性内容,可以传入页面中的一个Dom对象的Id,也可以是一个Dom对象或者一段HTML字符串用于显示在弹出框中。
- FrameBox需要传入一个URL地址,用于在弹出窗口中显示一个iFrame页面。
- AlertBox用于显示一般的提示信息,类似于JavaScript中的alert弹出框。传入的参数为需要显示的信息。
- ConfirmBox用于显示需要用户需要做出判断的信息,当用户点击“确定”或者“取消”时会触发用户传入的确定事件或者取消事件。
- ToastBox用于显示可以一个可以自动消失的提示信息。
o
o
o
o
另外系统还提供了一个可配置参数集合用于界面,以及自定义按钮等的配置。
这里没有使用同一个对象,通过传入不同状态参数的方式来实现不同效果的弹出窗口,而是通过采用继承自相同的父对象,在子对象中通过重载相关的方法来实现不同的效果。这样提高组件的可重用性和可扩展性,甚至如果你的页面只需要一种弹出窗口你可以只引用一种弹出窗口的实现,相应的也减少了文件的体积。
在易用性方面,系统提供了基本的五种模式用于选择,基本可以满足大部分情况的需求。当然一个控件不可能同时满足所有用户的需求,所以一个控件的可扩展性也是很重要的。可以在继承基础类的基础上通过重载需要的方法来实现自己需要的效果,其它相应的处理交给基类来实现,减少开发的代码量,提高了重复使用的效率(当然需要看一下基类中有哪些方法是需要付出的代价:P)。
目前尚未做IE6的兼容测试
使用JavaScript模仿消息提示框效果(兼容IE6+,FireFox)
如有谬误请不吝赐教。
2011.08.18更新
-
o
- 修正FrameBox拖动时容易丢失mouseup事件的bug
- 更新dragmanaer,添加拖动开始,进行,结束事件回调,添加setCapture和releaseCapture相关处理
o
转载请注明原文出处《JavaScript弹出窗口控件》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。