最近在读《JavaScript语言精粹》这本书,作者是 Douglas Crockford;Douglas Crockford是一名来自 Yahoo!的资深JavaScript架构师,以创建和维护JSON(JavaScript object notation)格式而为大家所熟知。他会定期在各类会议上发表有关高级JavaScript的主题演讲。他也是ESMAscript委员会的成员之一。
本来想写一篇读后感的,但是想想,《JavaScript语言精粹》是我在JavaScript方面阅读的第一本书,算是开拓视野,所以心中只有对这本书的总结,谈不上有感。而且我的目的就是为了节省阅读这本书的时间,帮助大家了解大概的内容,我会尽量把自己觉得其中的精髓传达给想读这本书的朋友。
注意:这本书不是给初学者的,也不是一本傻瓜式的教程。
语法 Grammer
JavaScript的两种注释形式:/**/(块注释)和 //(行注释)
-
块注释的字符对可能出现在现在正则表达式的字面量里,所以块注释相对来说不安全
例如:
/* var rm_a = /a*/.match(s); */
作者建议尽量避免使用 /**/注释,用 //来代替它。
标识符
标识符有一个字母开头,其后可以选择性的加上一个或多个字母、数字或下划线。
标识符不能使用下面这些保留字:
abstract
boolean break byte
case catch char class const continue
debugger default delete do double
else enum export extends
false final finally float for function
goto
if implements import in instanceof int interface
long
native new null
package private protected public
return
short static super switch synchronized
this throw throws transient true try typeof
var volatile void
while with
数字
JavaScript只有一个数字类型。它在内部被表示与64位的浮点数,和Java的double数字类型一样。
如果一个数字字面量有指数部分,那么这个字面量的值等于e之前的数字与10的e之后的数字的次方相乘。所以 100和 1e2是相同的数字。
数字拥有方法,JavaScript有一个对象Math,它包含一套用于数字的方法。
例如,可以用Math.floor(number)方法把一个数字转换为一个整数。
字符串
JavaScript没有字符类型。要表示一个字符,只需创建仅包含一个字符的字符串即可。
字符串有一个 length属性。例如,"seven".length是5。
字符串也有一些方法。例如,'cat'.toUpperCase( ) === 'CAT'
语句
当 var语句被用于函数内部时,它定义的是这个函数的私有变量。
语句通常是按照从上到下的顺序被执行。JavaScript可以通过条件语句(if和switch)、循环语句(while、for和do)、强制跳转语句(break、return和throw)和函数调用来改变执行的序列
表达式
1. . [] () ----------提取属性与调用函数
2. * / % ----------乘法、除法、求余
3. && ----------逻辑 与
4.|| ----------逻辑 或
5.?: ----------三元
三元运算符?后面跟着另一个表达式,然后接一个:,其后面接第三个表达式。
三元运算符?有三个运算数。如果第1个运算数的值为真,产生第2个运算数的值;如果为假,则产生第3个是运算数的值。
对象 Objects
JavaScript里的对象是无类型的(class-free),对象可以包含其他对象。
JavaScript包含一种原型链的特征,允许对象继承另一个对象的属性。正确的使用它能减少对象初始化时消耗的时间和内存。
1、对象字面量 Object Literals
JavaScript的标识符中包含连接符“-”是不合法的,但是允许包含下划线“_”,
所以用引号括住“first-name”是必需的。
2、检索 Retrieval
要检索对象里包含的值,可以采用[]后缀中括住一个字符串表达式的方式。
如果字符串表达式是一个字符串字面量,那么也可以用 . 表示法代替。
考虑优先使用 . 表示法,因为它更紧凑且可读性更好。
如果你尝试检索一个并不存在的成员属性的值,将返回undefined。
尝试从undefined的成员属性中取值将会导致TypeError异常。这时可以通过 && 运算符来避免错误。
3、原型 Prototype
每个对象都连接到一个原型对象,并且它可以从中继承属性。所有通过对象字面量创建的对象都连接的Object.prototype,它是JavaScript中标配对象。
当你创建一个新对象时,你可以选择某个对象作为它的原型。JavaScript提供的实现机制杂乱而复杂,但其实可以被明显地简化。我们将给Object增加一个create方法。这个方法创建一个使用原对象作为其原型的新对象。
if(typeof Object.beget !== 'function') {
Object.create = function(o) {
var F = function() {};
F.prototype = o;
return new F();
};
}
var another_stooge = Object.create(stooge);
原型连接在更新时是不起作用的。当我们对某个对象做出的改变时,不会影响该对象的原型。
如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JavaScript会试着从原型对象中去获取属性值。如果那个原型对象也没有改属性,那么再从它的原型中寻找,依此类推,直到最后到达终点Object.prototype。
如果想要的属性完全不存在原型链中,那么结果就是undefined值。这个过程称为委托。
原型关系是一种动态关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
4、反射 Reflection
检查对象并确定对象有什么属性是很容易的事情,只要试着去检索该属性并验证取得的值。Typeof 操作符对确定属性的类型有很大的帮助:
typeof flight.number //'number'
typeof flight.status //'string'
typeof flight.arrival //'object'
typeof flight.manifest //'undefined'
注意任何原型链中的任何属性都会产生值:
typeof flight.toString //'function'
typeof flight.constructor //'function'
有两种方法处理掉这些不需要的属性。第一个是让你的程序做检查并丢弃值为函数的属性。另一个就是使用 hasOwnProperty 方法,如果对象有独有的属性,它将返回 true。
hasOwnProperty方法不会检查原型链。
fligth.hasOwnProperty('number') //true
flight.hasOwnProperty('constructor') //false
5、枚举 Enumeration
for in 语句可用来遍历一个对象中所有属性名。改枚举过程将会列出所以的属性——包括函数和你可能不关心的原型中的属性——所以没必要过滤掉那些你不想要的值。最为常用的过滤器是 hasOwnProperty 方法,以及使用 typeof 来排除函数:
var name;
for(name in another_stooge) {
if(typeof another_stooge[name] !== 'function') {
document.writeln(name + ':' + another_stooge[name]);
}
}
属性名出现的顺序是不确定的,因此要对任何可能出现的顺序有所准备。如果你想要确保属性以特定的顺序出现,最后的办法是完全避免使用 for in 语句,而是创建一个数组,在其中以正确的顺序包含属性名:
var i;
var propertier = {
'fiirst-name',
'middle-name',
'last-name',
'profession'
};
for(i = 0; i< properties.length; i += 1) {
document.writeln(properties[i] + ':' + another_stooge[properties[i]]);
}
通过 for 而不是 for in,可以得到我们想要的属性,而不用担心可能发掘出原型链中的属性,并且我们按正确的顺序取得了它们的值。
6、删除 Delete
delete运算符可以用来删除对象的属性。如果对象包含该属性,那么该属性就会被移除。它不会触及原型链中的任何对象。
删除对象的属性可能会让来自原型链中的属性透现出来:
another_stooge.nickname //'Moe'
delete another_stooge.nickname;
another_stooge.nickname //'Curly'
7、减少全局变量污染 Global Abatement
全局变量削弱了程序的灵活性,应该避免使用。
最小化使用全局变量的方法之一是为你的应用值创建一个唯一的全局变量:
var MYAPP = {};
该变量此时变成了你的应用的容器:
MYAPP.stooge = {
"first-name": "joe",
"last-name": "Howard"
};
MYAPP.flight = {
airline: "Oceanic",
departure: {
IATA: "SYD",
city: "Sydney"
}
};
只要把全局性的资源都纳入一个名称空间下,你的这个程序与其他应用程序、组件或类库之间发生冲突的可能性就会显著降低。这个程序也会变得更容易阅读,因为很明显,MYAPP.stooge 指向的是顶层结构。还有一种有效减少全局污染的方法,隐藏信息的方法,就是 闭包!
函数 Functions
1、函数字面量 Function Literal
函数对象通过函数字面量来创建:
//创建一个名为 add 的变量,并用来把两个数字相加的函数赋值给它
var add = function(a,b) {
return a + b;
};
函数字面量包括4个部分。
第1个部分是保留字 function。
第2个部分是函数名,它可以被省略。函数可以用它的名字递归地调用自己。此名字也可能被调试器和开发工具用来识别函数。如果没有给函数命名,则它被成为匿名函数(anonymous)。
第3个部分是包围在圆括号中的一组参数。多个参数用逗号隔开。这些参数的名称被定义为函数中的变量。它们不像普通的变量那样被初始化为 undefined,而是在该函数被调用时初始化为实际提供的参数的值。
第4个部分是包围在花括号中的一组语句。这些语句是函数的主体,它们在函数被调用是执行。
函数字面量可以出现在任何允许表达式出现的地方。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为 闭包(closure)。它是JavaScript强大表现力的来源!
2、调用 Invocation
调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义的形式参数,每个参数还接手两个附加的参数:this 和 arguments。参数 this 在面向对象编程中非常重要,它的值取决于调用的模式。在JavaScript中一共有4种调用模式:方法调用模式、函数调用模式、构造器调用模式和 apply 调用模式。这些模式在如果初始化关键参数 this 上存在差异。
调用运算符是跟任何产生一个函数值的表达式后的一对圆括号。圆括号中内可包含0个或多个用逗号隔开的表达式。每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数名。当实际参数(arguments)和形式参数(parameters)的个数不匹配时,不会导致运行时的错误。如果实际参数值过多了,超出的参数值会被忽略。如果实际参数值过少,缺失的值被替换为 undefined 。对参数值不会进行类型检查:任何类型的值都可以被传递给任何参数。
方法调用模式 The Method Invocation Pattern
当一个函数被保存为对象的一个属性是,我们称它为一个方法。当一个方法被调用时, this 被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个 . 点符号的表达式或[subscript]下标表达式),那么它就是被当做一个方法来调用。
var myObject = {
value :0;
increment:function(inc){
this.value += typeof inc ==='number'?inc:1;
} / /increment方法接受一个可选的参数,如果参数不是数字,默认使用数字1.
};
myObject.increment();
document.writeln(myObject.value); // 1
myObject.increment(2);
document.writeln(myObject.value); // 3
方法可以使用 this 去访问对象,能从对象中修改该对象。this 到对象的每绑定发生在调用的时候,这个超级迟绑定使用函数可以对 this 高度复用。通过 this 可取得它们所属对象的上下文的方法称为公共方法。
函数调用模式 The Function Invocation Pattern
当函数并非对象的属性时,它被当作一个函数来调用:
var sum = add(3, 4); //sum的值为7
当函数以此模式调用时,this 被绑定到全局对象,这是语言设计上的一个错误。当内部函数被调用时,this 应该绑定到外部函数的 this 变量。解决方案是:如果该方法定义一个变量并给它赋值为 this ,那么内部函数就可以通过那个变量访问到 this,按照约定,命名为 that:
myObject.double = function () { // 给myObject增加一个double方法
var that = this; // 解决方法
var helper = function() {
that.value = add(that.value, that.value);
};
helper(); // 以函数的形式调用helper
};
myObject.double(); // 以方法的形式调用double
document.writeln(myObject.Value()); //6
构造器调用模式 The Constructor Invocation Pattern
JavaScript是基于原型继承的语言,意味着对象可以直接从其它对象继承属性,该语言是无类别的。
如果在一个函数前面带上new来调用,将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上。new前缀也会改变return语句的行为。
var Quo = function (string){ //创建构造器函数Quo,有status属性的对象
this.status = string;
};
Quo.prototype.get_status = function() { //提供一个名为get_status的公共方法
return this.status;
};
var myQuo = new Quo("confused"); //构造一个Quo实例
document.wirteln(myQuo.get_status()); //打印显示“confused”