# 《JavaScript 高级程序设计》读书笔记 ## 目录
第三章 基本语法
第四章 变量、作用域和内存问题
第五章 应用类型
    1. Array 类型
    2. RegExp 类型
    3. Function 类型
    4. String 类型
第六章 面向对象的程序设计
    6.1 理解对象
         1. 属性类型
        2. 读取属性
    6.2 创建对象
        1. 构造函数模式
        2. 原型模式
        3.组合使用构造函数和原型模式(主要使用方式)
    6.3 继承
        1. 原型链
        2. 组合继承(主要使用方式)
        3. 原型链继承
第七章 函数表达式
第八章 BOM
    1.window 对象
    2.location 对象
第十章 DOM
    1. Node 类型
    2.Document类型
    3.Element类型
    4.Text类型
    5.动态创建表格
第十一章 DOM扩展
第十二章 DOM2和DOM3
第十三 事件
    13.1 DOM事件流
    13.2 事件处理程序
        1. DOM0 级事件处理程序
        2. DOM2 级事件处理程序
        3.兼容IE
    13.3 事件处理程序
    13.4 事件类型
        1.UI 事件
        2.焦点事件
        3.鼠标和滚轮事件
        4.键盘与文本事件
        5.变动事件
        6.HTML5事件
    第十四章 表单脚本
    14.1 表单基础知识
        1.提交表单
        2.重置表单
        3.表单字段
            3.1 共有的表单字段属性
            3.2 共有的表单字段方法
            3.3 共有的表单字段事件
    14.2 文本框脚本
        1. 选择文本
        2. 过滤输入(**keypress**)
        3.自动切换焦点
        4. HTML5 验证约束API
    14.3 选择框脚本
        1.选择选项
        2.增加选项
        3.移除选项
        4.移动和重排选项
    14.4 表单序列化
    第十六章 HTML5 脚本编程
    1.媒体元素
    第十七章 错误处理
第二十章 JSON
    20.1 语法
    20.2 json序列化与反序列化
        1.序列化
        2.序列化选项
            2.1 过滤结果
            2.2 toJSON()方法
            2.3 解析选项
第二十二章 高级技巧
    22.1 高级函数
        1.安全类型检测
        2.作用域安全的构造函数
        3.函数绑定
    22.2 防篡改对象
        1.不可扩展对象
        3.冻结的对象
    22.3 高级定时器
        1.函数节流
第二十三章 离线应用与客户端存储
    23.1 离线检测
    23.2 数据存储
        1.Cookie
        2.web存储机制
            2.1 Storage 类型
            2.2 sessionStorage 对象
            2.3 localStorage 对象
## 正文
## 第三章 基本语法 1. 由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。同样地,如果浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数,如下面的例子所示: ```javascript var floatNum1 = 1.; // 小数点后面没有数字——解析为 1 var floatNum2 = 10.0; // 整数——解析为 10 ``` 2. 为了消除在使用parseInt() 函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(即多少进制)。如果知道要解析的值是十六进制格式的字符串,那么指定基数 16 作为第二个参数,可以保证得到正确的结果,例如:var num = parseInt("0xAF", 16); //175实际上,如果指定了 16 作为第二个参数,字符串可以不带前面的"0x",如下所示: ```javascript var num1 = parseInt("AF", 16); //175 var num2 = parseInt("AF"); //NaN ``` 3. 多数情况下,调用 toString()方法不必传递参数。但是,在调用数值的 toString()方法时,可以传递一个参数:输出数值的基数。默认情况下, toString()方法以十进制格式返回数值的字符串表示。而通过传递基数,toString()可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。下面给出几个例子: ```javascript var num = 10; alert(num.toString()); // "10" alert(num.toString(2)); // "1010" alert(num.toString(8)); // "12" alert(num.toString(10)); // "10" alert(num.toString(16)); // "a" ``` 4. 在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。 String()函数遵循下列转换规则: + 如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果; - 如果值是 null,则返回"null"; - 如果值是 undefined,则返回"undefined"。 下面再看几个例子: ```javascript var value1 = 10; var value2 = true; var value3 = null; var value4; alert(String(value1)); // "10" alert(String(value2)); // "true" alert(String(value3)); // "null" alert(String(value4)); // "undefined" ``` 5. Object 的每个实例都具有下列属性和方法。 + **constructor**:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是 Object()。 + **hasOwnProperty(propertyName)**:用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如: o.hasOwnProperty("name"))。 + **isPrototypeOf(object)**:用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原型)。 + **propertyIsEnumerable(propertyName)**:用于检查给定的属性是否能够使用 for-in 语句(本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符 串形式指定。 + **toLocaleString()**:返回对象的字符串表示,该字符串与执行环境的地区对应。 + **toString()**:返回对象的字符串表示。 + **valueOf()**:返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。

## 第四章 变量、作用域和内存问题 1. **ECMAScript 中所有函数的参数都是按值传递的。** ```javascript function setName(obj) { obj.name = "Nicholas"; } var person = new Object(); setName(person); alert(person.name); //"Nicholas" ``` ```javascript function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg"; } var person = new Object(); setName(person); alert(person.name); //"Nicholas" ``` ## 第五章 应用类型 ### 1. Array 类型 1. 数组的 length 属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。请看下面的例子: ```javascript var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组 colors.length = 2; alert(colors[2]); //undefined ``` 这个例子中的数组 colors 一开始有 3 个值。将其 length 属性设置为 2 会移除最后一项(位置为2 的那一项),结果再访问 colors[2]就会显示 undefined 了。如果将其 length 属性设置为大于数组项数的值,则新增的每一项都会取得 undefined 值,如下所示: ```javascript var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组 colors.length = 4; alert(colors[3]); //undefined ``` 2. 数组常用方法 ```javascript var colors = ["red", "blue", "green"]; alert(colors.toString()); // red,blue,green alert(colors.valueOf()); // red,blue,green alert(colors); // red,blue,green alert(colors.join(",")); //red,green,blue alert(colors.join("||")); //red||green||blue var colors = new Array(); // 创建一个数组 var count = colors.push("red", "green"); // 推入两项 alert(count); //2 count = colors.push("black"); // 推入另一项 alert(count); //3 var item = colors.pop(); // 取得最后一项 alert(item); //"black" alert(colors.length); //2 var item = colors.shift(); //取得第一项 alert(item); //"red" alert(colors.length); //2 var colors = new Array(); //创建一个数组 var count = colors.unshift("red", "green"); //推入两项 alert(count); //2 count = colors.unshift("black"); //推入另一项 alert(count); //3 var item = colors.pop(); //取得最后一项 alert(item); //"green" alert(colors.length); //2 ``` 3. 数组重排序 在默认情况下, `sort()`方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。为了实现排序, sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值, sort()方法比较的也是字符串,如下所示。 ```javascript var values = [1, 2, 3, 4, 5]; values.reverse(); alert(values); //5,4,3,2,1 var values = [0, 1, 5, 10, 15]; values.sort(); alert(values); //0,1,10,15,5 //传入比较函数 function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } var values = [0, 1, 5, 10, 15]; values.sort(compare); alert(values); // 15,10,5,1,0 ``` 4. 数组操作方法 `splice()`的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下 3 种。 - **删除**:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2)会删除数组中的前两项。 - **插入**:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、 0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置 2 开始插入字符串"red"和"green"。 - **替换**:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串"red"和"green"。 `splice()`方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。下面的代码展示了上述 3 种使用 splice()方法的方式。 ```javascript //基于当前数据创建新的数组 var colors = ["red", "green", "blue"]; var colors2 = colors.concat("yellow", ["black", "brown"]); alert(colors); //red,green,blue alert(colors2); //red,green,blue,yellow,black,brown //slice var colors = ["red", "green", "blue", "yellow", "purple"]; var colors2 = colors.slice(1); var colors3 = colors.slice(1,4); alert(colors2); //green,blue,yellow,purple alert(colors3); //green,blue,yellow var colors = ["red", "green", "blue"]; var removed = colors.splice(0,1); // 删除第一项 alert(colors); // green,blue alert(removed); // red,返回的数组中只包含一项 removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项 alert(colors); // green,yellow,orange,blue alert(removed); // 返回的是一个空数组 removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项 alert(colors); // green,red,purple,orange,blue alert(removed); // yellow,返回的数组中只包含一项 ``` 5. 数组迭代方法 ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能会也可能不会影响方法的返回值。以下是这 5 个迭代方法的作用。 + **every()**:对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。 + **filter()**:对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。 + **forEach()**:对数组中的每一项运行给定函数。这个方法没有返回值。 + **map()**:对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。 + **some()**:对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。 以上方法都不会修改数组中的包含的值。 ```javascript var numbers = [1,2,3,4,5,4,3,2,1]; var everyResult = numbers.every(function(item, index, array){ return (item > 2); }); alert(everyResult); //false var someResult = numbers.some(function(item, index, array){ return (item > 2); }); alert(someResult); //true var filterResult = numbers.filter(function(item, index, array){ return (item > 2); }); alert(filterResult); //[3,4,5,4,3] var mapResult = numbers.map(function(item, index, array){ return item * 2; }); alert(mapResult); //[2,4,6,8,10,8,6,4,2] numbers.forEach(function(item, index, array){ //执行某些操作 }); ``` 6. 归并方法 ECMAScript 5 还新增了两个归并数组的方法: `reduce()`和 `reduceRight()`。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中, reduce()方法从数组的第一项开始,逐个遍历到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。传给 reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。 ```javascript var values = [1,2,3,4,5]; var sum = values.reduce(function(prev, cur, index, array){ return prev + cur; }); alert(sum); //15 var sum = values.reduceRight(function(prev, cur, index, array){ return prev + cur; }); alert(sum); //15 ``` ### 2. RegExp 类型 ``` var expression = / pattern / flags ; ``` 1 . 其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。 正则表达式的匹配模式支持下列 3 个标志。 + **g**:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止; + **i**:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写; + **m**:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。 模式中使用的所有元字符都必须转义。正则表达式中的元字符包括: ( [ { \ ^ $ | ) ? * + .]} ```javascript /* * 匹配字符串中所有"at"的实例 */ var pattern1 = /at/g; /* * 匹配第一个"bat"或"cat",不区分大小写 */ var pattern2 = /[bc]at/i; /* * 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写 */ var pattern3 = /.at/gi; /* * 匹配第一个"bat"或"cat",不区分大小写 */ var pattern1 = /[bc]at/i; /* * 匹配第一个" [bc]at",不区分大小写 */ var pattern2 = /\[bc\]at/i; /* * 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写 */ var pattern3 = /.at/gi; /* * 匹配所有".at",不区分大小写 */ var pattern4 = /\.at/gi; /* * 匹配第一个"bat"或"cat",不区分大小写 */ var pattern1 = /[bc]at/i; /* * 与 pattern1 相同,只不过是使用构造函数创建的 */ var pattern2 = new RegExp("[bc]at", "i"); ``` 2. RegExp 对象的主要方法是 `exec()`,该方法是专门为捕获组而设计的。 exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性: index 和 input。其中, index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。 ```javascript 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" ``` 对于 exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用 exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用 exec()则都会在字符串中继续查找新匹配项,如下面的例子所示。 ```javascript var text = "cat, bat, sat, fat"; var pattern1 = /.at/; var matches = pattern1.exec(text); alert(matches.index); //0 alert(matches[0]); //cat alert(pattern1.lastIndex); //0 matches = pattern1.exec(text); alert(matches.index); //0 alert(matches[0]); //cat alert(pattern1.lastIndex); //0 var pattern2 = /.at/g; var matches = pattern2.exec(text); alert(matches.index); //0 alert(matches[0]); //cat alert(pattern2.lastIndex); //3 matches = pattern2.exec(text); alert(matches.index); //5 alert(matches[0]); //bat alert(pattern2.lastIndex); //8 var text = "000-00-0000"; var pattern = /\d{3}-\d{2}-\d{4}/; if (pattern.test(text)){ alert("The pattern was matched."); } ``` 3. RegExp构造函数属性 | 长属性名 | 短属性名 | 说 明 | | :----------: | :------: | :----------------------------------------------------------: | | input | $_ | 最近一次要匹配的字符串。 Opera未实现此属性 | | lastMatch | $& | 最近一次的匹配项。 Opera未实现此属性 | | lastParen | $+ | 最近一次匹配的捕获组。 Opera未实现此属性 | | leftContext | $` | input字符串中lastMatch之前的文本 | | multiline | $* | 布尔值,表示是否所有表达式都使用多行模式。 IE和Opera未实现此属性 | | rightContext | $' | Input字符串中lastMatch之后的文本 | ```javascript 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 } 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 } //RegExp.$1、 RegExp.$2…RegExp.$9,分别用于存储第一、第二……第九个匹配的捕获组。 var text = "this has been a short summer"; var pattern = /(..)or(.)/g; if (pattern.test(text)){ alert(RegExp.$1); //sh alert(RegExp.$2); //t } ``` ### 3. Function 类型 1. 每个函数都包含两个非继承而来的方法: `apply()`和 `call()`。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先, apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。例如: ```javascript 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 /*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 ``` 2. ECMAScript 5 还定义了一个方法: `bind()`。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。例如: ```javascript window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } var objectSayColor = sayColor.bind(o); objectSayColor(); //blue ``` ### 4. String 类型 1. ECMAScript还提供了三个基于子字符串创建新字符串的方法: `slice()`、 `substr()`和 `substring()`。这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。具体来说, slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而 substr()的第二个参数指定的则是返回的字符个数。如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置。与concat()方法一样, slice()、 substr()和 substring()也不会修改字符串本身的值——它们只是返回一个基本类型的字符串值,对原始字符串没有任何影响。请看下面的例子。 ```javascript var stringValue = "hello world"; alert(stringValue.slice(3)); //"lo world" alert(stringValue.substring(3)); //"lo world" alert(stringValue.substr(3)); //"lo world" alert(stringValue.slice(3, 7)); //"lo w" alert(stringValue.substring(3,7)); //"lo w" alert(stringValue.substr(3, 7)); //"lo worl" ``` 2. 字符串的模式匹配 ```javascript //match var text = "cat, bat, sat, fat"; var pattern = /.at/; //与 pattern.exec(text)相同 var matches = text.match(pattern); alert(matches.index); //0 alert(matches[0]); //"cat" alert(pattern.lastIndex); //0 //search var text = "cat, bat, sat, fat"; var pos = text.search(/at/); alert(pos); //1 //replace ar text = "cat, bat, sat, fat"; var result = text.replace("at", "ond"); alert(result); //"cond, bat, sat, fat" result = text.replace(/at/g, "ond"); alert(result); //"cond, bond, sond, fond" var text = "cat, bat, sat, fat"; result = text.replace(/(.at)/g, "word ($1)"); alert(result); //word (cat), word (bat), word (sat), word (fat) function htmlEscape(text){ return text.replace(/[<>"&]/g, function(match, pos, originalText){ switch(match){ case "<": return "<"; case ">": return ">"; case "&": return "&"; case "\"": return """; } }); } alert(htmlEscape("

Hello world!

")); //<p class="greeting">Hello world!</p> /*split(),这个方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象(这个方法不会将字符串看成正则表达式)。 split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。请看下面的例子 */ var colorText = "red,blue,green,yellow"; var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"] var colors2 = colorText.split(",", 2); //["red", "blue"] var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""] ``` ## 第六章 面向对象的程序设计 ### 6.1 理解对象 #### 1. 属性类型 1. ECMAScript 中有两种属性:数据属性和访问器属性。 1. **数据属性** 数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性。 + **[[Configurable]]**:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + **[[Enumerable]]**:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + **[[Writable]]**:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + **[[Value]]**:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。 ```javascript var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas" var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" delete person.name; alert(person.name); //"Nicholas" //一旦把属性定义为不可配置的,就不能再把它变回可配置了 var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas" }); //抛出错误 Object.defineProperty(person, "name", { configurable: true, value: "Nicholas" }); ``` 2. **访问器属性** 访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的) + **[[Configurable]]**:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。 + **[[Enumerable]]**:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true。 + **[[Get]]**:在读取属性时调用的函数。默认值为 undefined。 + **[[Set]]**:在写入属性时调用的函数。默认值为 undefined。 **访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。** ```javascript var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition); //2 // 定义多个属性 var book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 }, year: { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); ``` #### 2. 读取属性 1. 使用 ECMAScript 5 的 `Object.getOwnPropertyDescriptor()`方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。 ```javascript var book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 }, year: { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); alert(descriptor.value); //2004 alert(descriptor.configurable); //false alert(typeof descriptor.get); //"undefined" var descriptor = Object.getOwnPropertyDescriptor(book, "year"); alert(descriptor.value); //undefined alert(descriptor.enumerable); //false alert(typeof descriptor.get); //"function" ``` ### 6.2 创建对象 #### 1. 构造函数模式 ```javascript function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true //构造函数模式的缺陷 alert(person1.sayName == person2.sayName); //false //解决办法 function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); ``` #### 2. 原型模式 ```javascript 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(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true alert(Person.prototype.constructor=== Person); //true alert(Person.prototype == person1.__proto__); //true ```


```javascript //覆盖原型中的属性 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"—— 来自原型 ``` ```javascript //属性恢复 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"—— 来自原型 ``` ```javascript //使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。 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 person1.name = "Greg"; alert(person1.name); //"Greg"—— 来自实例 alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas"—— 来自原型 alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas"—— 来自原型 alert(person1.hasOwnProperty("name")); //false ``` ```javascript // 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 ``` 在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将[[Enumerable]]标记为 false 的属性)的实例属性也会在 for-in 循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。 ```javascript //获取所有可枚举的属性 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" // 获取所有属性 不论是否可以枚举 var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor,name,age,job,sayName" ``` ```javascript // 更简单的原型语法 function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true // 弊端 function Person(){ } //这种赋值方式 相当于把Person.prototype指向一个用字面量创建的对象,相当于下面的创建结果 Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; console.log(Person.prototype.constructor); //[Function: Object] var obj = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; console.log(obj.constructor); //[Function: Object] //更好的创建方式 以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的, function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; //最好的创建方式 function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器 Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person }); ``` ```javascript // 扩展原生对象的原型方式 alert(typeof Array.prototype.sort); //"function" alert(typeof String.prototype.substring); //"function" String.prototype.startsWith = function (text) { return this.indexOf(text) == 0; }; var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true ``` ```javascript // 原生模式的弊端 function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true ``` #### 3.组合使用构造函数和原型模式(主要使用方式) ```javascript 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 ``` ### 6.3 继承 #### 1. 原型链 ```javascript function Parent(){ this.property = "parent"; this.value="common value"; } Parent.prototype.getParentValue= function(){ return this.property; }; Parent.prototype.getValue= function(){ return this.value; }; function Son(){ this.subproperty = "son"; } Son.prototype = new Parent(); Son.prototype.getSonValue = function (){ return this.subproperty; }; var instance = new Son(); console.log(instance.getParentValue()); //parent console.log(instance.getSonValue()); //son console.log(instance.getValue()); //common value //重写原型中的方法 Son.prototype.getValue=function(){ return this.value+" modified by son" //common value modified by son }; console.log(instance.getValue()); // 确定原型和实例的关系 console.log(instance instanceof Object); //true console.log(instance instanceof Parent); //true console.log(instance instanceof Son); //true // 确定原型和实例的关系 console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(Parent.prototype.isPrototypeOf(instance)); //true console.log(Son.prototype.isPrototypeOf(instance)); //true ``` #### 2. 组合继承(主要使用方式) ```javascript function Parent(name) { this.name = name; this.colors = ["red", "blue", "green"]; } Parent.prototype.sayName = function () { console.log(this.name); }; function Son(name, age) { //继承属性 Parent.call(this, name); this.age = age; } //继承方法 Son.prototype = new Parent(); Son.prototype.constructor = Son; Son.prototype.sayAge = function () { console.log(this.age); }; var instance1 = new Son("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new Son("Greg", 27); console.log(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27 ``` #### 3. 原型链继承 ```javascript 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"); console.log(person.name); // Nicholas console.log(yetAnotherPerson.name); // Linda console.log(anotherPerson.name); // Greg //包含引用类型值的属性始终都会共享相应的值 console.log(person.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] console.log(yetAnotherPerson.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] console.log(anotherPerson.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] var anotherPerson2 = Object.create(person, { name: { value: "Greg2" } }); console.log(anotherPerson2.name); //"Greg2" ``` ## 第七章 函数表达式 1. 递归的隐藏问题 ```javascript function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial=function(){ return 0 }; console.log(anotherFactorial(4)); //0 factorial = null; console.log(anotherFactorial(4)); //出错 //稳妥实现方式1 function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } // 稳妥实现方式2 var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } }); ``` 2. #### *闭包是指有权访问另一个函数作用域中的变量的函数。* ```javascript //闭包 function f(param) { // 内部函数持有了外部函数的变量 return function () { return param+10 } } let f1 = f(2); console.log(f1()); //12 ``` ```javascript //回调函数和this关键字 function f(param, fun) { for (let i = 0; i < 1000; i++) { param++; } fun(param) } var a = {}; a.say = f1; function f1() { this.name = 1000; var that = this; f(222, function (param) { console.log("this " + this.name); // undefined console.log("global " + global.name); // undefined console.log("that name " + that.name); // 1000 console.log("结果 " + param) // 1222 }); } a.say(); console.log(a.name); //1000 ``` ## 第八章 BOM ### 1.window 对象 ```javascript //保证兼容性下确定窗口位置 var leftPos = (typeof window.screenLeft == "number") ? window.screenLeft : window.screenX; var topPos = (typeof window.screenTop == "number") ? window.screenTop : window.screenY; //窗口大小 var pageWidth = window.innerWidth, pageHeight = window.innerHeight; if (typeof pageWidth != "number"){ if (document.compatMode == "CSS1Compat"){ pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; } else { pageWidth = document.body.clientWidth; pageHeight = document.body.clientHeight; } } // 打开新窗口 window.open("http://www.wrox.com/","wroxWindow","height=400,width=400,top=10,left=10,resizable=yes"; // 判断弹出窗口是否被屏蔽 var blocked = false; try { var wroxWin = window.open("http://www.wrox.com", "_blank"); if (wroxWin == null){ blocked = true; } } catch (ex){ blocked = true; } if (blocked){ alert("The popup was blocked!"); } ``` ```javascript // 间歇调用和超时调用 //超时调用 setTimeout("alert('Hello world!') ", 1000); //不建议传递字符串! //推荐的调用方式 setTimeout(function() { alert("Hello world!"); }, 1000); //设置超时调用 var timeoutId = setTimeout(function() { alert("Hello world!"); }, 1000); //注意:把它取消 clearTimeout(timeoutId); // 间歇调用 setInterval ("alert('Hello world!') ", 10000); //不建议传递字符串! //推荐的调用方式 setInterval (function() { alert("Hello world!"); }, 10000); ``` ### 2.location 对象 | 属性名 | 例子 | 说 | | :------: | :------------------: | :----------------------------------------------------------: | | hash | "#contents" | 返回URL中的hash(#号后跟零或多个字符),如果URL | | host | "www.wrox.com:80" | 返回服务器名称和端口号(如果有) | | hostname | "www.wrox.com" | 返回不带端口号的服务器名称 | | href | "http:/www.wrox.com" | 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值 | | pathname | "/WileyCDA/" | 返回URL中的目录和(或)文件名 | | port | "8080" | 返回URL中指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串 | | protocol | "http:" | 返回页面使用的协议。通常是http:或https: | | search | "?q=javascript" | 返回URL的查询字符串。这个字符串以问号开头 | ```javascript //位置操作 window.location = "http://www.wrox.com"; location.href = "http://www.wrox.com"; location.replace("http://www.wrox.com/"); location.assign("http://www.wrox.com"); location.reload(); //重新加载(有可能从缓存中加载) location.reload(true); //重新加载(从服务器重新加载) ``` ## 第十章 DOM ### 1. Node 类型

```JavaScript //新增节点 var returnedNode = someNode.appendChild(newNode); //someNode 有多个子节点 var returnedNode = someNode.appendChild(someNode.firstChild); alert(returnedNode == someNode.firstChild); //false alert(returnedNode == someNode.lastChild); //true //插入后成为最后一个子节点 returnedNode = someNode.insertBefore(newNode, null); alert(newNode == someNode.lastChild); //true //插入后成为第一个子节点 var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); //true alert(newNode == someNode.firstChild); //true //插入到最后一个子节点前面 returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true //替换第一个子节点 var returnedNode = someNode.replaceChild(newNode, someNode.firstChild); //替换最后一个子节点 returnedNode = someNode.replaceChild(newNode, someNode.lastChild); //移除第一个子节点 var formerFirstChild = someNode.removeChild(someNode.firstChild); //移除最后一个子节点 var formerLastChild = someNode.removeChild(someNode.lastChild); // 深拷贝 var deepList = myList.cloneNode(true); alert(deepList.childNodes.length); //3( IE < 9)或 7(其他浏览器) // 浅拷贝 var shallowList = myList.cloneNode(false); alert(shallowList.childNodes.length); ``` ### 2.Document类型 2.1 JavaScript 通过 Document 类型表示文档。在浏览器中, document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个 HTML 页面。而且, document 对象是 window 对象的一个属性,因此可以将其作为全局对象来访问。 ```javascript var html = document.documentElement; //取得对的引用 alert(html === document.childNodes[0]); //true alert(html === document.firstChild); //true var body = document.body; //取得对的引用 //取得文档标题 var originalTitle = document.title; //设置文档标题 document.title = "New page title"; //取得完整的 URL var url = document.URL; //取得域名 var domain = document.domain; //取得来源页面的 URL var referrer = document.referrer; //如果 URL 中包含一个子域名,例如 p2p.wrox.com,那么就只能将 domain 设置为"wrox.com"(URL 中包含"www",如 www.wrox.com 时,也是如此)。不能将这个属性设置为 URL 中不包含的域, document.domain = "wrox.com"; // 成功 document.domain = "nczonline.net"; // 出错 //假设页面来自于 p2p.wrox.com 域 允许由紧绷到松散 不允许松散到紧绷 document.domain = "wrox.com"; //松散的(成功) document.domain = "p2p.wrox.com"; //紧绷的(出错!)! ``` 2.2 查找元素 ```javascript //getElementById var div = document.getElementById("mydiv"); //getElementsByTagName var images = document.getElementsByTagName("img"); alert(images.length); //输出图像的数量 alert(images[0].src); //输出第一个图像元素的 src 特性 alert(images.item(0).src); //输出第一个图像元素的 src 特性 //对 HTMLCollection 而言,我们可以向方括号中传入数值或字符串形式的索引值。在后台,对数值索引就会调用 item(),而对字符串索引就会调用 namedItem()。 var myImage = images.namedItem("myImage"); var myImage = images["myImage"];
Which color do you prefer?
//getElementsByName var radios = document.getElementsByName("color"); ``` ### 3.Element类型 ```javascript
var div = document.getElementById("myDiv"); alert(div.tagName); //"DIV" alert(div.tagName == div.nodeName); //true if (element.tagName == "div"){ //不能这样比较,很容易出错! //在此执行某些操作 } if (element.tagName.toLowerCase() == "div"){ //这样最好(适用于任何文档) //在此执行某些操作 }
var div = document.getElementById("myDiv"); // 取值 alert(div.id); //"myDiv"" alert(div.className); //"bd" alert(div.title); //"Body text" alert(div.lang); //"en" alert(div.dir); //"ltr" //赋值 div.id = "someOtherId"; div.className = "ft"; div.title = "Some other text"; div.lang = "fr"; div.dir ="rtl"; //取值 alert(div.getAttribute("id")); //"myDiv" alert(div.getAttribute("class")); //"bd" alert(div.getAttribute("title")); //"Body text" alert(div.getAttribute("lang")); //"en" alert(div.getAttribute("dir")); //"ltr" //赋值 div.setAttribute("id", "someOtherId"); div.setAttribute("class", "ft"); div.setAttribute("title", "Some other text"); div.setAttribute("lang","fr"); div.setAttribute("dir", "rtl"); //删除属性 div.removeAttribute("class"); // 创建元素方式1 var div = document.createElement("div"); div.id = "myNewDiv"; div.className = "box"; document.body.appendChild(div); //创建元素方式2 var div = document.createElement("
"); //查找子元素 var ul = document.getElementById("myList"); //选择所有后代元素中标签为li,不论是否是直接子元素还是间接子元素 var items = ul.getElementsByTagName("li"); ``` ### 4.Text类型 文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容。 - appendData(text):将 text 添加到节点的末尾。 - deleteData(offset, count):从 offset 指定的位置开始删除 count 个字符。 - insertData(offset, text):在 offset 指定的位置插入 text。 - replaceData(offset, count, text):用 text 替换从 offset 指定的位置开始到 offset+count 为止处的文本。 - splitText(offset):从 offset 指定的位置将当前文本节点分成两个文本节点。 - substringData(offset, count):提取从 offset 指定的位置开始到 offset+count 为止处的字符串。 ```javascript //创建文本节点 var textNode = document.createTextNode("Hello world!"); //创建文本节点 var element = document.createElement("div"); element.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element); //规范化文本节点 DOM 文档中存在相邻的同胞文本节点很容易导致混乱,因为分不清哪个文本节点表示哪个字符串 var element = document.createElement("div"); element.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element); alert(element.childNodes.length); //2 element.normalize(); alert(element.childNodes.length); //1 alert(element.firstChild.nodeValue); // "Hello world!Yippee!" //分割文本节点 var element = document.createElement("div"); element.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); document.body.appendChild(element); var newNode = element.firstChild.splitText(5); alert(element.firstChild.nodeValue); //"Hello" alert(newNode.nodeValue); //" world!" alert(element.childNodes.length); //2 ``` ### 5.动态创建表格 **为\元素添加的属性和方法如下:** - caption:保存着对\元素的 HTMLCollection。 - tFoot:保存着对\元素(如果有)的指针。 - tHead:保存着对\元素(如果有)的指针。 - rows:是一个表格中所有行的 HTMLCollection。 - createTHead():创建\元素,将其放到表格中,返回引用。 - createTFoot():创建\元素,将其放到表格中,返回引用。 - createCaption():创建\元素。 - deleteTFoot():删除\元素。 - deleteCaption():删除\元素添加的属性和方法如下:** - rows:保存着\元素中行的 HTMLCollection。 - deleteRow(pos):删除指定位置的行。 - insertRow(pos):向 rows 集合中的指定位置插入一行,返回对新插入行的引用。 **为\元素添加的属性和方法如下:** - cells:保存着\元素中单元格的 HTMLCollection。 - deleteCell(pos):删除指定位置的单元格。 - insertCell(pos):向 cells 集合中的指定位置插入一个单元格,返回对新插入单元格的引用。 ```javascript //创建 table var table = document.createElement("table"); table.border = 1; table.width = "100%"; //创建 tbody var tbody = document.createElement("tbody"); table.appendChild(tbody); //创建第一行 tbody.insertRow(0); tbody.rows[0].insertCell(0); tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1")); tbody.rows[0].insertCell(1); tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1")); //创建第二行 tbody.insertRow(1); tbody.rows[1].insertCell(0); tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2")); tbody.rows[1].insertCell(1); tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2")); //将表格添加到文档主体中 document.body.appendChild(table); ``` ## 第十一章 DOM扩展 **1. querySelector()方法** querySelector()方法接收一个 **CSS 选择符**,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回 null。 ```javascript //取得 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"); ``` **2.querySelectorAll()方法** querySelectorAll()方法接收的参数与 querySelector()方法一样,**都是一个 CSS 选择符**,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个 NodeList 的实例。 如果没有找到匹配的元素, NodeList 就是空的。 ```javascript //取得某
中的所有元素(类似于 getElementsByTagName("em")) var ems = document.getElementById("myDiv").querySelectorAll("em"); //取得类为"selected"的所有元素 var selecteds = document.querySelectorAll(".selected"); //取得所有

元素中的所有元素 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"; } ``` **3.元素遍历** Element Traversal API 为 DOM 元素添加了以下 5 个属性。 - childElementCount:返回子元素(不包括文本节点和注释)的个数。 - firstElementChild:指向第一个子元素; firstChild 的元素版。 - lastElementChild:指向最后一个子元素; lastChild 的元素版。 - previousElementSibling:指向前一个同辈元素; previousSibling 的元素版。 - nextElementSibling:指向后一个同辈元素; nextSibling 的元素版。 **支持的浏览器为 DOM 元素添加了这些属性,利用这些元素不必担心空白文本节点。** **4.getElementsByClassName()方法** ```javascript //取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓 var allCurrentUsernames = document.getElementsByClassName("username current"); //取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素 var selected = document.getElementById("myDiv").getElementsByClassName("selected"); ``` **5.classList 属性** classList 属性是新集合类型 DOMTokenList 的实例。与其他 DOM 集合类似,OMTokenList 有一个表示自己包含多少元素的 length 属性,而要取得每个元素可以使用 item()方法,也可以使用方括号语法。此外,这个新类型还定义如下方法。 - add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。 - contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。 - remove(value):从列表中删除给定的字符串。 - toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。 ```javascript

...
//删除"disabled"类 div.classList.remove("disabled"); //添加"current"类 div.classList.add("current"); //切换"user"类 div.classList.toggle("user"); //确定元素中是否包含既定的类名 if (div.classList.contains("bd") && !div.classList.contains("disabled")){ //执行操作 ) //迭代类名 for (var i=0, len=div.classList.length; i < len; i++){ doSomething(div.classList[i]); } ``` **6. 焦点管理** ```javascript var button = document.getElementById("myButton"); button.focus(); alert(document.activeElement === button); //true var button = document.getElementById("myButton"); button.focus(); alert(document.hasFocus()); //true ``` **7.readyState 属性** ```javascript //loading,正在加载文档; //complete,已经加载完文档。 if (document.readyState == "complete"){ //执行操作 } ``` **8.自定义属性** ```javascript
//本例中使用的方法仅用于演示 var div = document.getElementById("myDiv"); //取得自定义属性的值 var appId = div.dataset.appId; var myName = div.dataset.myname; //设置值 div.dataset.appId = 23456; div.dataset.myname = "Michael"; //有没有"myname"值呢? if (div.dataset.myname){ alert("Hello, " + div.dataset.myname); } ``` **9.innerHTML 属性 和 outerHTML 属性** ​ 在读模式下, **innerHTML** 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的 HTML 标记。在写模式下, innerHTML 会根据指定的值创建新的 DOM 树,然后用这个 DOM 树完全替换调用元素原先的所有子节点。下面是一个例子。 ​ 在读模式下, **outerHTML** 返回调用它的元素及所有子节点的 HTML 标签。在写模式下, outerHTML会根据指定的 HTML 字符串创建新的 DOM 子树,然后用这个 DOM 子树完全替换调用元素。 **10.insertAdjacentHTML()方法** - "**beforebegin**",在当前元素之前插入一个紧邻的同辈元素; - "**afterbegin**",在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素; - "**beforeend**",在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素; - "**afterend**",在当前元素之后插入一个紧邻的同辈元素。 ```javascript //作为前一个同辈元素插入 element.insertAdjacentHTML("beforebegin", "

Hello world!

"); //作为第一个子元素插入 element.insertAdjacentHTML("afterbegin", "

Hello world!

"); //作为最后一个子元素插入 element.insertAdjacentHTML("beforeend", "

Hello world!

"); //作为后一个同辈元素插入 element.insertAdjacentHTML("afterend", "

Hello world!

"); ``` **11.滚动** **scrollIntoView()** :可以在所有 HTML 元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果给这个方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入 false 作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口顶部平齐。)不过顶部不一定平齐。 **scrollIntoViewIfNeeded(alignCenter)** :只在当前元素在视口中不可见的情况下,才滚动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。如果将可选的 alignCenter 参数设置为 true,则表示尽量将元素显示在视口中部(垂直方向)。Safari 和 Chrome 实现了这个方法。 **scrollByLines(lineCount):**将元素的内容滚动指定的行高, lineCount 值可以是正值,也可以是负值。 Safari 和 Chrome 实现了这个方法。 **scrollByPages(pageCount):**将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。 Safari 和 Chrome 实现了这个方法。 ```javascript //让元素可见 document.forms[0].scrollIntoView(); //将页面主体滚动 5 行 document.body.scrollByLines(5); //在当前元素不可见的时候,让它进入浏览器的视口 document.images[0].scrollIntoViewIfNeeded(); //将页面主体往回滚动 1 页 document.body.scrollByPages(-1); ``` **12.contains()方法** 判断某个节点是不是另一个节点的后代。 ```javascript alert(document.documentElement.contains(document.body)); //true ``` ## 第十二章 DOM2和DOM3 **1.偏移量、客户区大小、滚动大小**



## 第十三 事件 ### 13.1 DOM事件流 “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

### 13.2 事件处理程序 #### 1. DOM0 级事件处理程序 使用 DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的 this 引用当前元素。 ```javascript // 绑定事件 var btn = document.getElementById("myBtn"); btn.onclick = function(){ alert(this.id); //"myBtn" }; btn.onclick = null; //删除事件处理程序 ``` #### 2. DOM2 级事件处理程序 “DOM2 级事件” 定义了两个方法,用于处理指定和删除事件处理程序的操作: addEventListener()和 removeEventListener()。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。 **使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。** ```javascript var btn = document.getElementById("myBtn"); btn.addEventListener("click", function(){ alert(this.id); }, false); btn.addEventListener("click", function(){ alert("Hello world!"); }, false); btn.removeEventListener("click", function(){ //没有用! alert(this.id); }, false); //有效解绑 var btn = document.getElementById("myBtn"); var handler = function(){ alert(this.id); }; btn.addEventListener("click", handler, false); //这里省略了其他代码 btn.removeEventListener("click", handler, false); //有效 ``` #### 3.兼容IE ```javascript var EventUtil = { addHandler: function(element, type, handler){ if (element.addEventListener){ element.addEventListener(type, handler, false); } else if (element.attachEvent){ element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, removeHandler: function(element, type, handler){ if (element.removeEventListener){ element.removeEventListener(type, handler, false); } else if (element.detachEvent){ element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } } }; ``` ### 13.3 事件处理程序 兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或 DOM2 级),都会传入 event 对象。 ```javascript var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert(event.type); //"click" }; btn.addEventListener("click", function(event){ alert(event.type); //"click" }, false); ``` event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。 | 属性/方法 | 类 型 | 读/写 | 说 明 | | :------------------------: | :----------: | :---: | :----------------------------------------------------------- | | target | Element | 只读 | 事件的目标 | | bubbles | Boolean | 只读 | 表明事件是否冒泡 | | cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 | | currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 | | defaultPrevented | Boolean | 只读 | 为 true 表 示 已 经 调 用 了 preventDefault()(DOM3级事件中新增) | | detail | Integer | 只读 | 与事件相关的细节信息 | | eventPhase | Integer | 只读 | 调用事件处理程序的阶段: 1表示捕获阶段, 2表示“处于目标”, 3表示冒泡阶段 | | preventDefault() | Function | 只读 | 取 消 事 件 的 默 认 行 为 。 如 果 cancelable是true,则可以使用这个方法 | | stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增) | | stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 | | trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示 事 件 是 由 开 发 人 员 通 过 JavaScript 创 建 的(DOM3级事件中新增) | | type | String | 只读 | 被触发的事件的类型 | | view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 | ```javascript //点击myBtn元素 var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert(event.currentTarget === this); //true alert(event.target === this); //true }; document.body.onclick = function(event){ alert(event.currentTarget === document.body); //true alert(this === document.body); //true alert(event.target === document.getElementById("myBtn")); //true }; //阻止默认行为 var link = document.getElementById("myLink"); link.onclick = function(event){ event.preventDefault(); }; //阻止冒泡 var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert("Clicked"); event.stopPropagation(); }; ``` 事件对象的 eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么 eventPhase 等于 1;如果事件处理程序处于目标对象上,则 eventPhase 等于 2;如果是在冒泡阶段调用的事件处理程序,eventPhase 等于 3。这里要注意的是,尽管“处于目标”发生在冒泡阶段,但 eventPhase 仍然一直等于 2。 ```javascript var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert(event.eventPhase); //2 }; document.body.addEventListener("click", function(event){ alert(event.eventPhase); //1 }, true); document.body.onclick = function(event){ alert(event.eventPhase); //3 }; ``` ### 13.4 事件类型 #### 1.UI 事件 UI 事件指的是那些不一定与用户操作有关的事件。这些事件在 DOM 规范出现之前,都是以这种或那种形式存在的,而在 DOM 规范中保留是为了向后兼容。现有的 UI 事件如下。 - **load**:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在\元素上面触发,或者当嵌入的内容加载完毕时在\元素上面触发。 - **unload**:当页面完全卸载后在 window 上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后在\元素上面触发。 - **abort**:在用户停止下载过程时,如果嵌入的内容没有加载完,则在\元素上面触发。 - **error**:当发生 JavaScript 错误时在 window 上面触发,当无法加载图像时在\元素上面触发,当无法加载嵌入内容时在\元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。 - **select**:当用户选择文本框(\或\)中的一或多个字符时触发。 - **resize**:当窗口或框架的大小变化时在 window 或框架上面触发。 - **scroll**:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。 \元素中包含所加载页面的滚动条。 多数这些事件都与 window 对象或表单控件相关。 ```javascript EventUtil.addHandler(window, "load", function(event){ alert("Loaded!"); }); // 加载图片 var image = document.getElementById("myImage"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); }); //创建并加载图片 EventUtil.addHandler(window, "load", function(){ var image = document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); }); document.body.appendChild(image); image.src = "smile.gif"; }); //创建并加载图片2 EventUtil.addHandler(window, "load", function(){ var image = new Image(); EventUtil.addHandler(image, "load", function(event){ alert("Image loaded!"); }); image.src = "smile.gif"; }); //创建并加载脚本 EventUtil.addHandler(window, "load", function(){ var script = document.createElement("script"); EventUtil.addHandler(script, "load", function(event){ alert("Loaded"); }); script.src = "example.js"; document.body.appendChild(script); }); ``` #### 2.焦点事件 **blur**:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 **focus**:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 #### 3.鼠标和滚轮事件 **3.1 鼠标事件** 鼠标事件是 Web 开发中最常用的一类事件,毕竟鼠标还是最主要的定位设备。 DOM3 级事件中定义了 9 个鼠标事件。 - **click**:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。 - **dblclick**:在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不是 DOM2 级事件规范中规定的,但鉴于它得到了广泛支持,所以 DOM3 级事件将其纳入了标准。 - **mousedown**:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。 - **mouseenter**:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。 - **mouseleave**:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。 - **mousemove**:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。 - **mouseout**:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。 - **mouseover**:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。不能通过键盘触发这个事件。 - **mouseup**:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。 **3.2 位置:** ```javascript //客户区坐标位置 相对于浏览器可视窗口 var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); alert("Client coordinates: " + event.clientX + "," + event.clientY); }); //页面坐标位置 相对于整个html页面 var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); alert("Page coordinates: " + event.pageX + "," + event.pageY); }); //屏幕坐标位置 相对于整个电脑屏幕 var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); alert("Screen coordinates: " + event.screenX + "," + event.screenY); }); ``` **3.3 修改键** 虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键就是 Shift、 Ctrl、 Alt 和 Meta(在 Windows 键盘中是 Windows 键,在苹果机中是 Cmd 键),它们经常被用来修改鼠标事件的行为。 DOM 为此规定了 4 个属性,表示这些修改键的状态: shiftKey、 ctrlKey、 altKey 和 metaKey。这些属性中包含的都是布尔值,如果相应的键被按下了,则值为 true,否则值为 false。 ```javascript var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); var keys = new Array(); if (event.shiftKey){ keys.push("shift"); } if (event.ctrlKey){ keys.push("ctrl"); } if (event.altKey){ keys.push("alt"); } if (event.metaKey){ keys.push("meta"); } alert("Keys: " + keys.join(",")); }); ``` **3.4 相关元素** 在发生 mouseover 和 mouserout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对 mouseover 事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对 mouseout 事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。DOM 通过 event 对象的 **relatedTarget** 属性提供了相关元素的信息。 **这个属性只对于 mouseover和 mouseout 事件才包含值**;对于其他事件,这个属性的值是 null。 **3.5 鼠标按钮** 只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发 click 事件,因此检测按钮的信息并不是必要的。但对mousedown 和 mouseup 事件来说,则在其 event 对象存在一个 button 属性,表示按下或释放的按钮。 DOM 的 button 属性可能有如下 3 个值: 0 表示主鼠标按钮, 1 表示中间的鼠标按钮(鼠标滚轮按钮), 2 表示次鼠标按钮。 **3.6 鼠标滚轮事件** ```javascript EventUtil.addHandler(document, "mousewheel", function(event){ event = EventUtil.getEvent(event); alert(event.wheelDelta); }); ``` #### 4.键盘与文本事件 **4.1 键盘事件与文本事件** **键盘事件**: - **keydown**:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。 - **keypress**:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下 Esc 键也会触发这个事件。Safari 3.1 之前的版本也会在用户按下非字符键时触发 keypress事件。 - **keyup**:当用户释放键盘上的键时触发。 **文本事件:** - **textInput**。这个事件是对 keypress 的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发 textInput 事件。 **4.2 键码** 在发生 **keydown** 和 **keyup** 事件时, event 对象的 **keyCode** 属性中会包含一个代码,与键盘上一个特定的键对应。 (回车键定对应的键码为13) **4.3 字符编码** 发生 **keypress** 事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入或删除字符的键都会触发 keypress 事件。IE9、 Firefox、 Chrome 和 Safari 的 event 对象都支持一个 **charCode** 属性,这个属性只有在发生keypress 事件时才包含值,而且这个值是按下的那个键所代表字符的 ASCII 编码。此时的 keyCode通常等于 0 或者也可能等于所按键的键码。 **4.5 textInput 事件** 由于 textInput 事件主要考虑的是字符,因此它的 event 对象中还包含一个 data 属性,这个属性的值就是用户输入的字符(而非字符编码)。 ```javascript var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "textInput", function(event){ event = EventUtil.getEvent(event); alert(event.data); }); ``` 另外, event 对象上还有一个属性,叫 **inputMethod**,表示把文本输入到文本框中的方式。 - 0,表示浏览器不确定是怎么输入的。 - 1,表示是使用键盘输入的。 - 2,表示文本是粘贴进来的。 - 3,表示文本是拖放进来的。 - 4,表示文本是使用 IME 输入的。 - 5,表示文本是通过在表单中选择某一项输入的。 - 6,表示文本是通过手写输入的(比如使用手写笔)。 - 7,表示文本是通过语音输入的。 - 8,表示文本是通过几种方法组合输入的。 - 9,表示文本是通过脚本输入的。 #### 5.变动事件 DOM2 级的变动(mutation)事件能在 DOM 中的某一部分发生变化时给出提示。变动事件是为 XML或 HTML DOM 设计的,并不特定于某种语言。 DOM2 级定义了如下变动事件。 - **DOMSubtreeModified**:在 DOM 结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。 - **DOMNodeInserted**:在一个节点作为子节点被插入到另一个节点中时触发。 - **DOMNodeRemoved**:在节点从其父节点中被移除时触发。 - **DOMNodeInsertedIntoDocument**:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在 DOMNodeInserted 之后触发。 - **DOMNodeRemovedFromDocument**:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在 DOMNodeRemoved 之后触发。 - **DOMAttrModified**:在特性被修改之后触发。 - **DOMCharacterDataModified**:在文本节点的值发生变化时触发。 #### 6.HTML5事件 **6.1 beforeunload 事件** 之所以有发生在 window 对象上的 beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。 为了显示这个弹出对话框,必须将 event.returnValue 的值设置为要显示给用户的字符串(对IE 及 Fiefox 而言),同时作为函数的值返回(对 Safari 和 Chrome 而言),如下面的例子所示。 ```javascript EventUtil.addHandler(window, "beforeunload", function(event){ event = EventUtil.getEvent(event); var message = "I'm really going to miss you if you go."; event.returnValue = message; return message; }); ``` **6.2 DOMContentLoaded 事件** 如前所述, window 的 load 事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。而 DOMContentLoaded 事件则在形成完整的 DOM 树之后就会触发,不理会图像、 JavaScript 文件、 CSS 文件或其他资源是否已经下载完毕。与 load 事件不同,DOMContentLoaded 支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。 要处理 DOMContentLoaded 事件,可以为 document 或 window 添加相应的事件处理程序(尽管这个事件会冒泡到 window,但它的目标实际上是 document)。来看下面的例子。 ```javascript EventUtil.addHandler(document, "DOMContentLoaded", function(event){ alert("Content loaded"); }); ``` **6.3 hashchange 事件** HTML5 新增了 hashchange 事件,以便在 URL 的参数列表(及 URL 中“#”号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在 Ajax 应用中,开发人员经常要利用 URL 参数列表来保存状态或导航信息。 必须要把 hashchange 事件处理程序添加给 window 对象,然后 URL 参数列表只要变化就会调用它。此时的 event 对象应该额外包含两个属性: oldURL 和 newURL。 ```javascript EventUtil.addHandler(window, "hashchange", function(event){ alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL); }); //保证兼容性的情况下可以按照此方法调用得到当前参数列表 EventUtil.addHandler(window, "hashchange", function(event){ alert("Current hash: " + location.hash); }); ``` ### 第十四章 表单脚本 ### 14.1 表单基础知识 在 HTML 中,表单是由\
元素来表示的,而在 JavaScript 中,表单对应的则是 HTMLFormElement 类型。HTMLFormElement 继承了 HTMLElement,因而与其他 HTML 元素具有相同的默认属性。不过, HTMLFormElement 也有它自己下列独有的属性和方法。 - **acceptCharset**:服务器能够处理的字符集;等价于 HTML 中的 accept-charset 特性。 - **action**:接受请求的 URL;等价于 HTML 中的 action 特性。 - **elements**:表单中所有控件的集合(HTMLCollection)。 - **enctype**:请求的编码类型;等价于 HTML 中的 enctype 特性。 - **length**:表单中控件的数量。 - **method**:要发送的 HTTP 请求类型,通常是"get"或"post";等价于 HTML 的 method 特性。 - **name**:表单的名称;等价于 HTML 的 name 特性。 - **reset()**:将所有表单域重置为默认值。 - **submit()**:提交表单。 - **target**:用于发送请求和接收响应的窗口名称;等价于 HTML 的 target 特性。 #### 1.提交表单 ```html ``` 以这种方式提交表单时,浏览器会在将请求发送给服务器之前触发 submit 事件。这样,我们就有机会验证表单数据,并据以决定是否允许表单提交。阻止这个事件的默认行为就可以取消表单提交。例如,下列代码会阻止表单提交。 ```javascript var form = document.getElementById("myForm"); EventUtil.addHandler(form, "submit", function(event){ //取得事件对象 event = EventUtil.getEvent(event); //阻止默认事件 EventUtil.preventDefault(event); }); ``` ```javascript //提交表单方式二 var form = document.getElementById("myForm"); //提交表单 form.submit(); ``` 调用 submit()方法的形式提交表单时,**不会触发 submit 事件**,因此要记得在调用此方法之前先验证表单数据 。 #### 2.重置表单 ```html ``` ```javascript var form = document.getElementById("myForm"); EventUtil.addHandler(form, "reset", function(event){ //取得事件对象 event = EventUtil.getEvent(event); //阻止表单重置 EventUtil.preventDefault(event); }); ``` ```javascript var form = document.getElementById("myForm"); //重置表单 form.reset(); ``` **与调用 submit()方法不同,调用 reset()方法会像单击重置按钮一样触发 reset 事件。** #### 3.表单字段 ```javascript var form = document.getElementById("form1"); //取得表单中的第一个字段 var field1 = form.elements[0]; //取得名为"textbox1"的字段 var field2 = form.elements["textbox1"]; //取得表单中包含的字段的数量 var fieldCount = form.elements.length;
  • Red
  • Green
  • Blue
var form = document.getElementById("myForm"); var colorFields = form.elements["color"]; alert(colorFields.length); //3 var firstColorField = colorFields[0]; var firstFormField = form.elements[0]; alert(firstColorField === firstFormField); //true ``` ##### 3.1 共有的表单字段属性 除了\
元素之外,所有表单字段都拥有相同的一组属性。由于\类型可以表示多种表单字段,因此有些属性只适用于某些字段,但还有一些属性是所有字段所共有的。表单字段共有的属性如下。 - **disabled**:布尔值,表示当前字段是否被禁用。 - **form**:指向当前字段所属表单的指针;**只读**。 - **name**:当前字段的名称。 - **readOnly**:布尔值,表示当前字段是否只读。 - **tabIndex**:表示当前字段的切换(tab)序号。 - **type**:当前字段的类型,如"checkbox"、 "radio",等等。 - **value**:当前字段将被提交给服务器的值。对文件字段来说,这个属性是只读的,包含着文件在计算机中的路径。 ```javascript var form = document.getElementById("myForm"); var field = form.elements[0]; //修改 value 属性 field.value = "Another value"; //检查 form 属性的值 alert(field.form === form); //true //把焦点设置到当前字段 field.focus(); //禁用当前字段 field.disabled = true; //避免多次提交表单 EventUtil.addHandler(form, "submit", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); //取得提交按钮 var btn = target.elements["submit-btn"]; //禁用它 btn.disabled = true; }); ``` ##### 3.2 共有的表单字段方法 每个表单字段都有两个方法: 即 **focus()** 和 **blur()** ```javascript //html5 新增属性 autofocus // js 设置方式 EventUtil.addHandler(window, "load", function(event){ var element = document.forms[0].elements[0]; if (element.autofocus !== true){ element.focus(); console.log("JS focus"); } }); document.forms[0].elements[0].blur(); ``` ##### 3.3 共有的表单字段事件 除了支持鼠标、键盘、更改和 HTML 事件之外,所有表单字段都支持下列 3 个事件。 **blur**:当前字段失去焦点时触发。 **change**:对于\和\ var textbox = document.forms[0].elements["textbox1"]; alert(textbox.value); textbox.value = "Some new value"; ``` #### 1. 选择文本 选择(**select**)事件 ```javascript //鼠标选择文本时候触发 var textbox = document.forms[0].elements["textbox1"]; EventUtil.addHandler(textbox, "select", function(event){ var alert("Text selected" + textbox.value); }); ``` 获取选中的内容**selectionStart**,**selectionEnd**,设置选中的范围**setSelectionRange** ```html Title ``` #### 2. 过滤输入(**keypress**) ```html Title ``` #### 3.自动切换焦点 ```html Title ``` #### 4. HTML5 验证约束API ```html var isUsernameRequired = document.forms[0].elements["username"].required input.stepUp(); //加 1 input.stepUp(5); //加 5 input.stepDown(); //减 1 input.stepDown(10); //减 10
document.forms[0].elements["btnNoValidate"].formNoValidate = true; ``` 通过设置 **novalidate** 属性,可以告诉表单不进行验证。 如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证表单,可以在相应的按钮上添加 **formnovalidate** 属性。 **约束校验** ```html Title ``` 与 checkValidity()方法简单地告诉你字段是否有效相比, **validity** 属性则会告诉你为什么字段有效或无效。这个对象中包含一系列属性,每个属性会返回一个布尔值。 - **customError** :如果设置了 setCustomValidity(),则为 true,否则返回 false。 - **patternMismatch**:如果值与指定的 pattern 属性不匹配,返回 true。 - **rangeOverflow**:如果值比 max 值大,返回 true。 - **rangeUnderflow**:如果值比 min 值小,返回 true。 - **stepMisMatch**:如果 min 和 max 之间的步长值不合理,返回 true。 - **tooLong**:如果值的长度超过了 maxlength 属性指定的长度,返回 true。有的浏览器(如 Firefox 4) - 会自动约束字符数量,因此这个值可能永远都返回 false。 - **typeMismatch**:如果值不是"mail"或"url"要求的格式,返回 true。 - **valid**:如果这里的其他属性都是 false,返回 true。 checkValidity()也要求相同的值。 - **valueMissing**:如果标注为 required 的字段中没有值,返回 true。 - 因此,要想得到更具体的信息,就应该使用 validity 属性来检测表单的有效性。下面是一个例子。 ```javascript if (input.validity && !input.validity.valid){ if (input.validity.valueMissing){ alert("Please specify a value.") } else if (input.validity.typeMismatch){ alert("Please enter an email address."); } else { alert("Value is invalid."); } } ``` ### 14.3 选择框脚本 选择框是通过\ ``` 在 DOM 中,每个\
元素(如果有)的指针。 - tBodies:是一个\元素,将其放到表格中,返回引用。 - deleteTHead():删除\元素。 - deleteRow(pos):删除指定位置的行。 - insertRow(pos):向 rows 集合中的指定位置插入一行。 **为\