显示标签为“Javascript”的博文。显示所有博文
显示标签为“Javascript”的博文。显示所有博文

2010年7月29日星期四

遍历字符的性能

  1. s.charAt(i)
  2. s.split("")[i]

Test

在 IE7, Firefox3.6.8, Chrome5.0, Safari5.0, Opera10 上测试, 除了 Firefox 之外,所有的浏览器均是第 2 种表达式更快速。

2009年1月6日星期二

Javascript无块级作用域

最近在做一系列Javascript压缩工具,语法压缩,语义压缩,字符串压缩均有涉及(p.s.有趣的是,压缩变量名之类的“有损压缩”不影响代码执行,但是字符串压缩这样的“无损压缩”却总是需要解压消耗)。

在实现压缩局部变量名时,最初的实现是将 if/else, for/in, do/while, switch/case/default, try/catch/finally, with和Object实例对象(后面统称为“块级作用域”)与function一样,都作为独立的作用域,但是测试发现在Javascript中并不是这么回事。

尝试着在块级作用域里声明定义变量:
if(true){
var bool=true;
}
document.write(bool); // output:true
会发现输出true,而且不论嵌套多深,也不论使用任何块级作用域进行嵌套,最后变量依然如同在调用处之前,而且同级的作用域中定义的一样;值为在此之前,在块级作用域中所做改变的结果。注意:如果块级作用域未被执行,则其中声明定义的变量会被声明(var name),但不被定义(即未被初始化,此时name为undefined,引用name时不抛异常)。
if(false){var bool=true;}
document.write(bool); // output:undefined
之前虽知道,IE里for(var i=0;...)里定义的i,在for之外也是可以使用的,但是现在才知道这种情况更为猖獗,大出我的意料之外,而更意外的是,IE 7(IE6?), Firefox 3(FF1,FF2?), Opera 9, Chrome 1, Safari 3表现均丝毫不差。

虽然与某些编程规范不同,但是js就是这样了,唉。早知道这样,压缩变量名这部分也就不用那么费劲了,只得重写了。

另外还有一些Javascript作用域方面的文章:
Javascript中的作用域,好像是realazy翻译的,文章很好,虽然有点文不对题(主要阐述this的作用域链问题)。
js变量作用域及可访问性的探讨,详细介绍了各种变量的作用域及其可访问性问题,有部分不准确/正确的,出处未知,遍地都是转载。

附A 测试代码:
// Global Scope
if (true){
var v10 = true;
}else {
var v00 = false;
}
document.write("v10 : "+v10+"<br />");
document.write("v00 : "+v00+"<br />");
document.write("<hr />");

// Function Scope
function functionScopes(){
if (true){
// 以下均执行
var v11 = true;

do{
var v12 = true;
}while (false);

while (true){
var v13 = true;
break;
}

switch (1){
case 1:
var v14 = true;
default:
var v15 = true;
}

try{
var v16 = true;
throw new Error("");
}catch(e){
var v17 = true;
}finally{
var v18 = true;
}

for (var v19=0; v19<1; v19++){
var v1a=true;
}

with(v11){
var v1b = true;
}

function inn1(){
var inn11 = false;
}
}else {
// 以下均未执行
var v01 = false;

do{
var v02 = false;
}while (false);

while (false){
var v03 = false;
}

try{
var v06 = true;
throw new Error("");
}catch(e){
var v07 = true;
}finally{
var v08 = true;
}

switch (1){
case 1:
var v04 = true;
default:
var v05 = false;
}

for (var v09=0; v09<1; v09++){
var v0a=true;
}

with(v01){
var v0b = true;
}

function inn0(){
var inn01 = false;
}
}
document.write("v11 : "+v11+"<br />");
document.write("v12 : "+v12+"<br />");
document.write("v13 : "+v13+"<br />");
document.write("v14 : "+v14+"<br />");
document.write("v15 : "+v15+"<br />");
document.write("v16 : "+v16+"<br />");
document.write("v17 : "+v17+"<br />");
document.write("v18 : "+v18+"<br />");
document.write("v19 : "+v19+"<br />");
document.write("v1a : "+v1a+"<br />");
document.write("v1b : "+v1b+"<br />");
document.write("<hr />");
document.write("v01 : "+v01+"<br />");
document.write("v02 : "+v02+"<br />");
document.write("v03 : "+v03+"<br />");
document.write("v04 : "+v04+"<br />");
document.write("v05 : "+v05+"<br />");
document.write("v06 : "+v06+"<br />");
document.write("v07 : "+v07+"<br />");
document.write("v08 : "+v08+"<br />");
document.write("v09 : "+v09+"<br />");
document.write("v0a : "+v0a+"<br />");
document.write("v0b : "+v0b+"<br />");

document.write("<hr />");
try{alert(inn11);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(inn01);}catch(e){document.write((e.message||e)+"<br />");}
}
functionScopes();

document.write("<hr />");
try{alert(v11);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v12);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v13);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v14);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v15);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v16);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v17);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v18);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v19);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v1a);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v1b);}catch(e){document.write((e.message||e)+"<br />");}

try{alert(v01);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v02);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v03);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v04);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v05);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v06);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v07);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v08);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v09);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v0a);}catch(e){document.write((e.message||e)+"<br />");}
try{alert(v0b);}catch(e){document.write((e.message||e)+"<br />");}

附B 测试结果(IE 7, FF 3, Opera 9, Safari 3, Chorme 1均同):
v10 : true
v00 : undefined

v11 : true
v12 : true
v13 : true
v14 : true
v15 : true
v16 : true
v17 : true
v18 : true
v19 : 1
v1a : true
v1b : true

v01 : undefined
v02 : undefined
v03 : undefined
v04 : undefined
v05 : undefined
v06 : undefined
v07 : undefined
v08 : undefined
v09 : undefined
v0a : undefined
v0b : undefined

'inn11' 未定义
'inn01' 未定义

'v11' 未定义
'v12' 未定义
'v13' 未定义
'v14' 未定义
'v15' 未定义
'v16' 未定义
'v17' 未定义
'v18' 未定义
'v19' 未定义
'v1a' 未定义
'v1b' 未定义
'v01' 未定义
'v02' 未定义
'v03' 未定义
'v04' 未定义
'v05' 未定义
'v06' 未定义
'v07' 未定义
'v08' 未定义
'v09' 未定义
'v0a' 未定义
'v0b' 未定义

2008年12月29日星期一

Javascript String 方法效率大比拼

最初是通过梅子梅花雪)关于大型字符串拼接效率(12)的研究得到启发,最近又看到never-online的从trim原型函数看js正则表达式的性能 ,里面有介绍正则表达式效率陷阱等问题,并提出解决方法。我向来对这些鸡毛蒜皮感兴趣,也开始对大型字符串各种方法实现的效率进行比较,并尝试提高这些方法的效率。

1. 大型字符串拼接
梅子所言,使用数组的join方法确实是最好的实现,可以根据这个思路设计StringBuilder, StringBuffer类。



2. 大型字符串trim
其实never-online在他的文章里有一些说的不准确的地方,代码也不算很精炼。既然这是鸡毛蒜皮的小事,这些零碎东西当然要斤斤计较了。
我很久以前有收集到这样一些实现,I:
String.prototype.trim = function(){
return this.replace(/(^\s+)(\s+$)/g, '');
};
为了避免正则表达式使用括号带来的消耗,可以写成这样,II:
String.prototype.trim = function(){
return this.replace(/(?:^\s+)(?:\s+$)/g, '');
};
另外有一套实现是这样的,III:
String.prototype.lTrim = function(){
return this.replace(/^\s+/, '');
};
String.prototype.rTrim = function(){
return this.replace(/\s+$/, '');
};
String.prototype.trim = function(){
return this.lTrim().rTrim();
};
其实调用函数也会多少有一点消耗,写成这样或许会快一点点(开个玩笑,这样写会带来一些冗余代码,这时候就需要基于效率(时间)、代码量(空间)和可维护性方面的考量了),IV:
String.prototype.trim = function(){
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
后来我对正则表达式有了更多的了解,知道了贪婪与非贪婪匹配,于是自作聪明写了这一段:
String.prototype.trim = function(){
return this.replace(/^\s*(.*?)\s*$/, "$1"); // 两端空白字符贪婪匹配,中间字符非贪婪匹配。
};
我曾经为这段代码自鸣得意了好长一段时间,不过后来想到点号不包括换行符,字符串中间有换行符时,返回值就不正确了,于是不情愿的改成这样(多行模式效率也很低),V:
String.prototype.trim = function(){
return this.replace(/^\s*((?:.\n)*?)\s*$/, "$1");
};
谁知,这样的代码遇到大家伙时效率会一落千丈,哎,失败。

原以为String.replace方法比String.substr、String.substring效率低,于是想,只使用正则表达式获得两头(或者一头)的索引位置,然后使用substring方法取出子串,VI:
String.prototype.trim = function(){
var l=this.length;
/^[\s]*/.test(this);
// /(?=[^\s])/.test(this);// -- never-online
var s = RegExp.lastIndex;
if(1==s && !Char.isBlank(this.charAt(0)))s=0;
if(s==l){return '';}
// /\s*$/.test(this);
// var e=RegExp.index;
var e=$lastIndexOf(function(c){return !Char.isBlank(c);});
e=-1==e?l:e+1;
return this.substring(s,e);
};
而最土的方法,莫过于两头都使用循环获得索引了,VII:
String.prototype.trim = function(){
var f=function(c){return !Char.isBlank(c);};
var l=this.length, s=this.$indexOf(f), e=this.$lastIndexOf(f);
if(-1==s)s=0;
e= -1==e?l:e+1;
return this.substring(s, e);
};
代码里老是for啊for的一大串,为了节省字节,而且有可能的话,也准备再优化一下循环,就实现了$indexOf和$lastIndexOf两个方法,可以传递一个返回boolean值的函数作为参数(本来还想也支持正则表达式参数的,想想以前扩展indexOf和lastIndexOf方法后的效率,就算了),这样就可以求得第一个非空白和最后一个非空白字符的位置了。
String.prototype.$indexOf = function(f){
for(var i=0,c,l=this.length; i=0; i--){
c=this.charAt(i);
if(f(c)){return i;}
}
return -1;
};
String.prototype.$lastIndexOf = function(f){
for(var i=this.length-1,c; i>=0; i--){
c=this.charAt(i);
if(f(c)){return i;}
}
return -1;
};
至于说要扩展到支持更长子串和起始索引,以后有需要再说了(顺便说一下,子串越长,有优化算法可以得到更高效率)。
另一个辅助方法:
var Char = {
isBlank:function(c){
//return /\s/.test(c);
return ' '==c '\t'==c '\r\n'==c '\n'==c '\r'==c;
}
};
到了永不在线(Google Translate翻译为“永远在线”)的算法,VIII:
String.prototype.trim = function(){
var s = this.replace(/^\s+/, '');
var l=s.length, e=l;
if(0==l){return '';}
for(var i=l-1; i>=0; i--){
if(!Char.isBlank(s.charAt(i))){
e=i+1;
break;
}
}
return this.substring(0,e);
};
最初这个算法让我很兴奋,直觉上,感觉这样效率肯定要高,不过事实并不是这么简单。

说到这些实现的效率,无法一概而论,因为不同的字符串,它们的效率比也大不同,甚至异乎寻常。

影响trim方法效率的,主要与字符串的总长度,前面空白字符串长度,后面空白字符串长度,以及前中后的比例有关。详细的效率对比表有时间再上,这里只简要提一下:

对于较小的字符串,各种实现都有不错的表现,而对于大型字符串,则实现III, IV表现较为稳定,甚至可以处理超大型字符串(修正:之前有误写成I,II两个较为稳定)。




3. 大型字符串字节长度
即双字节长度为2。注意:这个提法其实也不正确,Javascript是使用Unicode字符集的,所有的字符都(有可能)是双字节字符。将汉字等转换为双字节长度主要是为了某些应用。
最土的方法还是循环遍历所有字符,I:
String.prototype.bytes = function(){
var l=this.length, r=l, n=0xff;
for(var i=l; i>=0; i--){
if(this.charCodeAt(i)>n){
r++;
}
}
return r;
};
这里判断字符是否双字节有很多方法,效率较高的之间相差(大概)不大。

另一种实现则看起来很轻灵,寥寥几行,II:
String.prototype.bytes = function(){
return this.replace(/[^\x00-\xff]/g,"xx").length;
};
多动脑子,则想法愈多(也常把简单的事情复杂化),我想如果可以快速取得表达式(双字节/单字节)匹配次数,两值相加应该比较高效,III:
String.prototype.bytes = function(){
return this.length+this.replace(/[\x00-\xff]/g,"").length;
};
IV:
String.prototype.bytes = function(){
return this.length+(this.match(/[^\x00-\xff]/g)"").length;
};
另外看到梅花雪用数组能提供字符串拼接速度,也想:把字符串split为数组,不想对大型字符串而言,这split一步就慢得不行。

bytes方法的效率:使用Javascript脚本循环大型字符串(I),确实远不如内置的replace方法(II)快,而使用正则表达式match方法(IV)又比replace方法(III)稍快,排名第二。

总结:
1. replace方法因匹配而被替换的子串愈长,效率愈低。
2. 根据目标字符串,选择合适的实现。

2008年11月7日星期五

文本宽度 及 文本框滚动偏移量(scrollLeft)

背景:
最近将多标签输入框进行修改,把原来随光标(caret)移动的自动建议浮动条改为根据当前标签相对左对齐。
解决方法很简单,计算当前标签前的文本宽度(将需要计算的文本转义后放入一个各字体样式与目标输入框相同的容器,如span中,span.offsetWidth即是文本的宽度。注意:文本框中,中文半角空格的宽度和英文半角空格的宽度显示为不同,虽然这两个空格本质上相同),浮动条根据该值定位即可。

问题:
当标签文本过长,超出文本框宽度时,标签文本会向左滚动,但是此时当前标签前面的文本宽度不变,定位浮动条时需要减去文本向左滚动的尺寸。

网页文档对象中,元素都有一个可读写的scrollLeft属性,表示元素内容相当元素容器向左移动的偏移量。

但是Firefox和Opera有一些例外,如文本框的scrollLeft始终为0,Mozilla的描述是:
If the element can't be scrolled (e.g. it has no overflow), scrollLeft is set to 0.
对于一些不可滚动(scroll,即没有溢出)的元素,scrollLeft始终为0。而单行文本框(在Gecko引擎看来)是不可滚动的,即使将样式指定为overflow:scroll(IE会出现水平和垂直滚动条这样的怪胎)。

虽说单行文本框是不应该出现滚动条这样怪异的形态,但是这不表示它是不可滚动的,当文本宽度大于文本框宽度时,(为了将光标caret显示在文本框可见区域)文本势必向左滚动,如图:

解决办法:
对于文本框不支持scrollLeft的浏览器,一个临时的解决办法是,在文本宽度大于文本框宽度时,浮动条定位在相对文本框后端若干像素(便于输入)的位置,在增量输入时,体验不是太差,但是在光标向前移动使文本向右滚动时,就会出现偏差。

最终解决办法是期待浏览器能够得到正确的scrollLeft值,或者其他巧妙的计算方法。

2008年9月1日星期一

同步Web客户端的时间

富客户端应用的普遍流行,许多操作直接在客户端完成,而一些跟时间相关的操作,客户端时间的正确性就显得尤为重要,一个和标准时间相差得离谱的时间,可能会带来滑稽甚至严重的错误。

在C/S构架下,对时间有要求的应用程序一般解决方法是同步客户端和服务器端的时间,一些要求较高的应用甚至需要专门的时间戳技术。

普通的B/S应用程序一般没有权限同步客户端的时间(也不一定有这个必要),这个问题也有一些解决办法。

1. 响应用户请求时返回服务器时间,并使用计时器(如:window.setInterval)做一个虚拟时钟。但是考虑到浏览器消耗和脚本计时器本身并不精准,长时间运行后时钟会出现较大的误差,予以忽略。

2. 响应用户请求时返回服务器时间,并计算服务器与客户端之间的时间差,每次时间操作均以该差值进行修正(可以专门做一个接口)。或者考虑只有在差值较大的情况下才进行修正(并提醒用户修正客户机时间)。

另外还要考虑用户所在不同时区的情况(Date.getTimezoneOffset)。

但是对于较精准的时间需求,这样仍然有一个问题,即从服务器端响应到客户端返回过程中的网络传输时间。这是一个随机数,根据网络情况和其他偶然因素有关,从几十毫秒到几十秒不等,有时达数分钟之久,最惨的是整个页面超时,当然同步也就没有必要了:)


如上图,客户端从客户机的t0时刻发送请求到服务端,然后服务端在T0时刻得到请求,经过连接数据、计算等操作,最终在T1时刻响应完成,客户端最后在t1时刻加载完成。

此时我们最关心的是服务器返回到客户端加载完成这段时间的网络传输耗时,但是暂时无法精确求得,只有一个相当不凑和方法:客户端发生请求时,带上当时的客户机时间(t0,浏览器请求头信息里一般带有,也可以通过客户端脚本new Date(),此时则需要通过异步请求),服务器计算所耗时间也可以得到,响应时返回服务器计算耗时(T1-T0)以及浏览器请求的时刻(t0)以及服务器时间,客户端加载完成时刻(t1)计算网络传输总共耗时为:(t1-t1)-(T1-T0),之后以一定比例(如1:1)计算响应的网络传输耗时。

结果相当不凑和,而过程又相当麻烦,所以只作为抛砖引玉之篇。



图片来自Baidu图片搜索

2008年7月31日星期四

Javascript:undefined & null

昨天遇到一个问题:之前工作良好的代码,突然不受控制。经过调试发现,函数中传递进来的undefined常量居然是一个对象(IE:Object, FF:NodeList)。最后换成null解决。

测试发现undefined虽然是系统内置的常量(表示类型/对象未定义)但不是关键字,可以像String对象一样被覆写(undefined={})。


注:
Javascript中undefined和null是两种很特殊的类型/对象。undefined表示对象未定义,未定义对象会抛出不可预测的异常,而null则表示变量引用对象不存在。使用等于号(==)比较,他们相等;而使用完全等于号(===)比较时,他们不相等。

2008年4月8日星期二

阶乘(factorial)&尾递归(Tail Recursion)

今天看了用递归计算阶乘咋不行呢?》受益良多,这里做下小结。

传统的递归算法写起来很漂亮,代码很简洁,但是没递归一次就需要更深一层的堆栈支持,可能会造成内存溢出而失败,所以递归和goto语句一样声名狼藉。
甚至《代码大全》的作者有这样一句话:如果为我工作的程序员用递归去计算阶乘,那么我宁愿换人。作者对递归的态度相当谨慎,这在静态命令式语言中显然是正确的,但是在函数式语言中,由于有尾递归优化的存在,递归反而是最自然的形式,况且我打心里认为递归更符合人类思维。(by )

尾递归就是从最后开始计算,每递归一次就算出相应的结果,也就是说,函数调用出现在调用者函数的尾部,因为是尾部,所以根本没有必要去保存任何局部变量,直接让被调用的函数返回时越过调用者,返回到调用者的调用者去。举例说明。
 
线性递归(传统递归方式):
function recursion(n){
    return n==1?1:n*recursion(n-1);
}

 
尾递归:
function tailRecursion(n, a){
    a = a||1; // 尾递归之尾,即上次递归结果。
    return n==1?a:tailRecursion(n-1, a*n);
}


这里将基于尾递归的求数值阶乘算法贴下:
Math.factorial_III = function(n){
    var a = arguments[1]||1;
    return n<=1?a:Math.factorial_III(n-1, a*n);
};
 
效率上和循环迭代、阶乘改进算法相当甚至稍胜出(ie6,firefox2,safari3),普通递归的效率最为底下,且需要深入堆栈。
 
参考:
尾递归》-百度百科
用递归计算阶乘咋不行呢?》-

2008年3月31日星期一

正则表达式拼接和构建零长度对象

之前在网上收集到一个正则表达式拼接的方法/函数,一直没有注意,直到昨天用jsdoc生成api文档时才看到。心里想这样的方法实际应用中大概没什么意义,但是出于好奇就拿出来玩了一下,这一玩不要紧啊,这么精炼的东西差点成垃圾。

可能是网络编辑器的原因,原始代码有bug,经过修正和测试,现在的代码如下:代码I


/**
* 连接两个正则表达式。
* 题外话:获得字符串常量""的长度,比构建空数组再求其长度效率高(忽略构建过程,求长度消耗时间相同)。
* @param {RegExp} r 指定被连接的正则表达式对象。
* @param {String} p 连接后的表达式使用的选项,由"i","g","m"组合而成。
* @return {RegExp} 返回连接后的正则表达式。
*/
RegExp.prototype.concat = function(r, p){
var i=(this.source.match(/\((?!\?:)/g) || "").length; // 正向预搜索。
return new RegExp(this.source+r.source.replace(/\\(\d)/g, function($0, $1){
return "\\" + (i+($1 | 0)); // 修正第二个表达式中的反向引用。注意这里的位运算。
}), p);
};

原方法名是contact,现在为了和字符串类的一致,换为concat,至于bug/不足这里不做解读。
这个实际应用意义不大的小程式却有值得称道的几点:
1. 正向预搜索匹配第一个表达式里左括号(不包括非捕获组(?:))的个数。
2. 当第一个表达式没有匹配时返回0长度对象(再求其长度),可以构建空数组([])和空字符串(""),这里使用空字符串,理由下面再解释。
3. 修正反向引用时用的位运算(),这里将匹配到的数值字符串与0位或没有其他意义,只是将数值字符串转型为数值,相当于parseInt()函数。
下面介绍为什么使用空字符串而不是空数组构建0长度对象。代码II
var I = 10000;

var d = new Date();
for (var i=0; i<I; i++){
"".length;
}
d = new Date()-d;

var d2 = new Date();
for (var i=0; i<I; i++){
[].length;
}
d2 = new Date()-d2;

document.write(d+":"+d2);

一万次循环叠加可以发现构建空字符串比构建空数组求长度快1到3倍。
再将代码改为:代码III
var I = 10000;
var s="";
var d = new Date();
for (var i=0; i<I; i++){
s.length;
}
d = new Date()-d;

var a=[];
var d2 = new Date();
for (var i=0; i<I; i++){
a.length;
}
d2 = new Date()-d2;

document.write(d+":"+d2);

可以发现,求空字符串长度与空数组长度的过程效率相当。可见,代码II处空数组效率多余的消耗主要在构建空数组对象上。

2007年12月30日星期日

(Javascript) 阶乘改进算法

最近对数学再返无穷兴趣,在一个数学方面极好的博客上读得关于阶乘计算的好文,就把其中一个较易实现的改进算法发布如下(Javascript实现):

/**
 * 求阶乘的改进算法。
 * 循环求积次数减少一半,但是求平方时增加开销,在IE6,Firefox2,Safari3下测试求170的阶乘
 * (大于170的阶乘结果为Infinity,没有实际意义)均比简单求积方法少不到10毫秒。
 * 求小于50的阶乘的效率表现有时不如直接使用简单的累乘方法。
 * @param {Number, Integer} n 求阶乘的目标整数。
 * @see <a href=" http://www.matrix67.com/blog/article.asp?id=442">计算阶乘的另一些有趣的算法</a>,
 *  <a href=" http://www.luschny.de/math/factorial/index.html">巨牛,20多种阶乘算法的代码</a>
 */
Math.factorial_II = function(n){
     if (n===0){return 1;}
     if (!n.isPositiveInteger()){throw new Error("param error.")}
     var m=(n.isOdd()?(n+1):n)/2; // middle number.
     var r = n.isOdd()?m:m*n; //result;
     for (var i=1; i<m; i++){
         //r*=(Math.pow(m,2) - Math.pow(i,2)); // Math.pow方法求平方比两个数直接相乘的效率低很多。
         r*=(m*m - i*i);
     }
     return r;
};

/**
 * 判断当前数值对象是否是整数。
 * @return {Boolean} true,如果数值是整数,否则返回false。
 */
Number.prototype.isInteger = function(){
    return /^[+-]?\d+$/.test(this);
};

/**
 * 判断数值是否是为负数。
 * @return {Boolean} true,如果数值是负数,否则返回false。
 */
Number.prototype.isNegative = function(){
    return this<0;
};

/**
 * 判断数值是否为负整数。
 * @return {Boolean} true,如果数值为负整数,否则返回false。
 */
Number.prototype.isNegativeInteger = function(){
    return this.isNegative() && this.isInteger();
};

/**
 * 判断当前数字的值是否为奇数(定义:不能被2整除的(整)数,如1,3和5)。
 * 关于零(0)是否属于偶数,目前似乎尚无定论,这里不予理会,作为偶数处理。
 * @return {Boolean} true,如果当前值是奇数,否则,返回false。
 */
Number.prototype.isOdd = function(){
    /* Javascript整除(取模)运算:
     * 被除数为正整数时,结果为0或1;
     *       为负整数时,结果为0或-1;
     *       为0时,结果为0;
     *       为小数时,结果为正或负小数。
     */
    return this.isInteger() && ((this%2)!==0);
};

 

2007年10月16日星期二

使用jsdoc建立javascript文档


刚刚使用jsdoc实验/学习创建了javascript文档,这里写一点基本的记录。
 
jsdoc的sourceforge项目地址:http://jsdoc.sourceforge.net/
 
安装和使用
 
安装应该说比较简单,可以参考用JSDoc建立有益的JavaScript相关文档。为防止链接失效,这里拷贝一个文本备份:
 

不管因为什么原因,不给一个应用程序建立文档都不是一件好事,即使建立文档的工作一般令人厌烦。在给客户端JavaScript建立文档时尤其如此;与服务器端技术相比,客户端JavaScript通常被人们认为是前者的一个丑陋的继子。有趣的是,JavaScript文档问题的解决办法却来自一个非常相似的服务器端技术――Java。

有一个非常简洁的工具叫做Javadoc,可帮助Java开发者生成文档。这个工具还有一个JavaScript版本―― JSDoc,它可用于开发HTML文档,不仅帮助你给代码建立文档,而且有助于预防一些问题,如几个函数或对象基本上实现相同的功能,以及缺乏一般性的库知识。本教程将为你说明如何安装和使用JSDoc,以给JavaScript创建文档。

安装JSDoc
遗憾的是,安装JSDoc并不十分简单。这主要是因为它是用Perl编写的,因此除非你已经安装了Perl,否则就需要一两个额外的步骤。由于我最近在笔记本电脑上安装了Windows XP Pro,我自己就经历过这些额外的步骤。首先我必须下载和安装 ActivePerl,它是一个msi文件。A显示了一部分安装过程[图略]。(注:双击运行msi文件,使用默认设置即可)

成功安装ActivePerl后,接下来就要下载和解压JSDoc。这时,你可能会在一个DOS窗口中运行JSDoc,但如果你这样做,它可能无法运行。这是因为你缺少一个Perl软件包,如B所示[图略](注:错误提示为Can't locate HTML/Template.pm in @INC ..... )。幸运的是,你可以在同一个窗口中输入ppm调用Perl软件包管理器,如C所示[图略]

使用Perl软件包管理器,你就可以安装缺少的HTML-模板软件包,如D所示。[图略](注:点击工具栏View All packages[Ctr+1]按钮,在搜索框输入HTML-Template,选中列表中的HTML-Template项,如果它前面的图标是灰色的,右键并点击出现的唯一菜单项 Install HTML-Template安装,也可以标记然后安装)
(又注:网上有"在Dos窗口输入ppm命令,然后
PPM> install HTML-Template
PPM> quit "的做法,不过我下载的1.10.2版输入ppm就会弹出软件包管理器,命令行不可输入)

使用JSDoc

表中列出了一些你可以在JavaScript文档中使用的标签。

标签

描述

@addon

把一个函数标记为另一个函数的扩张,另一个函数的定义不在源文件中。

@argument

用大括号中的自变量类型描述一个自变量。

@author

函数/类作者的姓名。

@base

如果类是继承得来,定义提供的类名称。

@class

用来给一个类提供描述,不能用于构造器的文档中。

@constructor

描述一个类的构造器。

@deprecated

表示函数/类已被忽略。

@exception

描述函数/类产生的一个错误。

@exec

 

@extends

表示派生出当前类的另一个类。

@fileoverview

表示文档块将用于描述当前文件。这个标签应该放在其它任何标签之前。

@final

指出函数/类。

@ignore

让JSDoc忽视随后的代码。

@link

类似于@link标签,用于连接许多其它页面。

@member

定义随后的函数为提供的类名称的一个成员。

@param

用大括号中的参数类型描述一个参数。

@private

表示函数/类为私有,不应包含在生成的文档中。

@requires

表示需要另一个函数/类。

@return

描述一个函数的返回值。

@returns

描述一个函数的返回值。

@see

连接到另一个函数/类。

@throws

描述函数/类可能产生的错误。

@type

指定函数/成员的返回类型。

@version

函数/类的版本号。

除上面提供的信息外,在JSDoc.pl命令后增加-h或-help选项,就会显示在生成文档时可以使用的选项列表。G显示的是使用帮助选项的结果。

命令行进入JsDoc根目录,输入命令:
> perl jsdoc.pl test.js tes2.js
默认安装perl会设置将perl安装路径放入系统环境变量,这样你可以在任意路径中使用perl命令。如果你想在任意目录找到jsdoc.pl,可以将jsdoc安装目录设置到系统环境变量中。可以批量为javascript源代码生成文档。

 

乱码问题

默认情况下生成中文文档会出现乱码,如果你查看文档源代码就会发现,源代码本没有乱码,你只需要在JSDoc文档模板(根目录下所有的tmpl文件,有的版本可能在JSDoc子目录下)头部加上META标记并重新生成文档即可。
<meta http-equiv="content-type" content="text/html; charset=gb2312" />
其中粗体gb2312可以换成Big5,UTF-8之类,请设置为与javascript源代码文件编码格式相同
see http://caterpillar.onlyfun.net/GossipCN/AjaxGossip/JSDocBig5.html

 

集成到Editplus

点击菜单栏工具,选择配置用户工具
菜单文本:jsdoc (可随意)
命令:perl                              (如果没有设置perl的环境变量,请指定到%PERL_HOME%\bin\perl.exe )
参数:jsdoc.pl $(FileName)    (如没有设置jsdoc的环境变量,使用%JSDOC_HOME%\jsdoc.pl $(FileName) )
初始目录:$(FileDir)
 
其他设置按照个人习惯酌情设置。
 

2007年10月9日星期二

深入Javascript对象比较(一) - 概述

 

概述

 

1.1 类型

 

Javascript 数据类型分为值类型引用类型,其中值类型包括字符串实体(例如:"string" ),数值实体(例如:100 )和布尔值实体(如:true )。而其他的复杂类型都属于引用类型,例如日期型( new Date() ),正则表达式(/a/gi, new RegExp("a", "gi") )数组( [1,2,3], new Array() )函数(function(){}, new Function() )和对象({a:"a", b:100}, new Object() )。这些都是Javascript 固有的数据类型,而用户自定义类型都属于引用类型(如:var Person = function(name){this.name=name;}; ),它们都只能使用new 关键字实例化为具体对象(new Person("hotoo") )。

 

为了理解Javascript 对象的类型,我们来看一些如下代码:

// test instanceof and typeof:

var instStr = ['"string"', '100' , 'true', '/a/g' , '[1,2,3]' , 'function(){}', '{}', 'null', 'undefined' ];

var inst = ["string", 100, true, /a/g, [1 ,2,3 ], function (){}, {}, null, undefined];

var ObjsStr = ["String", "Number" , "Boolean", "RegExp" , "Array", "Function", "Date" , "Object"];

var Objs = [String, Number , Boolean, RegExp, Array, Function, Date, Object];

jsoutInst ( "instanceof" , ObjsStr, Objs , instStr, inst);

 

 

 

function jsoutInst (methodName, tsStr , ts , osStr, os ){

    document.write ("<table border='1'><tr>" );

    document.write ("<td><strong>" +methodName+ "</strong></td>" );

    for (var i= 0; i< tsStr. length; i++){

       document.write ("<td>" +tsStr[i ]+"</td>" );

    }

    document.write ("</tr>" );

    for (var i= 0; i< os. length; i++){

       document.write ("<tr><td>" +osStr[i ]+"</td>" );

       for (var j= 0; j< ts. length; j ++){

           document.write ("<td>" +(os[i ] instanceof ts[j]? "<strong>true</strong>" :"false")+"</td>" );

       }

       document.write ("</tr>" );

    }

    document.write ("</table><br />" );

}

 

var typesStr = ["string", "number" , "boolean", "array" , "function", "date", "object" , "undefined"];

jsoutType ( "typeof" , typesStr, instStr , inst);

function jsoutType (methodName, tsStr , osStr , os){

    document.write ("<table border='1'><tr>" );

    document.write ("<td><strong>" +methodName+ "</strong></td>" );

    for (var i= 0; i< tsStr. length; i++){

       document.write ("<td>" +tsStr[i ]+"</td>" );

    }

    document.write ("</tr>" );

    for (var i= 0; i< os. length; i++){

       document.write ("<tr><td>" +osStr[i ]+"</td>" );

       for (var j= 0; j< tsStr. length; j ++){

           document.write ("<td>" +(typeof os[ i] == tsStr[ j]?"<strong>true</strong>" :"false" )+"</td>" );

       }

       document.write ("</tr>" );

    }

    document.write ("</table><br />" );

}

上面的代码很简单,第一个函数判断对象实例是否是某个类的实例(instanceof ),第二个函数对比对象实例是否与某类型(typeof )相等,他们将输出两个表格,我们来对比一下(为了方便阅读,这里将说明插在相应表格下。 )。


instanceof String Number Boolean RegExp Array Function Date Object
"string" false false false false false false false false
100 false false false false false false false false
true false false false false false false false false
/a/g false false false true false false false true
[1,2,3] false false false false true false false true
function(){} false false false false false true false true
{} false false false false false false false true
null false false false false false false false false
undefined false false false false false false false false

 

通过(instanceof )这个表格可以看出,值类型( "string", 100, true 等)不是任何对象的实体(instance ),而引用类型(/a/g, [1,2,3], function(){} {} )既是本身类型的实体,又是其父类型的实体(所有类型都继承自Object 类型)。

所有通过引用类型(包括值类型的wrapperString, Number Booleannew 出来的对象,都是其对应类和其父类(这里是Object )的实例。

虽然值类型不是其对应wrapper 类的实例,但是值类型却可以直接使用其wrapper 类的属性和方法,就如同值类型是其 wrapper 类的实例一样。例如:"ABC".toLowerCase()

 

typeof string number boolean array function date object undefined
"string" true false false false false false false false
100 false true false false false false false false
true false false true false false false false false
/a/g false false false false false false true false
[1,2,3] false false false false false false true false
function(){} false false false false true false false false
{} false false false false false false true false
null false false false false false false true false
undefined false false false false false false false true

 

2007年7月7日星期六

(Javascript) HTAutoComplete:不要进行词法分析

Keep It Simple, Stupid. (保持简单,保持拙。)
 
上篇关于AutoComplete控件的思考中提到为了设计一个万能的自动完成控件,要对HTAutoComplete进行词/语法分析的想法,现在我改变主意了,那是一个自作聪明的愚蠢想法。
 
举个简单的例子,对于用户输入的如下文本,应该如何理解呢(允许输入引号本身,粗体表示)?
"a,b",c
可以有两种取词方式,
("a)(b")(c)
("a,b")(c)
我们该如何选择?
 
再复杂一点,对于引号不成对的输入:
"a,b",c"
更复杂一些:
",a,b,",c,"

换个符号对?
(a,b),c

You Can't Write Perfect Software. (你不可能写出完美的软件。)
由于允许输入特殊符号本身,和用特殊符号来分词本身就很矛盾和复杂。而一个Javascript控件,一个帮助工具,没有必要做成无所不能。提供的功能越多,所受的制约就越多。

我的建议:
1. 帮助类只帮助;
2. 让服务器去万能。

 

2007年7月5日星期四

关于AutoComplete控件的思考

一,现有控件的问题。

最近初步完成了两个自动完成的控件,最原始的HTAutoComplete暂时只完成了最基本的功能,大多细节都没有时间去考虑,而主要精力都放在HTAutoComplete2这个控件上。HTAutoComplete2是一个主要用来帮助用户在一个文本框内自动完成输入一个或多个字符/串的控件,主要用途可以是多标签的输入。

完成了beta版后,有些朋友提出了各种问题,建议和意见,都很值得思考。

其中最为典型的问题是输入特殊字符和特殊输入的问题,一般的特殊字符(如Javascript正则表达式用的特殊字符等)基本都考虑到并一定程度上得到解决。但是由于最初设计上的"硬伤"和基于"K.I.S.S."原则上的考虑,像需要输入所有字符/串的需求似乎有点困难和没有必要。

问题:
现有beta版的控件上,已知还有一个特殊字符没有彻底解决——分隔符。没有办法获得分隔符本身,因为现在的版本只取得分隔符之间的字符/串。

方案:
可以将分隔符放在引号或者其他符号对中,如果这样,就需要进行语法分析,可以使用正则表达完成,理论上不是问题。

最后,现有控件需要进一步进行抽象和解耦。


二,进一步抽象。

自动完成的本质是什么?怎么进行抽象?

自动完成控件无非就是帮助用户更方便的输入,所以写自动完成控件需要做的就是这样:
捕获需要自动完成的关键字;
与候选匹配项进行匹配;
返回并呈现匹配结果;
用户使用更方便的方法选中返回的候选匹配选项
自动完成替换,自动完成功能结束。
所以可以定义一个抽象的自动完成类,其他自动完成实例可以从这个抽象类继承,各个实例通过覆写抽象类的方法来完成自动完成工作。

2007年6月29日星期五

(javascript) 字符/串大小写反转

在封装一个模拟Google风格的多标签自动完成类之余,写了这个字符/串大小写反转方法(Google搜索了一下,暂没发现有写这个方法的),如果有用,请自用之;发现问题,请指教之。
 
var Character = function(character){ // 单个字符类
    if (character.length != 1){
        throw Error("[Error:Arguments error.\nclass:org.xianyun.Character]");
    }
    this.value = character; // private:
    this.charCode = character.charCodeAt(0); // public:readonly.
};
Character.reverseCase = function(character){ // 单个字符大小写反转,静态方法。
    var charCode = character.charCodeAt(0);
    if (charCode>=65 && charCode<=90){ // A-Z
        return String.fromCharCode(charCode + 32);
    }else if(charCode>=97 && charCode<=122){ // a-z
        return String.fromCharCode(charCode - 32);
    }else {
        return character;
    }
};
Character.prototype.reverseCase = function(){ // 单个字符大小写反转,成员方法。
    return Character.reverseCase(this.value);
};

 

String.prototype.reverse = function(){ // 字符串顺序反转。
    var s = "";
    for (var i=this.length-1; i>=0; i--){
        s += this.charAt(i);
    }
    return s;
};

String.prototype.reverseCase = function(){ // 字符串大小写反转
    var s = "";
    for (var i=0; i<this.length; i++){
        s += Character.reverseCase(this.charAt(i));
        //s += new Character(this.charAt(i)).reverseCase();
    }
    return s;
};

2007年4月13日星期五

(javascript) HTStar (class)

Demo :
http://htstars.googlepages.com

本机测试过程中,Internet Explorer 6.0, Firefox 1.0, Opera 9.0, Netscape 8.0都运行流畅,上传到Google pages里进行测试 (网络速度比较慢) 时发现,Internet Explorer 6.0的缓存机制比较有问题,虽然使用CSS控制区域的background-image,但是IE每次都会到服务器重新加载这些图片,使标星区域出现暂时的忙白现象,而Firefox,Opera和Netscape都能够运行流畅。

我将再尝试写个控制Image对象的SRC属性的类,看是否有同样的问题。


通过算法控制,已经得到一定程度的解决。

2007年3月9日星期五

(Javascript) About Boolean

from : http://www.blogjava.net/amigoxie/archive/2007/03/08/102655.html
(JavaScript的数值处理对象学习)

var booleanObj1= new Boolean( false);
var booleanObj2 = new Boolean(booleanObj1);
document.write("The booleanObj2 value is " + booleanObj2);

上面的代码,你认为结果会是什么呢?实际输出是:
The booleanObj2 value is true
是不是出乎你的意料?那么来看看下面的代码:

var booleanObj1 = new Boolean(false);
var booleanObj2 = new Boolean(booleanObj1);
// <=>
booleanObj2 = new Boolean(booleanObj1.toString());
// <=>
booleanObj2 = new Boolean("false");
// try:
booleanObj2 = new Boolean(booleanObj1. valueOf());
document.write("The booleanObj2 value is " + booleanObj2);

你理解了吗?

2007年2月3日星期六

定时自动刷新

过年了,同事们着急着买火车票,网上查询之,欲让结果固定时间后自动刷新,以便及时获得最新消息。

得如下代码:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Auto Refresh</title>
<meta name="Generator" content="EditPlus">
<meta name="Author" content="闲耘">
<meta name="Keywords" content="">
<meta name="Description" content="">
</head>
<body style="margin:0 0 0 0;">
<iframe id="piao" src="http://www.xianyun.org/" style="width:100%;height:100%;border:0px;frame-border:0px;margin-width:0px;margin-height:0px;"></iframe>

<script language="JavaScript">
<!--
var AutoRefresh = {
iframe : document.getElementById('piao'),
delay : 60*1000,
timer : null
};
AutoRefresh.refresh = function(){
this.iframe.src = this.iframe.src;
};
AutoRefresh.start = function(){
this.timer = setInterval('AutoRefresh.refresh();', this.delay);
};
AutoRefresh.stop = function(){
clearInterval(this.timer);
this.timer = null;
};

AutoRefresh.start();
//-->
</script>
</body>
</html>

2007年2月2日星期五

(javascript) Module.js


var Module = function(){
this.currentName = '';
this.previousName = '';
this.show = function(module){
if (this.currentName == module){return;};
try{
this[this.currentName].deconstruction();
}catch (e){};
try{
this[module].construction();
}catch (e){};
this.previousName = this.currentName;
this.currentName = module;
};
this.showPrevious = function(){
try{
this[this.currentName].showPreviousModule();
}catch (e){};
};
this.showNext = function(){
try{
this[this.currentName].showNextModule();
}catch (e){};
setTimeout('this.focus()', 10);
};
this.$new = function(){
try{
this[this.currentName].$new();
}catch (e){};
};
this.edit = function(){
try{
this[this.currentName].edit();
}catch (e){};
};
this.$delete = function(){
try{
this[this.currentName].$delete();
}catch (e){};
};
this.refresh = function(){
try{
this[this.currentName].refresh();
}catch (e){};
};
this.search = function(){
try{
this[this.currentName].search();
}catch (e){};
};
this.help = function(){
try{
this[this.currentName].help();
}catch (e){};
};
};

// Demo:
var HTBlog = new Module();
HTBlog.currentName = 'ArticleList';

HTBlog.ArticleList = new Object();
HTBlog.ArticleList.construction = function(){
$('moduleArticleList').style.display = '';
HTBlog.ArticleList.setStyle('on');
HTBlog.ArticleList.setButton();
HTBlog.ArticleList.setTitle();
};
HTBlog.ArticleList.deconstruction = function(){
HTBlog.ArticleList.setStyle('off');
$('moduleArticleList').style.display = 'none';
};

HTBlog.Article = new Object();
HTBlog.Article.construction = function(){
$('moduleArticle').style.display = '';
};
HTBlog.Article.deconstruction = function(){
$('moduleArticle').style.display = 'none';
};

// ...