当前位置: 代码迷 >> JavaScript >> JavaScript: The Good Parts 读书笔记(5)
  详细解决方案

JavaScript: The Good Parts 读书笔记(5)

热度:649   发布时间:2012-10-06 17:34:01.0
JavaScript: The Good Parts 读书笔记(五)

五.Javascript 总结

?

语言精华:

  1. 函数是头等对象, 函数是有词法作用域的闭包(Lambda).
  2. 基于原型继承的动态对象, 对象是无类别的,可以通过普通的复制给任何对象添加一个新成员.一个对象可以从另一个对象继承成员元素.
  3. 对象字面变量和数组字面变量, 这对创建新的对象和数组来说是一种非常方便的表示法.

语言糟粕:

  1. 全局变量
    ??? Javascript中最糟糕的特性就是它对全局变量的依赖性。因为一个全局变量可以被程序的任何部分.在任意时间改变,它们会使得程序的行为被极大地复杂化。在程序中使用全局变量降低了程序的可靠性。
    ??? 三种方式可以定义全局变量:
    ??????? a.脱离任何函数直接安排一个var语句. var foo = value;
    ??????? b.直接添加一个属性到全局对象上. window.foo = value;
    ??????? c.直接使用未经声明的变量。这被称为隐式的全局变量: foo = value;
  2. 作用域
    ?? Javascript 的语法来源自C. 在所有其他类似C语言风格的语言里,一个代码块(括在一对花括号中的一组语句)会创造一个作用域。代码块中声明的变量在其外部是不可见的。而在Javascript中,代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。(注意,函数拥有作用域) 在Javascript中,声明变量的方式应该是在每个函数的开头部分声明所有变量。
    function blockArea(){
    	if(true){
    		var field = "in block!";
    	}
    	window.alert(field); // field 依然可见
    }
    blockArea();
    
    ?
  3. 自动补全分号
    ?? Javascript有一个机制,它试图通过自动插入分号来修正程序。但千万不能依靠它,他可能会掩盖更为严重的错误。请考虑在return 语句中自动插入分号导致的后果。如果一个return 语句返回一个值,这个值表达式的开始部分必须和return 在同一行上(!):
    function autoComplete(){
    	return   // --> return ; --> undefined
    	{
    		status : true
    	};
    }
    window.alert(autoComplete());
    // 自动插入分号导致程序被误解却没有任何警告信息。可以通过把 '{' 放在上一行的尾部来解决这个问题.
    function fixedComplete(){
    	return {
    		status : true
    	};
    }
    window.alert(fixedComplete());
    
    ?
  4. 保留字
    ??? Javascript保留了Java中大部分关键词,其中对于 class,byte,interface 等强类型语言所用的关键词而言,Javascript是无法使用上的。这些无用的保留关键词使得无法使用它们作为变量名,以及进行对象的属性导航(.)。同时,像 undefined, NaN, infinity 等使用到的关键词却不是保留字.
  5. typeof
    ???? typeof 运算符返回一个用于识别其操作数类型的字符串。例如 typeof 98.6 将返回'number'. 然而不幸的是,尝试 typeof null 将返回 'object' 而不是 'null'. 所以 typeof 将不能识别null, 当对null进行判断时,可以直接判断 value === null 或者 if(value && typeof value === 'object').
    ???? 另一个缺点是 typeof 对 正则表达式对象的识别在各个浏览器中式不一致的。有的返回 'object', 而另外的则返回 'function'.
  6. parseInt
    ???? parseInt 是一个将字符串转换为整数的函数. 但它却在遇到非数字时只是停止解析.所以 parseInt('16')与 parseInt('16 tons') 将产生相同的效果。它们均返回16而不会产生异常。如果parseInt 的第一个字符时0, 那么该字符串将是基于八进制来求值的。而八进制中 8与9 不是数字,所以parseInt('08) 和 parseInt('09) 均返回0. 这个错误导致了程序解析日期和时间时会出错。幸运的是,parseInt 还接受一个基础来作为参数,这样,parseInt('08',10) 的结果将是10, 所以应该总是提供这个参数.
  7. + 运算符
    ???? + 运算符可以用于加法运算或字符串连接。它究竟会执行那种操作时取决于其操作数的类型。在强类型语言中,这可能不是一个问题。但在Javascript中,如果你想要进行的是加法运算,请保证两个运算符都是整数.这通常是BUG的常见来源。
  8. 浮点数
    ????二进制的浮点数不能正确地处理十进制的小树,因此在Javascript 中, 0.1 + 0.2 并不等于 0.3. 如果想要在Javascript中进行精度运算,可以将小数转换为整数后进行操作。之后再缩小为浮点数。
    document.writeln(0.1+0.2); // 0.30000000000000004 
    document.writeln((0.1+0.2) === 0.3); // false
    document.writeln( (0.1 * 10 + 0.2 * 10) / 10 === 0.3); // true
    
    ?
  9. NaN
    ????NaN 是 IEEE 754中定义的一个特殊数量值。它表示值不是一个数字。然而下面的运算符将会返回true. typeof NaN === 'number', 该值可能会在试图将非数字形式的字符串转换为数字时产生(parseInt)。NaN 并不等同于它自己。 所以判断 NaN === NaN 将会返回false, 而 NaN !== NaN 将返回true.? Javascript 提供了一个isNaN 函数可以辨别数字与NaN, 但判断一个值是否可用作数字的最佳方法是使用isFinite函数, 因为它会筛除掉NaN 和 Infinity:
    function isNumber(value){
    	return typeof value === 'number' && isFinite(value);
    }
    
    ?
  10. 伪数组
    ?? 在Javascript中,并不存在真正的数组. 虽然这也不完全是坏事,带方法的数组确实很容易使用,并且不会产生数组越界错误. 但它们的性能与真正的数组比起来差别是很大的。而且 typeof 运算符不能识别数组和对象,要判断一个值是否为数组,还得必须检查它的 constructor 属性. 此外,在对不同帧或窗口创建的数组进行检测时,应该做更多的判断.
    function isArray(value){
    	return typeof value === 'object' && value.constructor === Array;
    }
    
    function isArray_enhanced(value){
    	return value && typeof value === 'object' &&
    			// 数组或伪数组(arguments)对象均包含length属性
    			typeof value.length === 'number' &&
    			// 对于 arguments对象,去掉下列判断将返回true.
    			typeof value.splice === 'function' &&
    			// 判断属性是否为可枚举的(for in 循环可用),对于所有数组,将得到false.
    			!(value.propertyIsEnumerable('length'));
    }
    ?最后,函数中的arguments 对象并不是一个真正的数组,它只是一个带有length成员元素的对象.
  11. false 的取值
    ?在进行布尔判断是,Javascript 会将一系列值看做false:
    //      Number -->  0 或 NaN
    //      String -->  ''(空串)
    //      Boolean --> false
    //      Object -->  null
    //      Undefined --> undefined
    
    ?这些值都等同于逻辑假值,但它们却是不可互换的。
  12. hasOwnProperty
    ??? hasOwnProperty 方法可以确认对象中的属性是否为原生属性(不继承自原型链). 然而由于它是一个方法而不是运算符. 所以在任何对象中,他可能会被一个不同的函数甚至一个非函数的值所替换。

语言鸡肋:

  1. == 判断
    ??? Javascript有两组相等运算符: ===/!== 和 ==/!= . 其中 === 和 !== 会先判断操作数的类型是否相同,之后在判断其值是否相等.大多数情况下它会按照你期望的方式工作。而 == 和 != 在操作数类型不一致时,将会尝试进行类型转换,这些类型转换规则复杂且难以记忆:
    document.writeln('' == '0');		  // false
    document.writeln(0 == '');	                  // true
    document.writeln(0 == '0');		  // true
    
    document.writeln(false == 'false');            // false
    document.writeln(false == 0);		  // true
    
    document.writeln(false == undefined);     // false
    document.writeln(false == null);	  // false
    document.writeln(null == undefined);       // true
    
    document.writeln(' \t\r\n ' == 0);	  // true
    
    ?
  2. with 语句
    ?? Javascript提供了一个with 语句, 其本意是可以快捷地访问对象中的属性. 不幸的是,它的结果可能是不可预料的。所以应该避免使用它.
    var obj = {"a":"123","b":"321"};
    with(obj){
    	a = b;
    }
    // 上面的代码等同于
    if(obj.a === undefined){
    	a = obj.b === undefined? b : obj.b;
    }else{
    	obj.a = obj.b === undefined? b : obj.b;
    }
    window.alert(obj.a);
    
    ?
  3. eval 函数
    ??? eval函数传递一个字符串给Javascript编译器,并执行器结果。 它是一个被滥用的最多的Javascript特性.那些对Javascript语言一知半解的人们最常用到它。例如为了进行属性赋值,可以有人会这么写:
    var myValue;
    eval("myValue = '123';");
    
    ? 而不是直接书写:
    myValue = '123';
    ?? 使用eval 函数会是代码难以阅读,并且使应用的性能显著降低。更重要的是,该函数还会导致安全性问题.因为它给被求值的文本授予了太多的权利。Function 构造器是eval 的另一种形式,所以也应该避免使用.浏览器所提供的setTimeout 和 setInterval 函数可以接受一个字符串作为参数。此时二者会像eval一样去解析并执行字符串.
  4. 位运算符
    ?? Javascript有着与Java相同的一套位运算符:
    &     and          按位与
    |      or             按位或
    ^     xor           按位异或
         not           按位非
    >>                   带符号右移
    >>>                 无符号(补0) 右移
    <<                   左移
    ?? 在Java里,位运算符处理的是整数。但Javascript中没有整数类型。它只有双精度的浮点数。因此,位操作符将他们的数字运算数先转换成整数,接着执行运算,然后再转换回去。在其他语言中,位操作是非常快的,然而在Javascript中,它们却很慢。所以Javascript中很少有需要进行位操作。
  5. function 语句与函数表达式
    ??? Javascript既有function 语句,同时也有函数表达式。其中一个function 语句就是其值为一个函数的var语句的速记形式:
    function foo(){}
    ?既是等同于:
    var foo = function(){};
    ??? 这里更推荐使用第二种形式,因为对于Javascript而言,理解函数就是对象是很重要的。
    ????一个语句不能以一个函数表达式开头,因为官方的语法假定以单词 function 开头的语句是一个function 语句。 解决方法是将函数表达式放在一对括号中:
    var module = (function(){
    	var hidden_variable;
    
    	return function(){
    		hidden_variable = 'private!';
    		window.alert(hidden_variable);
    	};
    })();
    module();
    
    ?
  6. 包装类型
    ?? Javascript有一套与Java类似的包装对象。例如:
    new Boolean(false);
    ?? 该表达式会返回一个对象。该对象又一个valueOf方法会返回被包装的值. 在Javascript中,这是完全没有必要的,使用这种方式往往会令人困惑, 因此避免使用 new Booleam, new Number 或 new String.对于 new Object 和 new Array, 可以使用 {} 和 [] 来代替.
  7. new 运算符
    ?? Javascript的new运算符创建一个原型继承自其操作数的新对象,然后调用该运算数,并把新对象绑定为this, 这给操作数(构造器函数)一个机会在返回给调用者之前进行初始化操作。这意味着,如果你忘记使用了 new 运算符,你所得到的就是一个普通函数调用,此时的this将会被绑定到全局对象上。这意味着构造器函数中的初始化操作会污染全局变量。这是一件很糟糕的事情,而且既没有编译时警告,也没有运行时警告。按照惯例,在打算使用new 构造函数时,函数名应该为首字母大写。
  8. void 运算符
    ??? 在很多语言中,void 是一种类型,表示没有值. 而在Javascript里,void 是一个运算符,它接受一个运算数并返回 undefined. 这没有什么用,而且会令人非常困惑。所以应该避免使用它。?
1 楼 rainsilence 2011-02-19  
全局变量没那么槽吧,他可以用来实现namespace
2 楼 wenbois2000 2011-02-21  
全局变量留下了一种冲突的可能性。
命名空间应该是语言本身提供支持来避免命名冲突, 而不是使用全局变量来模拟。
当然,在目前的情况下通过约定将程序中用到的对象全部组织到一个根(root)对象下也是常见的做法。
  相关解决方案