JavaScript高级程序设计(第3版)(文字版)2017
JavaScript新特性:
- JavaScript 1.6
Array.prototype.indexOf()
Array.prototype.lastIndexof()
Array.prototype.every()
Array.prototype.filter()
Array.prototype.forEach()
Array.prototype.map()
Array.prototype.some()
- JavaScript 1.8
Array.prototype.reduce()
Array.prototype.reduceRight()
- JavaScript 1.8.1
Object.getPrototypeOf()
//Support for native JSON
String.prototype.trim()
String.prototype.trimLeft()
String.prototype.trimRight()
String.prototype.startsWith()
- JavaScript 1.8.5
Object.create()
Object.defineProperty()
Object.defineProperties()
Object.getOwnPropertyDescriptor()
Object.keys()
Object.getOwnPropertyNames()
Object.preventExtensions()
Object.isExtensible()
Object.seal()
Object.isSealed()
Object.freeze()
Object.isFrozen()
Array.isArray()
Date.prototype.toJSON()
Function.prototype.bind()
Page50 3.4.5 Number 类型
parseInt()
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10 (十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56 (八进制数)
var num6 = parseInt("70"); // 70 (十进制数)
var num7 = parseInt("0xf"); // 15 (十六进制数)
var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10 (按十进制解析)
var num4 = parseInt("10", 16); //16 (按十六进制解析)
parseFloat()
var num1 = parseFloat("1234blue"); //1234 (整数)
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000
Page53 3.4.7 Object 类型
Object 的每个实例都具有下列属性和方法。
- constructor
- hasOwnProperty( propertyName )
- isPrototypeOf(object)
- propertyIsEnumerable( propertyName )
- toLocaleString()
- toString()
- valueOf()
Page 84 3.7.1 理解参数
ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。
Page 106 5.2.1 检测数组
自从 ECMAScript 3 做出规定以后,就出现了确定某个对象是不是数组的经典问题。对于一个网页,
或者一个全局作用域而言,使用 instanceof 操作符就能得到满意的结果:
if (value instanceof Array){
// 对数组执行某些操作
}
instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实
际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从
一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自
不同的构造函数。
为了解决这个问题, ECMAScript 5 新增了 Array.isArray() 方法。这个方法的目的是最终确定某
个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。这个方法的用法如下。
if (Array.isArray(value)){
// 对数组执行某些操作
}
支持 Array.isArray() 方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5+ 、 Opera 10.5+ 和 Chrome 。
Page 117 5.3 Date 类型
var now = new Date();
ECMAScript 提供了两个方法: Date.parse() 和 Date.UTC() 。
例如,要为 2004 年 5 月 25 日创建一个日期对象,可以使用下面的代码:
var someDate = new Date(Date.parse("May 25, 2004"))
下面的代码与前 面的例子是等价的:
var someDate = new Date("May 25, 2004");
以下是两个 使用 Date.UTC() 方法的例子:
// GMT 时间 2000 年 1 月 1 日午夜零时
var y2k = new Date(Date.UTC(2000, 0));
// GMT 时间 2005 年 5 月 5 日下午 5:55:55
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));
// 本地时间 2000 年 1 月 1 日午夜零时
var y2k = new Date(2000, 0);
// 本地时间 2005 年 5 月 5 日下午 5:55:55
var allFives = new Date(2005, 4, 5, 17, 55, 55);
ECMAScript 5 添加了 Data.now() 方法,返回表示调用这个方法时的日期和时间的毫秒数。这个方
法简化了使用 Data 对象分析代码的工作。例如:
// 取得开始时间
var start = Date.now();
// 调用函数
doSomething();
// 取得停止时间
var stop = Date.now(),
result = stop – start;
支持 Data.now() 方法的浏览器包括 IE9+ 、 Firefox 3+ 、 Safari 3+ 、 Opera 10.5 和 Chrome 。在不支
持它的浏览器中,使用 + 操作符把 Data 对象转换成字符串,也可以达到同样的目的。
// 取得开始时间
var start = +new Date() ;
// 调用函数
doSomething();
// 取得停止时间
var stop = +new Date(),
result = stop - start;
Date 类型也重写了 toLocaleString() 、 toString() 和 valueOf() 方法;
Page 121 5.4 RegExp 类型
使用正则表达式字面量和使用 RegExp 构造函数创建的正则表达式不一样。在 ECMAScript 3 中,
正则表达式字面量始终会共享同一个 RegExp 实例,而使用构造函数创建的每一个新 RegExp 实例都是 一个新实例。
来看下面的例子。
var re = null,
i;
for (i=0; i < 10; i++){
re = /cat/g;
re.test("catastrophe");
}
for (i=0; i < 10; i++){
re = new RegExp("cat", "g");
re.test("catastrophe");
}
在第一个循环中,即使是循环体中指定的,但实际上只为 /cat/ 创建了一个 RegExp 实例。由于实
例属性(下一节介绍实例属性)不会重置,所以在循环中再次调用 test() 方法会失败。这是因为第一
次调用 test() 找到了 "cat" ,但第二次调用是从索引为 3 的字符(上一次匹配的末尾)开始的,所以
就找不到它了。由于会测试到字符串末尾,所以下一次再调用 test() 就又从开头开始了。
第二个循环使用 RegExp 构造函数在每次循环中创建正则表达式。因为每次迭代都会创建一个新的
RegExp 实例,所以每次调用 test() 都会返回 true 。
ECMAScript 5 明确规定,使用正则表达式字面量必须像直接调用 RegExp 构造函数一样,每次都创
建新的 RegExp 实例。 IE9+ 、 Firefox 4+ 和 Chrome 都据此做出了修改。
RegExp 对象的主要方法是 exec() ,该方法是专门为捕获组而设计的。 exec() 接受一个参数,即
要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null 。
返回的数组虽然是 Array 的实例,但包含两个额外的属性: index 和 input 。其中, index 表示匹配
项在字符串中的位置,而 input 表示应用正则表达式的字符串。
var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
alert(matches.index); // 0
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
alert(matches[2]); // " and baby"
page 126
RegExp 构造函数的属性
长属性名 | 短属性名 | 说 明 |
---|---|---|
input | $_ | 最近一次要匹配的字符串。 Opera 未实现此属性 |
lastMatch | $& | 最近一次的匹配项。 Opera 未实现此属性 |
lastParen | $+ | 最近一次匹配的捕获组。 Opera 未实现此属性 |
leftContext | $` | input 字符串中 lastMatch 之前的文本 |
multiline | $* | 布尔值,表示是否所有表达式都使用多行模式。IE 和 Opera 未实现此属性 |
rightContext | $' | Input 字符串中 lastMatch 之后的文本 |
使用这些属性可以从 exec() 或 test() 执行的操作中提取出更具体的信息。请看下面的例子。|
var text = "this has been a short summer";
var pattern = /(.)hort/g;
/*
* 注意: Opera 不支持 input 、 lastMatch 、 lastParen 和 multiline 属性
* Internet Explorer 不支持 multiline 属性
*/
if (pattern.test(text)){
alert(RegExp.input); // this has been a short summer
alert(RegExp.leftContext); // this has been a
alert(RegExp.rightContext); // summer
alert(RegExp.lastMatch); // short
alert(RegExp.lastParen); // s
alert(RegExp.multiline); // false
}
如前所述,例子使用的长属性名都可以用相应的短属性名来代替。只不过,由于这些短属性名大都
不是有效的 ECMAScript 标识符,因此必须通过方括号语法来访问它们,如下所示。
if (pattern.test(text)){
alert(RegExp.$_); // this has been a short summer
alert(RegExp["$`"]); // this has been a
alert(RegExp["$'"]); // summer
alert(RegExp["$&"]); // short
alert(RegExp["$+"]); // s
alert(RegExp["$*"]); // false
}
除了上面介绍的几个属性之外,还有 多达 9 个用于存储捕获组的构造函数属性 。访问这些属性的语
法是 RegExp.$1 、 RegExp.$2 … RegExp.$9 ,分别用于存储第一、第二……第九个匹配的捕获组。在
调用 exec() 或 test() 方法时,这些属性会被自动填充。然后,我们就可以像下面这样来使用它们。
var text = "this has been a short summer";
var pattern = /(..)or(.)/g;
if (pattern.test(text)){
alert(RegExp.$1); //sh
alert(RegExp.$2); //t
}
page 132 5.5.4 函数内部属性
在函数内部,有两个特殊的对象: arguments 和 this 。
还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
ECMAScript 5 也规范化了另一个函数对象的属性: caller 。
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
以上代码会导致警告框中显示 outer() 函数的源代码。因为 outer() 调用了 inter() ,所以
inner.caller 就指向 outer() 。为了实现更松散的耦合,也可以通过 arguments.callee.caller
来访问相同的信息。
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
当函数在严格模式下运行时,访问 arguments.callee 会导致错误。 ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误, 严格模式还有一个限制:不能为函数的 caller 属性赋值,否则会导致错误。
Page 134 5.5.5 函数属性和方法
前面曾经提到过, ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个 属性: length 和 prototype 。其中, length 属性表示函数希望接收的命名参数的个数,如下面的例 子所示。
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0
在 ECMAScript 5 中, prototype 属性是不可枚举的,因此使用 for-in 无法发现。
每个函数都包含两个非继承而来的方法: apply() 和 call() 。
apply() 方法接收两个参数:一个 是在其中运行函数的作用域,另一个是参数数组。
其中,第二个参数可以是 Array 的实例,也可以是 arguments 对象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入 arguments 对象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 传入数组
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window 。
除非明确把函数添加到某个对象或者调用 apply() 或 call() ,否则 this 值将是 undefined 。
call() 方法与 apply() 方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()
方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用
call() 方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20
事实上,传递参数并非 apply() 和 call() 真正的用武之地;它们真正强大的地方是能够扩充函数
赖以运行的作用域。 下面来看一个例子。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
ECMAScript 5 还定义了一个方法: bind() 。这个方法会创建一个函数的实例,其 this 值会被绑
定到传给 bind() 函数的值 。例如:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
支持 bind() 方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5.1+ 、 Opera 12+ 和 Chrome 。
Page 145
ECMAScript 提供了 replace() 方法。如果第二个参数是字符串,那么还可以使用一些特殊的字符序列,将正则表达式操作得到的值插入
到结果字符串中。下表列出了 ECMAScript 提供的这些特殊的字符序列。
字符序列 | 替换文本 |
---|---|
$$ | $ |
$$ | |
$& | 匹配整个模式的子字符串。与 RegExp.lastMatch 的值相同 |
$' | 匹配的子字符串之前的子字符串。与 RegExp.leftContext 的值相同 |
$` | 匹配的子字符串之后的子字符串。与 RegExp.rightContext 的值相同 |
$n | 匹配第 n 个捕获组的子字符串,其中 n 等于 0 ~ 9 。例如, $1 是匹配第一个捕获组的子字符串, $2 是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串 |
$nn | 匹配第 nn 个捕获组的子字符串,其中 nn 等于 01 ~ 99 。例如, $01 是匹配第一个捕获组的子字符串, $02是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串 |
匹配整个模式的子字符串。与 RegExp.lastMatch 的值相同 | |
匹配的子字符串之前的子字符串。与 RegExp.leftContext 的值相同 | |
字符序列 | |
-------- | --------- |
匹配的子字符串之后的子字符串。与 RegExp.rightContext 的值相同 | |
$n | 匹配第 n 个捕获组的子字符串,其中 n 等于 0 ~ 9 。例如, $1 是匹配第一个捕获组的子字符串, $2 是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串 | |
$nn |匹配第 nn 个捕获组的子字符串,其中 nn 等于 01 ~ 99 。例如, $01 是匹配第一个捕获组的子字符串, $02是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串 | |
通过这些特殊的字符序列, 可以使用最近一次匹配结果中的内容,如下面的例子所示。 |
var text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
alert(result); //word (cat), word (bat), word (sat), word (fat)
replace() 方法的第二个参数也可以是一个函数
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match, pos, originalText){
switch(match){
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
alert(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
//<p class="greeting">Hello world!</p>
Page 157 第 6 章 面向对象的程序设计
ECMAScript 中有两种属性:数据属性和访问器属性。
- 数据属性
Configurable、Enumerable、Writable、Value
要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty() 方法。
建议不要在 IE8 中使用 Object.defineProperty() 方法。
- 访问器属性
Configurable、Enumerable、Get、Set
访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
支持 ECMAScript 5 的这个方法的浏览器有 IE9+ ( IE8 只是部分实现)、 Firefox 4+ 、 Safari 5+ 、 Opera
12+ 和 Chrome 。 在 这 个 方 法 之 前 , 要 创 建 访 问 器 属 性 , 一 般 都 使 用 两 个 非 标 准 的 方 法 :
defineGetter() 和 defineSetter() 。
由于为对象定义多个属性的可能性很大, ECMAScript 5 又定义了一个 Object.defineProperties() 方法。
支持 Object.defineProperties() 方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5+ 、 Opera 12+ 和
Chrome 。
Page 161 6.1.3
使用 ECMAScript 5 的 Object.getOwnPropertyDescriptor() 方法,可以取得给定属性的描述符。
支持这个方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5+ 、 Opera 12+ 和 Chrome 。
alert(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5 增加了一个新方法,叫 Object.getPrototypeOf() ,在所有支持的实现中,这个
方法返回 [[Prototype]] 的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
支持这个方法的浏览器有 IE9+ 、 Firefox 3.5+ 、 Safari 5+ 、 Opera 12+ 和 Chrome 。
Page 168
使用 delete 操作符则可以完全删 除实例属性,从而让我们能够重新访问原型中的属性,如下所示。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg" —— 来自实例
alert(person2.name); //"Nicholas" —— 来自原型
delete person1.name;
alert(person1.name); //"Nicholas" —— 来自原型
使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。
alert(person1.hasOwnProperty("name")); //false
- 原型与 in 操作符
有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时, in 操作符会在通
过对象能够访问给定属性时返回 true ,无论该属性存在于实例中还是原型中。看一看下面的例子。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" —— 来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //"Nicholas" —— 来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas" —— 来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
同时使用 hasOwnProperty() 方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于
原型中,如下所示。
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的( enumerated )属性,其中
既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将
[[Enumerable]] 标记为 false 的属性)的实例属性也会在 for-in 循环中返回,因为根据规定,所
有开发人员定义的属性都是可枚举的—— 只有在 IE8 及更早版本中例外。
IE 早期版本的实现中存在一个 bug ,即屏蔽不可枚举属性的实例属性不会出现在 for-in 循环中。
例如:
var o = {
toString : function(){
return "My Object";
}
};
for (var prop in o){
if (prop == "toString"){
alert("Found toString"); // 在 IE 中不会显示
}
}
要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys() 方法。这个方法
接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。例如:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
如果你想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames()
方法。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
//注意结果中包含了不可枚举的 constructor 属性。 Object.keys() 和 Object.getOwnPropertyNames() 方法都可以用来替代
// for-in 循环。 支持这两个方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5+ 、 Opera 12+ 和 Chrome 。
constructor 属性不再指向 Person
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
如果 constructor 的值真的很重要,可以像下面这样特意将它设 置回适当的值。
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它 们引用的仍然是最初的原型。
6.2.4 组合使用构造函数模式和原型模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
这种构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自
定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。
6.2.5 动态原型模式
function Person(name, age, job){
// 属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
使用动态原型模式时,不能使用对象字面量重写原型。前面已经解释过了,如果
在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
6.2.6 寄生构造函数模式
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
Page179
6.2.7 稳妥构造函数模式
function Person(name, age, job){
// 创建要返回的对象
var o = new Object();
// 可以在这里定义私有变量和函数
// 添加方法
o.sayName = function(){
alert(name);
};
// 返回对象
return o;
}
注意,在以这种模式创建的对象中, 除了使用 sayName() 方法之外,没有其他办法访问 name 的值。
可以像下面使用稳妥的 Person 构造函数。
Page180
6.3 继承
6.3.1 原型链
6.3.2 借用构造函数
6.3.3 组合继承
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
Page 188
ECMAScript 5 通过新增 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一
个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,
Object.create() 与 object() 方法的行为相同。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
支持 Object.create() 方法的浏览器有 IE9+ 、 Firefox 4+ 、 Safari 5+ 、 Opera 12+ 和 Chrome 。
6.3.5 寄生式继承
6.3.6 寄生组合式继承
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
第 7 章 函数表达式
在严格模式下,不能通过脚本访问 arguments.callee ,访问这个属性会导致错误。不过,可
以使用命名函数表达式来达成相同的结果。
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
});
Page196
7.2 闭包
闭包 是指有权访问另一个 函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
Page 199
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
Page 203
用作块级作用域(通常称为 私有作用域 )的匿名函数的语法如下所示。
(function(){
// 这里是块级作用域
})();
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); // 导致一个错误!
}
Page 204
7.4 私有变量
有权访问私有变量和私有函数的公有方法称为 特权方法
function MyObject(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
7.4.1 静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下所示。
(function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 构造函数
MyObject = function(){
};
// 公有 / 特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
8.2.2 位置操作
location.assign("http://www.wrox.com");
这样,就可以立即打开新 URL 并在浏览器的历史记录中生成一条记录。如果是将 location.href
或 window.location 设置为一个 URL 值,也会以该值调用 assign() 方法。例如,下列两行代码与
显式调用 assign() 方法的效果完全一样。
window.location = "http://www.wrox.com";
location.href = "http://www.wrox.com";
在这些改变浏览器位置的方法中,最常用的是设置 location.href 属性。
当通过上述任何一种方式修改 URL 之后,浏览器的历史记录中就会生成一条新记录,因此用户通
过单击“后退”按钮都会导航到前一个页面。要禁用这种行为,可以使用 replace() 方法。这个方法
只接受一个参数,即要导航到的 URL ;结果虽然会导致浏览器位置改变,但不会在历史记录中生成新记
录。在调用 replace() 方法之后,用户不能回到前一个页面,来看下面的例子:
location.replace("http://www.wrox.com/");
location.reload(); // 重新加载(有可能从缓存中加载)
location.reload(true); // 重新加载(从服务器重新加载)
第 10 章 DOM--page 250
对 arguments 对象使用 Array.prototype.slice() 方法可以
将其转换为数组。而采用同样的方法,也可以将 NodeList 对象转换为数组。来看下面的例子:
// 在 IE8 及之前版本中无效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
除 IE8 及更早版本之外,这行代码能在任何浏览器中运行。由于 IE8 及更早版本将 NodeList
实现为一个 COM 对象,而我们不能像使用 JScript 对象那样使用这种对象,因此上面的代码会导致
错误。要想在 IE 中将 NodeList 转换为数组,必须手动枚举所有成员。下列代码在所有浏览器中都
可以运行:
function convertToArray(nodes){
var array = null;
try {
array = Array.prototype.slice.call(nodes, 0); // 针对非 IE 浏览器
} catch (ex) {
array = new Array();
for (var i=0, len=nodes.length; i < len; i++){
array.push(nodes[i]);
}
}
return array;
}
page 275
但是,下面的代码在除 IE7 及更早版本之外的所有浏览器中都将返回 null 。
var div = document.getElementById("mydiv"); // 无效的 ID (在 IE7 及更早版本中可以)
IE8 及较低版本不区分 ID 的大小写,因此 "myDiv" 和 "mydiv" 会被当作相同的元素 ID 。
var radios = document.getElementsByName("color");
与 getElementsByTagName() 类似, getElementsByName() 方法也会返回一个 HTMLCollectioin 。
但是,对于这里的单选按钮来说, namedItem() 方法则只会取得第一项(因为每一项的 name 特性都相同) 。
4. 特殊集合
除了属性和方法, document 对象还有一些特殊的集合。这些集合都是 HTMLCollection 对象,
为访问文档常用的部分提供了快捷方式,包括:
- document.anchors ,包含文档中所有带name特性的<a>元素;
- document.applets ,包含文档中所有的 <applet> 元素,因为不再推荐使用 <applet> 元素,
所以这个集合已经不建议使用了;
- document.forms ,包含文档中所有的 <form> 元素,与 document.getElementsByTagName("form")
得到的结果相同;
- document.images ,包含文档中所有的 <img> 元素,与 document.getElementsByTagName
("img") 得到的结果相同;
- document.links ,包含文档中所有带href特性的<a>元素。
这个特殊集合始终都可以通过 HTMLDocument 对象访问到, 而且, 与 HTMLCollection 对象类似,
集合中的项也
page 280
HTML 元素
所有 HTML 元素都由 HTMLElement 类型表示,不是直接通过这个类型,也是通过它的子类型来表
示。 HTMLElement 类型直接继承自 Element 并添加了一些属性。添加的这些属性分别对应于每个 HTML
元素中都存在的下列标准特性。
id ,元素在文档中的唯一标识符。
title ,有关元素的附加说明信息,一般通过工具提示条显示出来。
lang ,元素内容的语言代码,很少使用。
dir ,语言的方向,值为 "ltr" ( left-to-right ,从左至右)或 "rtl" ( right-to-left ,从右至左) ,
也很少使用。
className ,与元素的 class 特性对应,即为元素指定的 CSS 类。没有将这个属性命名为 class ,
是因为 class 是 ECMAScript 的保留字(有关保留字的信息,请参见第 1 章)。
上述这些属性都可以用来取得或修改相应的特性值。以下面的 HTML 元素为例:
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
HTMLElementsExample01.htm
元素中指定的所有信息,都可以通过下列 JavaScript 代码取得:
var div = document.getElementById("myDiv");
alert(div.id); //"myDiv""
alert(div.className); //"bd"
alert(div.title); //"Body text"
alert(div.lang); //"en"
- 创建元素
使用 document.createElement() 方法可以创建新元素。这个方法只接受一个参数,即要创建元
素的标签名。这个标签名在 HTML 文档中不区分大小写,而在 XML (包括 XHTML )文档中,则是区
分大小写的。例如,使用下面的代码可以创建一个
var div = document.createElement("div");
在使用 createElement() 方法创建新元素的同时,也为新元素设置了 ownerDocuemnt 属性。此
时,还可以操作元素的特性,为它添加更多子节点,以及执行其他操作。来看下面的例子。
div.id = "myNewDiv";
div.className = "box";
在新元素上设置这些特性只是给它们赋予了相应的信息。由于新元素尚未被添加到文档树中,因此
设置这些特性不会影响浏览器的显示。要把新元素添加到文档树,可以使用 appendChild() 、 insertBefore() 或 replaceChild() 方法。下面的代码会把新创建的元素添加到文档的 元素中。
第11章 DOM 扩展
已完全支持 Selectors API Level 1
的浏览器有 IE 8+ 、 Firefox 3.5+ 、 Safari 3.1+ 、 Chrome 和 Opera 10+ 。
querySelector() 方法接收一个 CSS 选择符,返回与该模式匹配的第一个元素,如果没有找到匹
配的元素,返回 null 。
// 取得 body 元素
var body = document.querySelector("body");
// 取得 ID 为 "myDiv" 的元素
var myDiv = document.querySelector("#myDiv");
// 取得类为 "selected" 的第一个元素
var selected = document.querySelector(".selected");
// 取得类为 "button" 的第一个图像元素
var img = document.body.querySelector("img.button");
querySelectorAll() 方法接收的参数与 querySelector() 方法一样,都是一个 CSS 选择符,但
返回的是所有匹配的元素而不仅仅是一个元素。
// 取得某 <div> 中的所有 <em> 元素(类似于 getElementsByTagName("em") )
var ems = document.getElementById("myDiv").querySelectorAll("em");
// 取得类为 "selected" 的所有元素
var selecteds = document.querySelectorAll(".selected");
// 取得所有 <p> 元素中的所有 <strong> 元素
var strongs = document.querySelectorAll("p strong");
要取得返回的 NodeList 中的每一个元素,可以使用 item() 方法,也可以使用方括号语法,比如:
var i, len, strong;
for (i=0, len=strongs.length; i < len; i++){
strong = strongs[i]; // 或者 strongs.item(i)
strong.className = "important";
}
11.2 元素遍历
对于元素间的空格, IE9 及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这样,
就导致了在使用 childNodes 和 firstChild 等属性时的行为不一致。为了弥补这一差异,而同时又保
持 DOM 规范不变, Element Traversal 规范( www.w3.org/TR/ElementTraversal/ )新定义了一组属性。
Element Traversal API 为 DOM 元素添加了以下 5 个属性。
childElementCount :返回子元素(不包括文本节点和注释)的个数。
firstElementChild :指向第一个子元素; firstChild 的元素版。
lastElementChild :指向最后一个子元素; lastChild 的元素版。
previousElementSibling :指向前一个同辈元素; previousSibling 的元素版。
nextElementSibling :指向后一个同辈元素; nextSibling 的元素版。
支持 Element Traversal 规范的浏览器有 IE 9+ 、 Firefox 3.5+ 、 Safari 4+ 、 Chrome 和 Opera 10+ 。
11.3 HTML5
- getElementsByClassName() 方法
getElementsByClassName() 方法接收一个参数,即一个包含一或多个类名的字符串,返回带有
指定类的所有元素的 NodeList 。
// 取得所有类中包含 "username" 和 "current" 的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");
// 取得 ID 为 "myDiv" 的元素中带有类名 "selected" 的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");
支持 getElementsByClassName() 方法的浏览器有 IE 9+ 、 Firefox 3+ 、 Safari 3.1+ 、 Chrome 和
Opera 9.5+ 。
2. classList 属性
在操作类名时,需要通过 className 属性添加、删除和替换类名。因为 className 中是一个字
符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值。比如,以下面的 HTML 代
码为例。
// 删除 "user" 类
// 首先,取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/);
// 找到要删的类名
var pos = -1,
i,
len;
for (i=0, len=classNames.length; i < len; i++){
if (classNames[i] == "user"){
pos = i;
break;
}
}
// 删除类名
classNames.splice(i,1);
// 把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" ");
为了从
法替换类名并确认元素中是否包含该类名。添加类名可以通过拼接字符串完成,但必须要通过检测确定
不会多次添加相同的类名。很多 JavaScript 库都实现了这个方 法,以简化这些操作。
HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加
classList 属性。这个 classList 属性是新集合类型 DOMTokenList 的实例。与其他 DOM 集合类似,
DOMTokenList 有一个表示自己包含多少元素的 length 属性,而要取得每个元素可以使用 item() 方
法,也可以使用方括号语法。此外,这个新类型还定义如下方法。
add( value ) :将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
contains( value ) :表示列表中是否存在给定的值,如果存在则返回 true ,否则返回 false 。
remove( value ) :从列表中删除给定的字符串。
toggle( value ) :如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
这样,前面那么多行代码用下面这一行代码就可以代替了:
div.classList.remove("user");
支持 classList 属性的浏览器有 Firefox 3.6+ 和 Chrome 。
11.3.2 焦点管理
var button = document.getElementById("myButton");
button.focus();
alert( document.activeElement === button); //true
button.focus();
alert( document.hasFocus() ); //true
实现了这两个属性的浏览器的包括 IE 4+ 、 Firefox 3+ 、 Safari 4+ 、 Chrome 和 Opera 8+ 。
11.3.3 HTMLDocument 的变化
Document 的 readyState 属性有两个可能的值:
loading ,正在加载文档;
complete ,已经加载完文档。
if ( document.readyState == "complete" ){
// 执行操作
}
支持 readyState 属性的浏览器有 IE4+ 、 Firefox 3.6+ 、 Safari 、 Chrome 和 Opera 9+ 。
11.3.4 字符集属性
alert(document.charset); //"UTF-16"
document.charset = "UTF-8";