闲耘.博客

巨人归来。

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

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

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

由阿志翻译的飞鸽传书(IPMsg中文版),其日志部分(包括“关键字”和日期格式等)同样被翻译为中文,这本身没有什么不好,但是如果你想用IPMsg官方网(日文)(或英文)推荐的日志查看器(IPmsgView),那就不行了,除非将日志转为原始格式。

这里提供一个Javascript实现的在线转换器,可以帮你解决以上问题。如果您有上万行记录,或者机器性能不够自信,请分批转换。

http://labs.xianyun.org/trans/IPMsg-logs-trans.html

这里附上源码:

function trans2(src){
	var r = src.replace(/^ 来自: /mg, " From: ");
	r = r.replace(/^ 发往: /mg, " To: ");
	r = r.replace(/^  (文件)/mg, "  (files)");
	var m=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
	var w={"一":"Mon","二":"Tue","三":"Wed","四":"Thu","五":"Fri","六":"Sat","日":"Sun"};
	return r.replace(/^ 时间: ([0-9]{4})-([0-9]{2})-([0-9]{2}) (?:星期(.)) ([0-9]{2}:[0-9]{2}:[0-9]{2})(?: ((组播))?((RSA))?((封装))?)?/mg, function($0,$1,$2,$3,$4,$5,$6,$7,$8){
		var t=($6?"(multi)":"")+($7?"(RSA)":"")+($8?"(sealed)":""); // multi, RSA, sealed
		return " at "+w[$4]+" "+m[$2|0]+" "+$3+" "+$5+" "+$1+(t?" "+t:"");
	});
}

在javaeye.com论坛上看到JSI Side 代码风格与规范,里面提到的大部分细节/技巧都很有创意,但是看到关于原始类型对象量的部分,正好我也有过“研究”,想说两句,于是发现要注册,我觉得javaeye.com还是不错的,便填写资料注册之,注册过程/之后发现激活邮件里竟然带有我输入的原始明文密码,失望1;算了,有话想说,先吐为快吧,却发现新注册用户需要在三天之后才能评论,天哪,三天,此时我都已经无语了,失望2;跑到作者javaeye博客上,发现和论坛一样,也是3天….。

下面是我想说的两点,其实本身并没什么好说的,都是关于对象比较的,更多介绍请参看我的《深入Javascript对象比较》一文:

1.    new String(”test”) != new String(”test”);
两次new String()得到两个(不同的)对象,内存地址当然不相同。

2.    if(new Boolean(false)){
        alert(”执行到”)
      }else{
        alert(”未被执行”)
      }
new Boolean()得到的也是对象,在if里相当于true,当然“执行到”,你可以试一下: if(new Boolean(false).valueOf())

前段时间不记得在哪里看到的说法:Javascript全等于(===)比普通等于(==)效率低,我当时在没有测试的情况下就隐约相信了这样的提法,因为初步想来,要多一个等号字符,要同时比较类型和值,效率低一点不是没有道理的。有了这种想法,就时时顾虑着我之前偏向于使用“强类型”的完全等于比较符是否于我不利。

今天一位同事看我代码时“抱怨”我用的完全等于太多了,并随口问了句“三个等号效率要高一些吗?”兴许说者无心,但是听着有意,在Editplus里随手写了几行测试代码,却发现结果与前文提到的说法完全相反:

var I=400000;
var d1 = new Date();
for (var i=0; i<I; i++){
 1==”1″;
}
d1 = new Date()-d1;

var d2 = new Date();
for (var i=0; i<I; i++){
 1===”1″;
}
d2 = new Date()-d2;

document.write(d1+”:”+d2);

测试结果是完全等于(===)效率要高于普通等于(==),我猜想造成这样的结果缘由如下:完全等于不进行类型转换,但是普通等于需要进行类型转换(Javascript是弱类型、但不是无类型)。

注:这里使用了两个类型不同的对象/值进行比较,如果换成同类型对象/值进行比较(如:1==1/1===1)则效率相当。

关于使用等于/完全等于的建议:
1. 如果程序对“强”类型不作要求,或者看重代码量(虽然我认为多加几个等号代码也多不到哪里去),则建议使用等于(==);
2. 否则建议使用完全等于(===)。

我过多使用完全等于号即考虑类型匹配性,如果不匹配,快速失败。

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

传统的递归算法写起来很漂亮,代码很简洁,但是没递归一次就需要更深一层的堆栈支持,可能会造成内存溢出而失败,所以递归和goto语句一样声名狼藉。

甚至《代码大全》的作者有这样一句话:如果为我工作的程序员用递归去计算阶乘,那么我宁愿换人。作者对递归的态度相当谨慎,这在静态命令式语言中显然是正确的,但是在函数式语言中,由于有尾递归优化的存在,递归反而是最自然的形式,况且我打心里认为递归更符合人类思维。(by dennis)


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

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),普通递归的效率最为底下,且需要深入堆栈。

参考:
尾递归》-百度百科
用递归计算阶乘咋不行呢?》-dennis

之前在网上收集到一个正则表达式拼接的方法/函数,一直没有注意,直到昨天用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处空数组效率多余的消耗主要在构建空数组对象上。

以前用过各种博客系统,自己也写了一个(由于朋友赞助的服务器无法继续使用,只留了在本地演示)。很长时间以来一直使用Google收购的Blogger.com提供的服务,并绑定到自己的blog.xianyun.org域名下,由于国内过于和谐的缘故,Google DNS服务器也无法访问了,就另图出路。久仰WordPress大名,特来瞻仰。

 大概是系统的原因,以前在Blogger.com的日志暂时无法导入过来,实在是遗憾。

昨天申请的帐号,今天写第一篇,以自贺乔迁之喜。最近对数学再返无穷兴趣,在一个数学方面极好的博客上读得关于阶乘计算的好文,就把其中一个较易实现的改进算法发布如下(Javascript实现),初来乍到,不知道怎么发布源代码,请见谅:

/**
 * 求阶乘的改进算法。
 * @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.isNegativeInteger()){throw new Error(”param error.”)}
    if (n===0){return 1;}
    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));
    }
    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);
};

刚刚使用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)

其他设置按照个人习惯酌情设置。

概述

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, NumberBooleannew 出来的对象,都是其对应类和其父类(这里是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

 

更多内容请看这里:
深入Javascript对象比较二 — 实现代码

深入Javascript对象比较之三 — 测试代码

深入Javascript对象比较之四 — 测试结果; 之五 — 结束语

深入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;
};