阅读笔记《JavaScript语言精粹》

阅读笔记《JavaScript语言精粹》

对象

1.检索属性

使用[]和.

2.引用传递

JavaScript的简单数据类型包括数字、字符串、布尔值、null值和undefined值。其它所有的值都是对象。数组是对象,函数是对象,正则表达式是对象。对象通过引用传递,它们永远不会被复制。

3.原型

当我们对某个对象做出改变时,不会触及该对象的原型,只有在检索值的时候才会被用到。原型连接在更新时是不起作用的。delete删除对象中的属性,它也不会触及原型链中的任何对象,删除对象的属性可能会让来自原型链中的属性透现出来。

4.枚举

for-in语句枚举过程会列出所有的属性——包括函数和你可能不关心的原型中的属性——所以有必要过滤掉那些你不想要得值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数。

5.减少全局变量污染

JavaScript依赖于全局变量来进行链接,应当把全局性的资源都纳入一个名称空间之下,你的程序与其它应用程序、组件或类库之间发生冲突的可能性就会显著降低。


函数

1.函数对象

函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype),每个函数在对象创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。函数的与众不同之处在于它们可以被调用。

2.闭包

函数可以定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。

3.调用

除了声明时定义得形式参数,每个函数还接收两个附加的参数:this和arguments。参数this的值取决于调用的模式,这些模式在如何初始化关键参数this上存在差异。

  • 方法调用模式:当函数为对象的一个属性时,称它为该对象的方法,this绑定到该对象,方法可以通过this访问该对象的属性。

  • 函数调用模式:当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误。(应当绑定到外部父函数的this变量。)

  • 构造器调用模式:如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个对象。new前缀也会改变return语句的行为。(参照5.返回)

  • apply调用模式:函数是对象,所以也拥有方法。apply方法让我们构建一个参数数组传递给调用函数。apply接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。

4.参数

当函数被调用时,会得到一个arguments参数,可以通过它访问函数所有的参数列表,这使得编写一个无须指定参数个数的函数成为可能。arguments拥有一个length属性,但它不是数组,并没有任何数组的方法。

5.返回

一个函数总会有返回值,如何没有指定就返回undefined。如果函数调用时前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。

6.扩充类型的功能

通过给基本类型增加方法,我们可以极大的提高语言的变现力。因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上,哪怕对象示例是在方法被增加之前就创建好了。object.prototype.METHOD_NAME = ...

7.递归

递归函数可以非常高效的操作树形结构,遗憾的是,javas当前并没有提供递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。

8.作用域

JavaScript不支持块作用域,只有函数作用域,意味着定义在函数中的参数和变量在函数外部不可见,而在内部任何位置定义的变量,在该函数任何地方都可见。所以,最好在函数体的顶部声明函数中可能用到的所有变量。

9.模块

使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象,通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。
模块模式的一般形式:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有化变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。

10.级联

级联技术可以产生出极富表现力的接口。它也能给那波构造“全能”接口的热潮降温,一个接口没有必要一次做太多事情。

obj.setPositionX(10)
   .setPositionY(10)
   .color("red")
   .border("10px")
   .tip("级联");

11.记忆

函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称作记忆。JavaScript的对象和数组要实现这种优化非常的方便。比如用递归计算Fibonacci数列...


继承

JavaScript是弱类型的语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。
在基于类的语言中,对象是类的示例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。
JavaScript中可能的继承模式有很多。

1.伪类

使用new前缀。伪类模式本意是想向面向对象靠拢,但它看起来格格不入。

2.原型

原型模式中会摒弃类,转而专注于对象,一个新对象可以继承一个旧对象的属性。

3.函数化(模块)

函数化模式有很大的灵活性。它相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

4.部件

我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特征的函数。


数组

数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是一种性能出色的数据结构。不幸的是,JavaScript没有像此类数组一样的数据结构。
作为替代,JavaScript提供了一种拥有一些类数组特征的对象。它把数组的下标转换成字符串,用其作为属性。它明显地比一个真正的数组慢,但它使用起来很方便。它的属性检索和更新与对象一模一样,只不过多一个可以用整数作为属性名的特性。

1.长度

每个数组都有一个length属性。和大多数其它语言不同,JavaScript数组的length是没有上界的。如果用大于等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素,不会发生越界错误。length属性的值是这个数组的最大整数属性名加上1:

var arr = [];
arr[100] = true;
//arr.length  === 101

设置更大的length不会给数组分配更多的空间。而把length设小将导致所有下标大于等于新length的属性被删除。

2.删除

由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素。

var arr = [10,20,30];
delete arr[1];
// arr: [10, undefined, 30]

不幸的是,那样会在数组中留下一个空洞。因为前排的在被删除元素之后的元素保留着它们最初的属性。而你通常想要递减后面每个元素的属性。
JavaScript数组有一个splice方法用来进行数组元素的增加删除,对于大型数组来说可能会效率不高。

3.判断是否数组

JavaScript本身对于数组和对象的区别是混乱的。typeof运算符报告数组的类型是'object',这没有任何意义。我们可以通过is_array函数来弥补这个缺陷。

var is_array = function (value) {
	return value && typeof value === 'object' && value.constructor === Array;
};

遗憾的是,它在识别不同的窗口(window)或帧(frame)里构造的数组时会失败。有一个更好的方式去判断一个对象是否为数组:

var is_array = function (value) {
	return Object.prototype.toString.apply(value) === '[Object Array]';
};

代码风格

  1. 优秀的程序拥有一个前瞻性的结构,它会预见到未来才可能需要的修改,但不会让其成为过度的负担。
  2. 使用K&R风格,把 { 放在一行的结尾而不是下一行的开头,因为它会避免JavaScript的return语句中的一个可怕的设计错误。(插入分号)
  3. JavaScript拥有C语言的语法,但它的代码块没有作用域。所以在每个函数的开始部分声明所有的变量。
  4. JavaScript可以方便的使用全局变量,但随着程序的日益复杂,全局变量逐渐变得问题重重。对一个脚本应用或工具库,尽量只使用唯一一个全局变量。每个对象都有它自己的命名空间。使用闭包能提供进一步的信息隐藏。

毒瘤

展示JavaScript一些难以避免的问题特性。你必须知道这些问题并准备好应对措施。

1.全局变量

全局变量可以被程序的任何部分在任意时间修改,它们使得程序的行为变得极度复杂。在程序中使用全局变量降低了程序的可靠性。
全局变量使得在同一个程序中运行独立的子程序变得困难。如果某些全局变量的名称碰巧和子程序中的变量名称相同,那么他们将会相互冲突,可能导致程序无法运行,而且通常难以调试。
JavaScript的问题不仅在于它允许使用全局变量,而且在于它依赖全局变量。JavaScript没有连接器,所有的编译单元都会载入一个公共的全局对象中。
共有3种方式定义全局变量:

  • 第1种是在任何函数之外放置一个var语句:
var foo = value;
  • 第2种是直接给全局对象添加一个属性。全局对象时所有全局变量的容器。在Web浏览器里,全局对象名为window:
window.foo = value;
  • 第3种是直接使用未经声明的变量,这被称为隐身的全局变量:
foo = value;

2.作用域

JavaScript的语法来源于C。它采用了块语法,却没有提供块级作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。所以应该在每个函数的开头部分声明所有的变量。

3.自动插入分号

JavaScript有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序。但是,千万不要指望它,它可能会掩盖更为严重的错误。
return返回一个值时,这个值表达式的开始部分必须和return位于同一行,否则return后面被插入分号后,返回结果将是undefined。

4.Unicode

JavaScript设计之初,Unicode预期最多会有65536个字符。但从那以后它的容量慢慢增长到了拥有一百万个字符。
JavaScript的字符是16位的,足以覆盖原有的65536个字符。剩下的百万字符中的每一个都可以用一对字符来表示。Unicode把一对字符视为一个单一的字符。而JavaScript认为一对字符是两个不同的字符。

5.typeof

typeof null返回的是'Object',而不是null
对于正则表达式typeof /a/各种JavaScript的实现版本不太一致

6.parseInt

parseInt把一个字符串转换为整数,它在遇到非数字时会停止解析。
parseInt("16")与parseInt("16 haha")会产生相同的结果,它不会提醒我们出现了额外的文本。

7.+运算符

+运算符用于加法运算或字符串连接。如果其中一个运算数是一个(空)字符串,它会把另一个运算数转换成字符串并返回。如果你打算用 + 去做加法运算,请确保两个运算数都是Number。这个复杂的行为是bug的常见来源。

8.NaN

typeof NaN === 'number' //true
但是NaN表示的不是一个数字,该值可能会在试图把非数字形式的字符串转换为数字时产生。
typeof不能辨别数字和NaN,而且NaN也不等同于它自己:

NaN === NaN //false
NaN !== NaN //true
isNaN(NaN) //true
isNaN(0) //false
isNaN("0") //false
isNaN("haha") //true

9.伪数组

arguments不是一个数组,它只是一个有着length成员属性的对象。
检测数组:

if(Object.prototype.toString.apply(VALUE) == "[object Array]") {
	//VALUE是一个数组
}

10.假值

//值 (类型)
0         //Number
NaN 	  //Number
""   	  //String
false     //Boolean
null      //Object
undefined //Undefined

undefined和NaN并不是常量。它们居然是全局变量,而且你可以改变它们的值。千万不要这样做。

11.hasOwnProperty

hasOwnProperty方法可以用做一个过滤器去避开for in语句中的一个隐患。遗憾的是,它是一个方法,而不是一个运算符,所以在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换。

11.对象

JavaScript的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性。有时候那会带来麻烦。


糟粕

展示JavaScript一些有问题的特性,但我们很容易就能避免它们。

1.==

如果两个运算数类型不同,和!=会强制转换运算数值的类型。永远不要使用它们!
使用
=和!,只有在两个运算数类型相同且拥有相同值时=才会返回true,!==返回false。

2.with语句

with语句本意是用来快捷访问对象的属性,不幸的是,它的结果可能有时不可预料,所以应该避免使用它。且它本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的词法作用域。

3.eval

eval使得代码更加难以阅读。还使得性能显著降低,因为它需要运行编译器,还会让JSLint无法检测其中的问题。
浏览器提供的setTimeout和setInterval函数,参数传人字符串时也会调用eval。

4.continus语句

continue语句跳到循环的顶部,通过移除continue语句之后的代码,性能都会得到改善!

5.switch穿越

不要使用switch穿越,这会让我们更加容易发现不小心造成的case条件穿越导致的bug。

6.缺少块的语句

if, while, do 或 for 语句可以接受一个括在花括号中的代码,也可以接受单行语句。但那会模糊了程序的结构,不要这么做。

7.位运算符

JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。

8.function语句

不要在if中使用function语句,不同的浏览器在解析时的处理是不同的,这就会造成可移植性的问题。
一个语句不能以函数表达式开头,因为官方的语法假定单词function开头的语句是一个function语句。解决方法是把函数调用括在一对圆括号中:

(function () {
	//
}());

9.类型的包装对象

JavaScript有一套类型的包装对象,例如:new Boolean(false)会返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且又是还令人困惑。
不要使用new Boolean, new Number, new String, 也请避免使用new Objectnew Array,可使用 {} 和 [] 来代替。

10.new

使用new创建原型的新对象时,会涉及到this的绑定问题,如果在创建对象是忘记加上new,则会把this绑定到全局对象,造成全局变量污染。所以尽量避免去使用new。

11.void

在JavaScript中void是一个运算符,它接受一个运算数并返回undefined。这没什么用,且令人困惑。应避免去使用它。

posted @ 2015-11-10 21:04  、拂晓  阅读(283)  评论(0编辑  收藏  举报