读《编写高质量代码:改善JavaScript程序的188个建议》1

建议3:减少全局变量污染

定义全局变量有3种方式:

?在任何函数外面直接执行var语句。

var f=‘value‘;

?直接添加一个属性到全局对象上。全局对象是所有全局变量的容器。在Web浏览器中,全局对象名为window。

window.f=‘value‘;

?直接使用未经声明的变量,以这种方式定义的全局变量被称为隐式的全局变量。

f=‘value‘;

为方便初学者在使用前无须声明变量而有意设计了隐式的全局变量,然而不幸的是忘记声明变量成了一个非常普遍的现象。JavaScript的策略是让那些被忘记预先声明的变量成为全局变量,这导致在程序中查找Bug变得非常困难。

JavaScript语言最为糟糕的就是它对全局变量的依赖性。全局变量就是在所有作用域中都可见的变量。全局变量在很小的程序中可能会带来方便,但随着程序越来越大,它很快变得难以处理。因为一个全局变量可以被程序的任何部分在任意时间改变,使得程序的行为被极大地复杂化。在程序中使用全局变量降低了程序的可靠性。

全局变量使在同一个程序中运行独立的子程序变得更难。如果某些全局变量的名称与子程序中的变量名称相同,那么它们将会相互冲突并可能导致程序无法运行,而且通常还使程序难以调试。

实际上,这些全局变量削弱了程序的灵活性,应该避免使用全局变量。努力减少使用全局变量的方法:在应用程序中创建唯一一个全局变量,并定义该变量为当前应用的容器。

var My={};

My.name={

"first-name":"first",

"last-name":"last"

};

My.work={

number:123,

one:{

name:"one",

time:"2012-9-14 12:55",

city:"beijing"

},

two:{

name:"two",

time:"2012-9-12 12:42",

city:"shanghai"

}

};

只要把多个全局变量都追加在一个名称空间下,将显著降低与其他应用程序产生冲突的概率,应用程序也会变得更容易阅读,因为My.work指向的是顶层结构。当然也可以使用闭包体将信息隐藏,它是另一种有效减少“全局污染”的方法。

在编程语言中,作用域控制着变量与参数的可见性及生命周期。这为程序开发提供了一个重要的帮助,因为它减少了名称冲突,并且提供了自动内存管理。

var foo=function(){

var a=1,b=2;

var bar=function(){

var b=3,c=4;//a=1,b=3,c=4

a+=b+c;//a=8,b=3,c=4

};//a=1,b=2,c=undefined

bar();//a=21,b=2,c=undefined

};

大多数采用C语言语法的语言都拥有块级作用域。对于一个代码块,即包括在一对大括号中的语句,其中定义的所有变量在代码块的外部是不可见的。定义在代码块中的变量在代码块执行结束后会被释放掉。但是,对于JavaScript语言来说,虽然该语言支持代码块的语法形式,但是它并不支持块级作用域。

JavaScript支持函数作用域,定义在函数中的参数和变量在函数外部是不可见的,并且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见。

其他主流编程语言都推荐尽可能迟地声明变量,但是在JavaScript中就不能够这样,因为它缺少块级作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。

建议4:注意JavaScript数据类型的特殊性

1.防止浮点数溢出

二进制的浮点数不能正确地处理十进制的小数,因此0.1+0.2不等于0.3。

num=0.1+0.2;//0.30000000000000004

这是JavaScript中最经常报告的Bug,并且这是遵循二进制浮点数算术标准(IEEE 754)而导致的结果。这个标准适合很多应用,但它违背了数字基本常识。幸运的是,浮点数中的整数运算是精确的,所以小数表现出来的问题可以通过指定精度来避免。例如,针对上面的相加可以这样进行处理:

a=(1+2)/10;//0.3

这种处理经常在货币计算中用到,在计算货币时当然期望得到精确的结果。例如,元可以通过乘以100而全部转成分,然后就可以准确地将每项相加,求和后的结果可以除以100转换回元。

2.慎用JavaScript类型自动转换

在JavaScript中能够自动转换变量的数据类型,这种转换是一种隐性行为。在自动转换数据类型时,JavaScript一般遵循:如果某个类型的值被用于需要其他类型的值的环境中,JavaScript就自动将这个值转换成所需要的类型,具体说明见表1.1。

如果把非空对象用在逻辑运算环境中,则对象被转换为true。此时的对象包括所有类型的对象,即使是值为false的包装对象也被转换为true。

如果把对象用在数值运算环境中,则对象会被自动转换为数字,如果转换失败,则返回值为NaN。

当数组被用在数值运算环境中时,数组将根据包含的元素来决定转换的值。如果数组为空数组,则被转换为数值0。如果数组仅包含一个数字元素,则被转换为该数字的数值。如果数组包含多个元素,或者仅包含一个非数字元素,则返回NaN。

当对象用于字符串环境中时,JavaScript能够调用toString()方法把对象转换为字符串再进行相关计算。当对象与数值进行加号运算时,则会尝试将对象转换为数值,然后参与求和运算。如果不能够将对象转换为有效数值,则执行字符串连接操作。

3.正确检测数据类型

使用typeof运算符返回一个用于识别其运算数类型的字符串。对于任何变量来说,使用typeof运算符总是以字符串的形式返回以下6种类型之一:

?"number"

?"string"

?"boolean"

?"object"

?"function"

?"undefined"

不幸的是,在使用typeof检测null值时,返回的是“object”,而不是“null”。更好的检测null的方式其实很简单。下面定义一个检测值类型的一般方法:

function type(o){

return(o===null)?"null":(typeof o);

}

这样就可以避开因为null值影响基本数据的类型检测。注意:typeof不能够检测复杂的数据类型,以及各种特殊用途的对象,如正则表达式对象、日期对象、数学对象等。

对于对象或数组,可以使用constructor属性,该属性值引用的是原来构造该对象的函数。如果结合typeof运算符和constructor属性,基本能够完成数据类型的检测。表1.2所示列举了不同类型数据的检测结果。

建议6:正确处理JavaScript特殊值

2.正确使用null和undefined

JavaScript有5种基本类型:String、Number、Boolean、Null和Undefined。前3种都比较好理解,后面两种就稍微复杂一点。Null类型只有一个值,就是null;Undefined类型也只有一个值,即undefined。null和undefined都可以作为字面量在JavaScript代码中直接使用。

null与对象引用有关系,表示为空或不存在的对象引用。当声明一个变量却没有向它赋值的时候,它的值就是undefined。undefined的值会在如下情况中出现:

?从一个对象中获取某个属性,如果该对象及其prototype链中的对象都没有该属性,该属性的值为undefined。

?一个函数如果没有显式通过return语句将返回值返回给其调用者,其返回值就是undefined,但在使用new调用函数时例外。

?JavaScript的函数可以声明任意多个形参,当该函数实际被调用时,传入的参数的个数如果小于声明的形式参数的个数,那么多余的形式参数的值为undefined。

如果对值为null的变量使用typeof检测,得到的结果是“object”,而typeof undefined的值为“undefined”。null==undefined,null!==undefined。

与null不同,undefined不是JavaScript的保留字,在ECMAScript v3标准中才定义undefined为全局变量,初始值为undefined。因此,在使用undefined值时就存在一个兼容问题(早期浏览器可能不支持undefined)。除了直接赋值和使用typeof运算符外,其他任何运算符对undefined的操作都会引发异常。不过,可以声明undefined变量,然后查看它的值,如果它的值为undefined,则说明浏览器支持undefined值。例如:

var undefined;

alert(undefined);

如果浏览器不支持undefined关键字,可以自定义undefined变量,并将其赋值为undefined。例如:

var undefined=void null;

声明变量为undefined,将其初始化为表达式void null的值,由于运算符void在执行其后的表达式时会忽略表达式的结果值,而总是返回值undefined,因此利用这种方法可以定义一个变量为undefined,并将其赋值为undefined。既然是将变量undefined赋值为undefined,还可以使用如下方式:

var undefined=void 1;

或者使用没有返回值的函数:

var undefined=function(){}();

alert(undefined);//"undefined"

可以使用typeof运算符来检测某个变量的值是否为undefined:

var a;

if(typeof a=="undefined"){

}

3.使用假值

JavaScript的类型系统是非常混乱的,类型特性不明显,而且交叉错乱。JavaScript语法系统拥有一大组假值,如以下代码所示。这些值的布尔值都是false。

0//Number

NaN//Number

‘‘//String

false//Boolean

null//Object

undefined//Undefined

这些值全部都等同于false,但它们是不可互换的。例如,下面用法是错误的。

value=myObject[name];

if(value==null){

}

这是在用一种错误的方式去确定一个对象是否缺少一个成员属性。undefined是缺失的成员属性值,而上面代码片段用null来测试,使用了会强制类型转换的==运算符,而不是更可靠的===运算符。正确的用法如下:

value=myObject[name];

if(!value){

}

undefined和NaN并不是常见,它们是全局变量,还可以改变它们的值,虽然在程序设计中不应该采取这种做法,但可以改变它们的值。
时间: 2024-10-10 08:19:46

读《编写高质量代码:改善JavaScript程序的188个建议》1的相关文章

[已读]编写高质量代码 改善JavaScript程序的188个建议

吐槽一万遍,买的最后悔的一本,没有之一,大量篇幅抄袭<高性能javascript>,我记得还有部分抄袭<javascript精粹>,<javascript模式>有没有抄我就不知道了,内容章节安排也很不合理.

编写高质量代码改善java程序的151个建议——导航开篇

2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,惨不忍睹是吧.确实,人和代码一样都在成长,都在变好当中.有时候只是实现功能的编程,长进不了呀. 博客提供的好处就可以交流,讨论的学习方法你们应该知道. 在这里,我会陆陆续续的进行对<编写高质量代码改善java程序的151个建议>看法,希望大家点击交流. 正文 看这本书原因   1.项目做的只是实现功能,然而没有好好

转载-------编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)

阅读目录 建议1:不要在常量和变量中出现易混淆的字母 建议2:莫让常量蜕变成变量 建议3:三元操作符的类型务必一致 建议4:避免带有变长参数的方法重载 建议5:别让null值和空值威胁到变长方法              The reasonable man adapts himself to the world; The unreasonable one persists in trying to adapt the world himself. 明白事理的人使自己适应世界:不明事理的人想让世

编写高质量代码改善C#程序的157个建议——建议9: 习惯重载运算符

建议9: 习惯重载运算符 在开发过程中,应该习惯于使用微软提供给我们的语法特性.我想每个人都喜欢看到这样的语法特性: int x = 1; int y = 2; int total = x + y; 而不是用下面的语法来完成一样的事情: int x = 1; int y = 2; int total = int.Add(x, y); 同理,在构建自己的类型时,我们应该始终考虑该类型是否可以用于运算符重载.如果考虑类型Salary,下面的这段代码看起来就不是那么舒服了: Salary mikeIn

编写高质量代码改善C#程序的157个建议——建议45:为泛型类型参数指定逆变

建议45:为泛型类型参数指定逆变 逆变是指方法的参数可以是委托或者泛型接口的参数类型的基类.FCL4.0中支持逆变的常用委托有: Func<int T,out TResult> Predicate<in T> 常用委托有: IComparer<in T> 下面例子演示了泛型类型参数指定逆变所带来的好处: class Program { static void Main() { Programmer p = new Programmer { Name = "Mi

编写高质量代码改善C#程序的157个建议——建议27:在查询中使用Lambda表达式

建议27:在查询中使用Lambda表达式 LINQ实际上是基于扩展方法和Lambda表达式的.任何LINQ查询都能通过扩展方法的方式来代替. var personWithCompanyList = from person in personList select new { PersonName = person.Name, CompanyName = person.CompanyID==0?"Micro":"Sun" }; foreach (var item in

编写高质量代码改善C#程序的157个建议——建议26:使用匿名类型存储LINQ查询结果

建议26:使用匿名类型存储LINQ查询结果 从.NET3.0开始,C#开始支持一个新特性:匿名类型.匿名类型有var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 即支持简单类型也指出复杂类型.简单类型必须是一个非空初始值,复杂类型则是一个以new开头的初始化项. 匿名类型的属性是只读的,没有属性设置器,它一旦被初始化就不可更改. 如果两个匿名类型的属性值相同,那么就认为这两个匿名类型相等. 匿名类型可以再循环中用作初始化器. 匿名类型支持智能感知. 匿名

编写高质量代码改善C#程序的157个建议——建议20:使用泛型集合代替非泛型集合

建议20:使用泛型集合代替非泛型集合 在建议1中我们知道,如果要让代码高效运行,应该尽量避免装箱和拆箱,以及尽量减少转型.很遗憾,在微软提供给我们的第一代集合类型中没有做到这一点,下面我们看ArrayList这个类的使用情况: ArrayList al=new ArrayList(); al.Add(0); al.Add(1); al.Add("mike"); foreach (var item in al) { Console.WriteLine(item); } 上面这段代码充分演

编写高质量代码改善C#程序的157个建议——建议12: 重写Equals时也要重写GetHashCode

建议12: 重写Equals时也要重写GetHashCode 除非考虑到自定义类型会被用作基于散列的集合的键值:否则,不建议重写Equals方法,因为这会带来一系列的问题. 如果编译上一个建议中的Person这个类型,编译器会提示这样一个信息: “重写 Object.Equals(object o)但不重写 Object.GetHashCode()” 如果重写Equals方法的时候不重写GetHashCode方法,在使用如FCL中的Dictionary类时,可能隐含一些潜在的Bug.还是针对上一

编写高质量代码改善C#程序的157个建议——建议13: 为类型输出格式化字符串

建议13: 为类型输出格式化字符串 有两种方法可以为类型提供格式化的字符串输出.一种是意识到类型会产生格式化字符串输出,于是让类型继承接口IFormattable.这对类型来 说,是一种主动实现的方式,要求开发者可以预见类型在格式化方面的要求.更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活 多变的方法,可以根据需求的变化为类型提供多个格式化器.下面就来详细介绍这两种方法. 最简单的字符串输出是为类型重写ToString方法,如果没有为类型重写该方法,默认会调用Obj