存档

2010年1月 的存档

[转]JavaScript的陷阱

2010年1月17日 14:24 by webphoenix

  这本来是翻译Estelle Weyl的《15 JavaScript Gotchas》,里面介绍的都是在JavaScript编程实践中平时容易出错或需要注意的地方,并提供避开这些陷阱的方法,总体上讲,就是在认清事物本质的基础样要坚持好的编程习惯,其实这就是Douglas Crockford很久以前提出的JavaScript风格要素问题了,有些内容直接是相同的,具体请看《Javascript风格要素(1)》《Javascript风格要素(2)》。在翻译的过程中,我又看到了贤安去年翻译的《JavaScript的9个陷阱及评点》,其内容又有些交叉在一起,所以我就在现有翻译的基础上做了一个简单的拼合,并依据自己的理解增加了一些注释和解释。

  1. 区分大小写:变量名、属性和方法全部都区分大小写
  2. 不匹配的引号、圆括号或花括号将抛出错误
  3. 条件语句:3个常见陷阱
  4. 换行:一直用分号结束语句来避免常见的换行问题
  5. 标点法:在对象声明的尾部逗号将导致出错
  6. HTML id 冲突
  7. 变量作用域:全局变量对局部变量
  8. 函数重载:当重载不存在时,覆盖函数
  9. 区分string.replace()函数不是全局的
  10. parseInt应该包含两个参数
  11. “this”和绑定问题
  12. 为参数设置默认值,以免你遗漏它们
  13. for each循环是用于对象而不是数组
  14. switch语句需要点技巧
  15. 如果你要检查null,应该先检查undefined
  16. 时间处理陷阱

区分大小写

  变量名和函数名都是区分大小写的。就像配错的引号一样,这些大家都知道。但是,由于错误是不作声的,所以这是一个提醒。为自己选择一个命名规则,并坚持它。而且,记住JavaScript中的原生函数和CSS属性都是骆驼拼写法(camelCase)

getElementById('myId') != getElementByID('myId'); //它应该是“Id”而不是“ID”
getElementById('myId') != getElementById('myID'); // “Id”也不等于“ID” 

document.getElementById('myId').style.Color; //返回 "undefined"

不匹配的引号、圆括号或花括号

  避免陷入不匹配的引号、圆括号或花括号陷阱的最好方式是编码时一直同时写出打开和关闭这两个元素符号,然后在其中间加入代码。开始:

var myString = ""; //在输入字符串值之前写入这对引号
function myFunction(){
 if(){//关闭每个打开的括弧
 }
}
//统计所有的左括号和右括号数量,并且确保它们相等
alert(parseInt(var1)*(parseInt(var2)+parseInt(var3)));
//关闭每个打开的圆括号

  每当你打开一个元素,请关闭它。当你添加了关闭圆括号后,你再把函数的参数放进圆括号中。如果有一串圆括号,统计所有打开的圆括号和所有关闭的圆括号,并且确保这两个数字相等。

条件语句(3个陷阱)

  1. 所有的条件语句都必须位于圆括号中。执行语句主体不管是一句还是多句都强烈建议用花括号包围起来,这样能避免很多因修改或嵌套而产生的潜在错误。
    if(var1 == var2){
    //statement
    }
  2. 不要犯无意地使用赋值运算符的错误:把第二个参数的值赋给第一个参数。因为它是一个逻辑问题,它将一直返回true且不会报错。
    if(var1 = var2){} // 返回true。把var2赋值给var1
  3. JavaScript是弱类型,除了在switch语句中。当JavaScript在case比较时,它是非弱类型。
    var myVar = 5;
    if(myVar == '5'){ //返回true,因为JavaScript是弱类型
     alert("hi");  //这个alert将执行,因为JavaScript通常不在意数据类型
    }
    switch(myVar){
     case '5':
     alert("hi"); //这个alert将不会执行,因为数据类型不匹配
    }
    

换行

  当心JavaScript中的硬换行。换行被解释为表示行结束的分号。即使在字符串中,如果在引号中包括了一个硬换行,那么你会得到一个解析错误(未结束的字符串)。

var bad  = '<ul id="myId">
  <li>some text</li>
  <li>more text</li>
  </ul>'; // 未结束的字符串错误 

var good = '<ul id="myId">' +
 '<li>some text</li>' +
 '<li>more text</li>' +
 '</ul>'; // 正确

  前面讨论过的换行被解释为分号的规则并不适用于控制结构这种情况:条件语句关闭圆括号后的换行并不是给其一个分号。

  一直使用分号和圆括号,那么你不会因换行而出错,你的代码易于阅读,且除了那些不使用分号的怪异源码外你会少一些顾虑:所以当移动代码且最终导致两个语句在一行时,你无需担心第一个语句是否正确结束。

多余的逗号

  在任何JavaScript对象定义中,最后一个属性决不能以一个逗号结尾。Firefox不会出错,而IE会报语法错误。

var theObj = {
        city : "Boston",
        state : "MA",//IE6和IE7中有“缺少标识符、字符串或数字”的错误,IE8 beta2修正了它
  }

HTML id 冲突

  JavaScript DOM绑定(JavaScript DOM bindings)允许通过HTML id索引。在JavaScript中函数和属性共享同一个名字空间。所以,当在HTML中的一个id和函数或属性有相同的名字时,你会得到难以跟踪的逻辑错误。然而这更多是一个CSS最佳实践的问题,当你不能解决你的JavaScript问题时,想起它是很重要的。

<ul>
<li id="length">1</li>
<li id="thisLength">2</li>
<li id="thatLength">3</li>
</ul>
<script>
var listitems = document.getElementsByTagName('li');
var liCount = listitems.length; //IE下返回的是<li id="length">1</li>这个节点而不是所有<li的数量> 

var thisLength = document.getElementById('thisLength');
    thatLength = document.getElementById('thatLength');
    //IE下会出现“对象不支持此属性和方法”的错误,IE8 beta2下首次加载页面会出错,刷新页面则不会
    //在IE中thisLength和thatLength直接表示以其为id值的DOM节点,
    //所以赋值时会出错,当有var声明时,IE会把其当着变量,这个时候就正常了。
</script>

  如果你要标记(X)HTML,绝不要使用JavaScript方法或属性名作为id的值。并且,当你写JavaScript时,避免使用 (X)HTML中的id值作为变量名。

变量作用域

  JavaScript中的许多问题都来自于变量作用域:要么认为局部变量是全局的,要么用函数中的局部变量覆盖了全局变量。为了避免这些问题,最佳方案是根本没有任何全局变量。但是,如果你有一堆,那么你应该知道这些陷阱。

  不用var关键字声明的变量是全局的。记住使用var关键字声明变量,防止变量具有全局作用域。在下面例子中,在函数中声明的变量具有全局变量,因为没有使用var关键字声明:

anonymousFuntion1 = function(){
 globalvar = 'global scope'; //全局声明,因为“var”遗漏了
 return localvar;
}();
alert(globalvar); //弹出“global scope”,因为函数中的变量是全局声明 

anonymousFuntion2 = function(){
 var localvar = 'local scope'; //使用“var”局部声明
 return localvar;
}();
alert(localvar); //错误 “localvar未定义”。没有全局定义localvar

  作为参数引进到函数的变量名是局部的。如果参数名也是一个全局变量的名字,像参数变量一样有局部作用域,这没有冲突。如果你想在函数中改变一个全局变量,这个函数有一个参数复制于这个全局变量名,记住所有全局变脸都是window对象的属性。

var myscope = "global";
function showScope(myscope){
 return myscope; //局部作用域,即使有一个相同名字的全局变量
}
alert(showScope('local'));

function globalScope(myscope){
 myscope = window.myscope; //全局作用域
 return myscope;
}
alert(globalScope('local'));

  你甚至可以在循环中声明变量:

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

覆盖函数/重载函数

  当你不止一次的声明一个函数时,这个函数的最后一次声明将覆盖掉该函数的所有前面版本且不会抛出任何错误或警告。这不同于其他的编程语言,像Java,你能用相同的名字有多重函数,只要它们有不同的参数:调用函数重载。在JavaScript中没有重载。这使得不能在代码中使用JavaScript核心部分的名字极其重要。也要当心包含的多个JavaScript文件,像一个包含的脚本文件可能覆盖另一个脚本文件中的函数。请使用匿名函数和名字空间。

(function(){
 // creation of my namespace 创建我的名字空间
 if(!window.MYNAMESPACE) {
  window['MYNAMESPACE'] = {};
 }
 //如果名字空间不存在,就创建它
 //这个函数仅能在匿名函数中访问
 function myFunction(var1, var2){
  //内部的函数代码在这儿
 }
 // 把内部函数连接到名字空间上,使它通过使用名字空间能访问匿名函数的外面
 window['MYNAMESPACE']['myFunction'] = myFunction;
})(); // 圆括号 = 立即执行
// 包含所有代码的圆括号使函数匿名

  这个例子正式为了实现解决上一个陷阱“变量作用域”的最佳方案。匿名函数详细内容请看《Javascript的匿名函数》YUI整个库只有YAHOO和YAHOO_config两个全局变量,它正是大量应用匿名函数和命名空间的方法来实现,具体请看《Javascript的一种模块模式》

字符串替换

  一个常见错误是假设字符串替换方法的行为会对所有可能匹配都产生影响。实际上,JavaScript字符串替换只改变了第一次发生的地方。为了替换所有发生的地方,你需要设置全局标识。同时需要记住String.replace()的第一个参数是一个正则表达式。

var myString = "this is my string";
myString = myString.replace("","%20"); // "this%20is my string"
myString = myString.replace(/ /,"%20"); // "this%20is my string"
myString = myString.replace(/ /g,"%20"); // "this%20is%20my%20string"

parseInt

  在JavaScript得到整数的最常见错误是假设parseInt返回的整数是基于10进制的。别忘记第二个参数基数,它能是从2到36之间的任何值。为了确保你不会弄错,请一直包含第二个参数。

parseInt('09/10/08'); //0
parseInt('09/10/08',10); //9, 它最可能是你想从一个日期中得到的值

  如果parseInt没有提供第二个参数,则前缀为 ‘0x’ 的字符串被当作十六进制,前缀为 ‘0′ 的字符串被当作八进制。所有其它字符串都被当作是十进制的。如果 numString 的前缀不能解释为整数,则返回 NaN(而不是数字)。

‘this’

  另一个常见的错误是忘记使用“this”。在JavaScript对象中定义的函数访问这个对象的属性,但没有使用引用标识符“this”。例如,下面是错误的:

function myFunction() {
 var myObject = {
  objProperty: "some text",
  objMethod: function() {
   alert(objProperty);
  }
 };
 myObject.objMethod();
} 

function myFunction() {
 var myObject = {
  objProperty: "some text",
  objMethod: function() {
   alert(this.objProperty);
  }
 };
 myObject.objMethod();
}

  有一篇A List Apart文章用通俗易懂的英文表达了this绑定的问题。

  对this使用最大的陷阱是this在使用过程中其引用会发生改变:

<input type="button" value="Gotcha!" id="MyButton">
<script>
var MyObject = function () {
    this.alertMessage = "Javascript rules";
    this.ClickHandler = function() {
        alert(this.alertMessage );
        //返回结果不是”JavaScript rules”,执行MyObject.ClickHandler时,
        //this的引用实际上指向的是document.getElementById("theText")的引用
  }
}();
document.getElementById(”theText”).onclick =  MyObject.ClickHandler
</script>

  其解决方案是:

var MyObject = function () {
    var self = this;
    this.alertMessage = “Javascript rules”;
    this.OnClick = function() {
        alert(self.value);
    }
}();

  类似问题的更多细节和解决方案请看《JavaScript作用域的问题》

遗漏的参数

  当给函数增加一个参数时,一个常见的错误是忘记更新这个函数的所有调用。如果你需要在已经被调用的函数中增加一个参数来处理一个特殊情况下的调用,请给这个函数中的这个参数设置默认值,以防万一在众多脚本中的众多调用中的一个忘记更新。

function addressFunction(address, city, state, country){
 country = country || “US”; //如果没有传入country,假设 “US”
 span>//剩下代码
}

  你也能通过获取arguments来解决。但是在这篇文章我们的注意力在陷阱上。同时在《Javascript风格要素(2)》也介绍了||巧妙应用。

for关键字

  在JavaScript中关键字for有两种使用方式,一个是for语句,一个是for/in语句。for/in语句将遍历所有的对象属性(attribute),包括方法和属性(property)。决不能使用for/in来遍历数组:仅在当需要遍历对象属性和方法时才使用for/in。

  1. for(var myVar in myObject)语句用一个指定变量无任何规律地遍历对象的所有属性。如果for/in循环的主体删除了一个还没有枚举出的属性,那么该属性就不在枚举。如果循环主体定义了新属性,那么循环是否枚举该属性则是由JavaScript的实现决定。
  2. for(var 1=0; i < myArray.length; i++)语句会遍历完一个数组的所有元素。

  为了解决这个问题,大体上你可以对对象使用 for … in,对数组使用for循环:

listItems = document.getElementsByTagName('li');
for (var listitem in listItems){
 //这里将遍历这个对象的所有属性和方法,包括原生的方法和属性,但不遍历这个数组:出错了!
}
//因为你要循环的是数组对象,所用for循环
for ( var i = 0; i < listItems.length; i++) {
 //这是真正你想要的
}

  对象的有些属性以相同的方式标记成只读的、永久的或不可列举的,这些属性for/in无法枚举。实际上,for/in循环

会遍历所有对象的所有可能属性,包括函数和原型中的属性。所有修改原型属性可能对for/in循环带来致命的危害,所以需要采用hasOwnProperty和typeof做一些必要的过滤,最好是用for来代替for/in。

switch语句

Estelle Weyl写了一篇switch statement quirks,其要点是:

  1. 没有数据类型转换
  2. 一个匹配,所有的表达式都将执行直到后面的break或return语句执行
  3. 你可以对一个单独语句块使用多个case从句

undefined ≠ null

  null是一个对象,undefined是一个属性、方法或变量。存在null是因为对象被定义。如果对象没有被定义,而测试它是否是null,但因为没有被定义,它无法测试到,而且会抛出错误。

if(myObject !== null  && typeof(myObject) !== 'undefined') {
 //如果myObject是undefined,它不能测试是否为null,而且还会抛出错误
}
if(typeof(myObject) !== 'undefined' && myObject !== null) {
  //处理myObject的代码
}

Harish Mallipeddi对undefined和null有一个说明。

事件处理陷阱

  刚接触事件处理时最常见的写法就是类似:

window.onclick = MyOnClickMethod

  这种做法不仅非常容易出现后面的window.onclick事件覆盖掉前面的事件,还可能导致大名顶顶的IE内存泄露问题。为了解决类似问题,4年前Simon Willison就写出了很流行的addLoadEvent():

function addLoadEvent(func) {
 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 }else {
  window.onload = function() {
   oldonload();
   unc();
  }
 }
}
addEvent(window,'load',func1,false);
addEvent(window,'load',func2,false);
addEvent(window,'load',func3,false);

  当然在JavaScript库盛行的现在,使用封装好的事件处理机制是一个很好的选择,比如在YUI中就可以这样写:

YAHOO.util.Event.addListener(window, "click", MyOnClickMethod);

  本文转自 http://dancewithnet.com/ 作者:Kaven

技术文章 , ,

[转]Javascript风格要素

2010年1月17日 13:59 by webphoenix

  程序设计是困难的,其核心是管理的复杂性。计算机程序是人类做出的最复杂的东西。质量是不可靠的且隐蔽的。

  好的体系架构是必需给程序足够的结构使其健壮而不会陷入混乱的泥淖,但我们表达一个程序细节的方式是同等重要的。一个程序的本质会被不良的编码所隐藏。只有当一个程序的表达清晰时,我们才能有希望正确的推理出它的效率、安全和正确性。

  William Strunk的《风格要素》(The Elements of Style)是关于文学风格的经典著作,它是一本关于英文写作的薄手册,在用法、组织和形式上提出忠告。风格的理念应用于编程,在1972年Kreitzberg和Shneiderman的《FORTRAN风格要素》(The Elements of FORTRAN Style)中是不成功的,但在1978年的Kernighan和Plauger的《编程风格要素》(The Elements of Programming Style)中是非常成功的:

好的编程不能通过笼统的说教。学习编程的好方法是一次又一次的思考:真正的编程是如何通过一些良好实践的原则和一点常识来进行改进的。

  他们从他们批评和改进的其他的编程教科书中筛选程序。

  当我们在这里谈论风格时,我们谈论的不是潮流或者时尚,也不是CSS、布局惯例或排版。我们正在谈论的是能真正提高代码价值的表达式的永恒品质。对于公司来说,他们的评估和他们的代码是息息相关的,风格应该是一个至关重要的受关注内容。

  我们使用许多编程语言,但在某一方面,Javascript是最重要的,它是浏览器的语言。当人们访问我们的站点时,他们将邀请我们的Javascript程序在他们的机器中执行。我们有义务使那些程序执行好。

  没有好的关于Javascript编程的课本。在网页使用Javascript的大多数人学习它是通过从糟糕的书、糟糕的站点和糟糕的工具中复制相当糟糕的例子。我们这里有极好的Javascript程序员社区,但我们依旧能从较好的风格实践中获益。

  为了证明这个问题,我将从公共网站中抽取一些程序,展示它们如何能被改进。这并不是我有意为难任何人。我的意图仅是通过例子展示风格的价值。我不会保留任何秘密:我展示给你的是我们已经传送给世界上的每一个人。

淘汰过时结构

  下面的例子是2005-09-19摘自www.yahoo.com

<script language=javascript><!--
     lck='',
     sss=1127143538,
     ylp='p.gif?t=1127143538&_ylp=A0Je5ipy2C5D54AAwVX1cSkA',
     _lcs='';
--></script> 

  这个脚本块用了language属性。这个特性是微软为了支持VBScript引入的。然而Netscape采用它是为了支持非标准偏差。W3C不采取这个language属性,倾向使用MIME类型的type属性取代。不幸的是,MIME类型未得到标准化,所以它有时是”text/javascript”、”application/ecmascript”或其他。幸运的是所有的浏览器都选择Javascript作为默认的编程语言,所以简单的写<script>是最好的。它最小,且工作在最多的浏览器。在脚本中使用HTML的注释的时间要回溯到Netscape Navigator和Netscape Navigator 2的兼容问题上来。后者引入了<script>标签。然而,前者的用户能像文本一样看到脚本,因为在HTML惯例中不能识别的标签被忽略。<!–注释hack在Netscape Navigator 3出现的时候是需要的,现在它不被需要了。它是丑陋的且浪费空间的。

  逗号运算符像Javascript语法的大多数一样从C语言中借用。逗号运算符获得两个值,且返回第二个。在语言的定义中它的存在易于掩盖一定的编码错误,编译器也易于对一些错误视而不见。最好避免逗号运算符,并以分号运算符代替。

  在这个案例里,我们定义了一些全局变量。当指定一个未知(匿名)的变量时,Javascript会创建一个新的全局变量来替代产生的错误。事后看来,这是一个错误。即使当他们是一个标准错误,这是避免错误的最好办法。我们应该明确的声明变量。它花费我们四个字符,但是它正是要做的正确的事。

<script>
var lck = '3ek6b0i2he2a5eh3/o',
    sss = 1126894256,
    ylp = 'p.gif?t=1126894256&_ylp=A0Je5iOwCitDw2YBX331cSkA',
    _lcs = '94040';
</script>

  从上面我们能得出这样的原则:淘汰过时结构

结构化语句要始终使用区块

  下面这个例子是一个cookie类构造器。它创建了一个有get和set方法的对象。

function yg_cookie() {
    this.get = function (n) {
        var s,
            e,
            v = '',
            c = ' ' + document.cookie + ';';
        if ((s = c.indexOf((' ' + n + '='))) >= 0) {
            if ((e = c.indexOf(';',s)) == -1)
                e = c.length;
            s += n.length + 2;
            v = unescape(c.substring(s, e));
        }
        return (v);
    }
    this.set = function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    }
}
var _yc = new yg_cookie();

  Javascript的if语句和C语言的相似:它能执行一个语句或一个区块。关于用语句的问题是一个普通的错误非常难以探测。最好把

if ((e = c.indexOf(';', s)) == -1)
    e = c.length;

写成

if ((e = c.indexOf(';', s)) == -1) {
    e = c.length;
}

  区块的用处是避免了下面的情况:

if ((e = c.indexOf(';', s)) == -1)
    e = c.length;
    s += n.length + 2;

  它将出现当indexOf返回-1是,s是只被增加,但这不是实际情形。像这样的bug被发现代价是非常高的,但是可以通过一直使用大括号声明结构来廉价的避免。

避免赋值表达式

  Javascript从C继承的另一个坏习惯是赋值表达式。它出现在流线型代码中,但它能使控制流更难以理解。如果我们从他们的使用中分离了s和e的计算,get方法会变得更清晰。

this.get = function (n) {
    var v = '',
        c = ' ' + document.cookie + ';',
        s = c.indexOf((' ' + n + '=')),
        e = c.indexOf(';', s);
    if (s >= 0) {
        if (e == -1) {
            e = c.length;
        }
        s += n.length + 2;
        v = unescape(c.substring(s, e));
    }
    return (v);
}

  我们现在能看到当s被计算时,在indexOf参数两边有多余的括号。(在return语句中也有非必须的括号。)但是更重要的是,能容易的看出if (e == -1)的目的是什么:如果cookie中末尾的分号不存在,假定cookie结束在字符串的末端。然而,当我们计算c时,我们在cookie中加入了一个分号,它保证了预料的if条件将绝不会发生。所以我们能移除if。

使用对象参数

  当一个函数被指定一个值,像在this.get = function (n) { … }中,它应该以一个分号来结束所有的赋值语句。

function yg_cookie() {
    this.get = function (n) {
        var v = '',
            c = ' ' + document.cookie + ';',
            s = c.indexOf((' ' + n + '='));
        if (s >= 0) {
            s += n.length + 2;
            v = unescape(c.substring(s, c.indexOf(';', s)));
        }
        return v;
    };
    this.set = function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    };
}
var _yc = new yg_cookie();

  最后,我们看到yg_cookie是一个能产生一个无状态对象的构造器。我们一点也不需要构造器函数。我们能简单创建一个空对象,通过指派方法的方式来增加它。

var _yc = new Object();
_yc.get = function (n) {
    var v = '',
        c = ' ' + document.cookie + ';',
        s = c.indexOf((' ' + n + '='));
    if (s >= 0) {
        s += n.length + 2;
        v = unescape(c.substring(s, c.indexOf(';', s)));
    }
    return v;
};
_yc.set = function (n,v,e) {
    document.cookie = n + "=" + escape(v) +
        ";expires=" + (new Date(e * 1000)).toGMTString() +
        ";path=/" + ";domain=www.yahoo.com";
};

  如果我们不需要支持Netscape3和IE4,我们能通过对象字面量来实现的更加优雅。

var _yc = {
    get: function (n) {
        var v = '',
            c = ' ' + document.cookie + ';',
            s = c.indexOf((' ' + n + '='));
        if (s >= 0) {
            s += n.length + 2;
            v = unescape(c.substring(s, c.indexOf(';', s)));
        }
        return v;
    },
    set: function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    }
};

使用通用库

  此时对于处理cookies我们有几种方法。我们发现下一个事情是令人惊奇的,它是没有利用我们定义的方法的cookies处理方式代码。

var b,
    l = '',
    n = '0',
    y;
y = ' ' + document.cookie + ';';
if ((b = y.indexOf(' Y=v')) >= 0) {
    y = y.substring(b, y.indexOf(';', b)) + '&';
    if ((b = y.indexOf('l=')) >= 0) {
        l = y.substring(b + 2, y.indexOf('&', b));
        if ((b = y.indexOf('n=')) >= 0)
            n = y.substring(b + 2, y.indexOf('&', b));
    }
}

  它甚至复制了我们早前看到的同样技术。很有可能两块代码都改写自同一个有缺点的原稿。我们可以利用我们最近的工作来改进它:

var l = '',
    n = '0',
    y = _yc.get('Y') + '&',
    b = y.indexOf('l=');
if (b >= 0) {
    l = y.substring(b + 2, y.indexOf('&', b));
    b = y.indexOf('n=');
    if (b >= 0) {
        n = y.substring(b + 2, y.indexOf('&', b));
    }
}

  代码重用是软件工程的圣杯。我们可以想象通过最先进的技术避免大量的必需的手工工作来得到高效率。这里我们发现一种失败,使用一种方法需要在相邻需要它的地方进行定义。

  软件的体系结构倾向于反映生产他们的组织结构。在这种情况下,我们看到一个组织由于缺乏流程的连通意识而导致的明显低效的证据。风格的应用是吹毛求疵的,因为如果我们理解这几条是什么才有可能正确的一起使用这几条。

原文:Douglas CrockfordThe Elements of JavaScript Style Part One

  我们使用习惯用法可以使我们的意图更加的清晰和简洁。

使用==时,当心强制转换

  考虑下面函数:

function gw(f) {
 if (d.w.sv.checked == true) {
  zv = 'on';
 } else {
  zv = 'off';
 }
 procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
 escape(f.p.value) + "&sv=" + zv);
 return false;
}

  ==运算符不应该被用着和true比较值,因为它要执行强制转换。如果我们想确定d.w.sv.checked是否是布尔值
true,我们必须用===运算符。如果我们仅在意一个值是真实存在的不是假的,最好不要用相等运算符。

  例如,由于强制转换:1 == true是真,1 === true是假。==运算符隐藏了类型错误。

使用?:运算符选择两值之一

  if语句通常被用来从两个值中选择一个。这应该是三元操作符?:最适合的。

zv = d.w.sv.checked ? 'on' : 'off';

绝不使用隐含的全局变量

  变量zv不是作为一个var或函数参数来声明的,所以它是一个隐式的全局变量。如果这个页面的另一个函数使用了同样名字的全局变量,则可能得到一个失败的结果。这样的臭虫(bug)是非常难以发现,却很容易避免。这个例子中,我们既可以声明zv为一个var,也可以发现它仅仅被使用过一次而整个去掉它。

function gw(f) {
 procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
 escape(f.p.value) + "&sv=" + d.w.sv.checked ? 'on' : 'off');
 return false;
}

绝不使用?:运算符选择两种行为之一

  我们常质疑那些返回一个常量的函数,但这有时是在浏览器环境下所必需的。

  下面我们看一个不正确使用?:运算符的例子。它常被用于在两个任务间选择。

function u(o, z) {
 var em = o.id.substring(1);
 var p = d.getElementById('e' + em);
 if (p) {
  (z == 0) ? p.style.backgroundColor = '#fff' :
  p.style.backgroundColor = '#989898';
 }
 p = d.getElementById('e' + (em - 1));
 if (p) {
  (z == 0) ? p.style.backgroundColor = '#fff' :
  p.style.backgroundColor = '#989898';
 }
}

Z的判断是模糊不清的。Z正好等于0时我们选择#fff颜色,那么,Z不等于时?如上所述似乎指明的是前者,但它实际上是后者。在这个例子中幸运的是,我们大概想要的就是后者,所以它不是技术上错误(这次)。但是在文体上只糟糕的。

  我们可以用if代替?:,但碰巧的是这些值对应的是同一个左值(lvalue),所以我们无需if就可以改正这个错误。

function u(o, z) {
 var em = o.id.substring(1),
 p = d.getElementById('e' + em);
 if (p) {
  p.style.backgroundColor = z ? '#fff' : '#989898';
 }
 p = d.getElementById('e' + (em - 1));
 if (p) {
  p.style.backgroundColor = z ? '#fff' : '#989898';
 }
}

使用||运算符指定一个默认值

  事件处理程序依赖于浏览器。理想情况下,应用程序应该通过公共库隔绝对浏览器的依赖。当没有这样的库时,就会有些函数发生如下情况:

function md(e) {
 (window.event) ? ev = window.event : ev = e;
 (ev.target) ? sr = ev.target : sr = ev.srcElement;
 if (ev && sr && sr.id == "fp" || sr.id == "sb") st = 1;
 if (sr.className.indexOf("pllist") < 0 && sr.className != "more" &&
  sr.className != "plinkc" && sr.tagName != "scrollbar " &&
  _toClose && _toCloseNorgie) {
  d.getElementById(_toClose).innerHTML = "";
  _toClose = "";
  _toCloseNorgie.parentNode.className = '';
  _toCloseNorgie = '';
 }
}

  一些浏览器把事件对象作为一个参数传给事件管理程序。微软选用把事件对象放入到一个全局的事件变量中。在Javascript中,全局变量是全局对象的成员。在浏览器中,全局对象始终包含一个window对象成员,其值是全局对象。当测试一个变量是否存在时,通过window访问全局变量是避免未定义变量错误的一种方法。无论如何,做这样的测试不应该是必要的。

  我们能通过问它是否是另外一种,来代替首先判断是否是微软事件。

ev = e || event;

  我们用||(默认)运算符。如果e是真,我们将有它的值,但是如果e是假,则我们将用event

  在下一个语句,我们又用||运算符去确定sr是哪个值。

  我们应该用var去声明evsr来避免全局冲突:

function md(e) {
 var ev = e || event,
  sr = ev.target || ev.srcElement;
 if (sr && (sr.id == 'fp' || sr.id == 'sb')) {
  st = 1;
 }
 if (sr.className.indexOf('pllist') < 0 && sr.className != 'more' &&
 sr.className != 'plinkc' && sr.tagName != 'scrollbar ' &&
 _toClose && _toCloseNorgie) {
  d.getElementById(_toClose).innerHTML = '';
  _toClose = '';
  _toCloseNorgie.parentNode.className = '';
  _toCloseNorgie = '';
 }
}

全局变量是魔鬼

  下面我们看到另一个时间处理程序。正如你所料,它重复像前面一样破坏风格。

function kd(e) {
 (window.event) ? ev = window.event : ev = e;
 (ev.target) ? el = ev.target : el = ev.srcElement;
 if (ev && el) {
  code = ev.keyCode;
  id = el.id;
 } else {
  return;
 }
 ctn = lt.id.substring(1);
 if (code == 13) {
  return;
 } else if ((code == 191 || code == 222) && id != 'fp') {
  _ffs = 1;
  gk = 0;
 } else if ((code < 31 || code > 41) &&
 (code < 16 || code > 18) && code != 9 && code != 8 ) {
  gk = 1;
 } else {
  gk = 0;
 }
 if (!_ffs && (id == 'fp' || id == 'st')) {
  if (code == 9) {
   if (box.value == '' || (box.value != '' && (at == 1 || ev.shiftKey))) {
    mt(ctn);
   } else if (id == 'st' && box.value != '' && at == 0) {
    at = 1;
    mt(ctn);
   }
  } else if (id == 'fp' && gk == 0 &&  (box.value == '' && st == 0)
    && !ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
    d.getElementById('mk').focus();
    d.getElementById('mk').blur();
  } else if (gk == 1) {
  at = 0;
 }
 } else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
   (id == 'm6' && !ev.shiftKey && code == 9)){
    d.getElementById('mk').focus();
   } else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey && !ev.altKey){
    box.value = '';
    box.focus();
  }
}
function mt(ctn) {
 if ((ev && !ev.ctrlKey && !ev.altKey) || !ev) {
  if (ev.shiftKey){
   nextTab = parseInt(ctn) - 1;
  } else {
   nextTab = parseInt(ctn) + 1;
  }
  if (nextTab == 0) {
   d.getElementById('mk').focus();
  } else if (nextTab < 8 ) {
   t(d.getElementById('v' + nextTab));
  } else {
   return;
  }
 }
}

  有意思的是它有一个同伴函数mt,它仅被kd调用。mt被传给一个参数ctn,但kdmt之间的通讯大部分是通过全局变量。

使用内部函数避免全局变量

  我们可以通过增加传递给mt的参数数量来除掉所有的全局变量。但代替方案,我们将使mt变成kd的内部函数。作为一个内部函数,mt能访问kd的所有变量。

function kd(e) {
    var ev = e || event,
        el = ev.target || ev.srcElement,
        cnt,
        code = ev.keyCode,
        gk,
        id = el.id,
        ctn = lt.id.substring(1);

    function mt() {
        var nextTab;
        if (!ev.ctrlKey && !ev.altKey) {
            nextTab = parseInt(ctn) + ev.shiftKey ? -1 : 1;
            if (!nextTab) {
                d.getElementById('mk').focus();
            } else if (nextTab < 8 ) {
                t(d.getElementById('v' + nextTab));
            }
        }
    }

    if (code == 13) {
        return;
    } else if ((code == 191 || code == 222) && id != 'fp') {
        _ffs = 1;
        gk = 0;
    } else if ((code < 31 || code > 41) &&
            (code < 16 || code > 18) && code != 9 && code != 8 ) {
        gk = 1;
    } else {
        gk = 0;
    }
    if (!_ffs && (id == 'fp' || id == 'st')) {
        if (code == 9) {
            if (box.value == '' ||
                    (box.value != '' && (at == 1 || ev.shiftKey))) {
                mt();
            } else if (id == 'st' && box.value != '' && at == 0) {
                at = 1;
                mt();
            }
        } else if (id == 'fp' && gk == 0 && (box.value == '' && st == 0) &&
                !ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
            d.getElementById('mk').focus();
            d.getElementById('mk').blur();
        } else if (gk == 1) {
            at = 0;
        }
    } else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
            (id == 'm6' && !ev.shiftKey && code == 9)){
        d.getElementById('mk').focus();
    } else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey &&
            !ev.altKey) {
        box.value = '';
        box.focus();
    }
}

  在函数kd中,从两个地方调用函数mt。通过使它成为一个内部函数,我们能有效的减少kd所用到的全局变量的数目,这将降低了干扰其他组件的可能性。kd依旧是一个烂摊子,但它现在不是一无是处的烂摊子。

原文:Douglas CrockfordThe Elements of JavaScript Style Part Two: Idioms

本文转自 Part One
Part Two 作者:Kaven

技术文章 ,

[转]Javascript的匿名函数

2010年1月17日 13:38 by webphoenix

一、什么是匿名函数?

  在Javascript定义一个函数一般有如下三种方式:
  函数关键字(function)语句:

function fnMethodName(x){alert(x);}

  函数字面量(Function Literals):

var fnMethodName = function(x){alert(x);}

  Function()构造函数:

var fnMethodName = new Function('x','alert(x);')

  上面三种方法定义了同一个方法函数fnMethodName,第1种就是最常用的方法,后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。实际上,相当多的语言都有匿名函数。
二、函数字面量和Function()构造函数的区别

  虽然函数字面量是一个匿名函数,但语法允许为其指定任意一个函数名,当写递归函数时可以调用它自己,使用Function()构造函数则不行。

var f = function fact(x) {
  if (x < = 1) return 1;
  else return x*fact(x-1);
};

  Function()构造函数允许运行时Javascript代码动态的创建和编译。在这个方式上它类似全局函数eval()。
  Function()构造函数每次执行时都解析函数主体,并创建一个新的函数对象。所以当在一个循环或者频繁执行的函数中调用Function()构造函数的效率是非常低的。相反,函数字面量却不是每次遇到都重新编译的。
  用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。

var y = "global";
function constructFunction() {
    var y = "local";
    return new Function("return y");  //  无法获取局部变量
}
alert(constructFunction()());  // 输出 "global"

  和函数关键字定义相比Function()构造器有自己的特点且要难以使用的多,所以这项技术通常很少使用。而函数字面量表达式和函数关键字定义非常接近。考虑前面的区别,虽然有消息说字面量的匿名函数在OS X 10.4.3下的某些webkit的引擎下有bug,但我们平常所说的匿名函数均指采用函数字面量形式的匿名函数。更多详细内容可以阅读《JavaScript: The Definitive Guide, 5th Edition》的Functions那章。
三、匿名函数的代码模式

  昨天hedger wang在他的blog介绍了几种匿名函数的代码模式:
错误模式:其无法工作,浏览器会报语法错。

function(){
  alert(1);
}();

  函数字面量:首先声明一个函数对象,然后执行它。

(function(){
  alert(1);
} ) ( );

  优先表达式:由于Javascript执行表达式是从圆括号里面到外面,所以可以用圆括号强制执行声明的函数。

( function(){
  alert(2);
} ( ) );

  Void操作符:用void操作符去执行一个没有用圆括号包围的一个单独操作数。

void function(){
  alert(3);
}()

  这三种方式是等同的,hedger wang因为个人原因比较喜欢第3种,而在实际应用中我看到的和使用的都是第1种。
四、匿名函数的应用

  《Javascript的一种模块模式》中的第一句话就是“全局变量是魔鬼”。配合var关键字,匿名函数可以有效的保证在页面上写入Javascript,而不会造成全局变量的污染。这在给一个不是很熟悉的页面增加Javascript时非常有效,也很优美。实际上,YUI以及其相应的范例中大量使用匿名函数,其他的Javascript库中也不乏大量使用。
  Javascript的函数式编程(functional programming)的基石。具体请看《用函数式编程技术编写优美的 JavaScript》和《函数式JavaScript编程指南》。

本文转自 http://dancewithnet.com/ 作者:Kaven

技术文章 ,

JavaScript中call方法的使用

2010年1月15日 20:02 by webphoenix

  今天在看Mootools JavaScript Framework时,看到了N个地方都调用了call这个方法,虽然知道call是JS里内置的,但是由于平常很少用到,几乎不了解它的功能。今天google一下,这个call功能的确很强,出乎我的意料。现把学到的知识分享一下,介绍call的同时也同时说下其他几个相关的方法和属性 callee,caller,apply。

1.call方法

  微软的JScript帮助文档里这么说的:

call 方法
调用一个对象的一个方法,以另一个对象替换当前对象。

call([thisObj[,arg1[, arg2[, [,.argN]]]]])

参数
thisObj 可选项。将被用作当前对象的对象。
arg1, arg2, , argN 可选项。将被传递方法参数序列。

说明
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

要求
版本 5.5

请参阅
应用于:Function 对象

  刚开始看到了“调用一个对象的一个方法,以另一个对象替换当前对象”这句话时,我就傻了,实在没搞懂,谁调用谁替换谁啊。看到后面,也没一个例子,后来还是google一下,终于找到了一篇【javaScript 中 call 函数的用法说明】,这篇文章里说的还是蛮清楚的,我在这基础上“提炼”扩展了一下

1.1:只有方法才call方法,其他对象或属性没有
这里指的方法即typeof(any)==’function’,其他typeof非function的都没有call方法

//如方法funcA
function funcA()
{
//codes
}
//对象objB
var objB = {
/* property */
};
var strC = "12345";
//那么只有 funcA有call方法,objB, strC都没有call方法

请看执行下面这段代码

var Natives = [
	['String',String],
	['Math',Math],
	['Object',Object],
	['Array',Array],
	['Date',Date],
	['Number',Number],
	['RegExp',RegExp],
	['Boolean',Boolean],
	['Error',Error]
];

var str = [];
for(var i=0,length=Natives.length;i<length;i++)
{
	var str = 'typeof('+Natives[i][0]+')=';
	str += typeof(Natives[i][1])+',ttypeof(';
	str += Natives[i][0]+'.call)=';
	str += typeof(Natives[i][1].call);
	str.push(str);
}

alert(str.join('n'));

1.2.call的使用
[object1.]func1.call(object2,arg1,arg2…);

举例如下
//例1

function Add(a,b) {
	alert(a+b);
}
function Sub(a,b) {
	alert(a-b);
}
Add.call(Sub,1,2);
//结果是弹出 3
Add.call(null,1,2);
//结果同样是弹出 3

//例2

function Class1() {
	this.name = 'Class1';
	this.show = function() {
		alert('in class1, this.name='+this.name);
	};
}
function Class2() {
	this.name = 'Class2';
	this.show = function() {
		alert('in class2, this.name='+this.name);
	};
}
var c1 = new Class1();
var c2 = new Class2();
c1.show.call(c2);
c1.show();
c2.show();
//c1.show.call(c2); 弹出 in class1,this.name=class2
//c2.show(); 弹出 in class2,this.name=class2
//
/*
这里有必要先解释一下为什么 c1.show.call(c2); 弹出的是 in class1,this.name=class2,
当你用c1.show call c2的时候就是调用 c1.show,不过c1.show中的this.name中的this,已经被Class2代替,此时的this.name就是Class2里的name即 'Class2',故弹出的是 in class1,this.name=class2
如果不明白,看下面这个例子
*/

//例3

function Class3() {
	this.show = function() {
		alert(this.name);
	}
}
function Class4() {
	this.name = 'Class4';
}
var c3 = new Class3();
var c4 = new Class4();
c3.show.call(c4);
/*
其实如果单看Class3,是有问题的,在show方法中却调用了一个 this.name,而Class3中根本就没有name没有这个属性
当执行c3.show.call(c4)时,把 Class3的show方法中的this用 Class4代替,此时的 this.name就是 Class4中的name 即 'Class4'
*/

下面说个继承的问题,JS中继承一般使用如下的方法

function Class5()
{
	this.name = "Class5";
	this.func2 = function(){
		alert('this is function2,this.name='+this.name);
	};
}
var C5 = new Class5();
function Class6()
{
	this.name = "Class6";
	this.func1 = function() {
		alert('this is function1,this.name='+this.name);
	}
}
Class6.prototype = C5;
var c6 = new Class6();
c6.func2();
//弹出 this is function2 ,this.name = Class6

这里的继承只能是单继承

下面看这个例子

function Class7()
{
	this.name = 'Class7';
	this.func2 = function(){
		alert('this is func2,this name='+this.name);
	}
}
function Class8()
{
	Class7.call(this);
	this.name = 'Class8';
}
var c8 = new Class8();
c8.func2();
//弹出 this is func2,this name=Class8

这种继承方式是不是感觉比上面那种要好一些?毕竟我直接用父“类”名去操作,而不需要实例化父类一个对象
还可以在 Class7.call(this)和this.name = ‘Class8′之间再去call其他的类,这样就做到了多继承,如下例


function ClassA()
{
	this.valueA = 'valueA';
}
function ClassB()
{
	this.vlaueB = 'valueB';
}
function ClassC()
{
	ClassA.call(this);
	ClassB.call(this);
	this.test = function() {
		alert(this.valueA+'+'+this.valueB);
	}
}
var cCc = new ClassC()
cCc.test();
//弹出 valueA+valueB

2.apply 方法
微软的JScript参考文档里这样描述:

apply 方法
应用某一对象的一个方法,用另一个对象替换当前对象。
apply([thisObj[,argArray]])

参数
thisObj 可选项。将被用作当前对象的对象。
argArray 可选项。将被传递给该函数的参数数组。
说明
如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。

要求
版本 5.5
请参阅
应用于:Function 对象

和apply 方法和 call 类似,不过apply传的参数是通过一个数组而不是逐个传的

3. callee属性
callee属性是arguments的一个属性,返回方法的正文即
func.arguments.callee = func
如下面这个例子

function test()
{
	alert(arguments.callee);
}
test();
//将弹出test方法的全部内容

还有这个

function factorial(n){
	if (n <= 0)
		return 1;
	else
		return n * arguments.callee(n - 1)
}
alert(factorial(3));

可以看出可以轻松实现递归调用

4.caller属性
caller属性是方法的一个属性,返回当前调用该方法的方法
即如果A方法调用了B方法,那么在B.caller即A

function A()
{
	B();
}
function B()
{
	alert(B.caller);
}
A();

以上简单的说了下 call、callee、caller和apply的用法,希望有点帮助

技术文章 ,

谨慎使用IE下特有的CSS属性expression

2010年1月5日 10:30 by webphoenix

  最近在写一个基于IE内核浏览器的程序的HTML时候,发现程序占用的CPU很多,Process Explorer发现 IERAME.DLL占用CPU最多,于是跟踪,最后到样式表的一个expression属性,代码如下:

.NO-SELECT {
	script:expression(
		this.onselectstart = function() {
			return false;
		}
	);
}

  原来的意图是加了这个样式后就不能选中某个层,跟踪发现这个样式的效率超低,很消耗CPU资源,还不如直接在脚本里加上onselectstart事件,如果使用的是IE,强烈建议不要使用IE下特有的CSS属性 expression

技术文章 , , ,