文章目录
  1. 1. 第二章 在HTMl中使用JavaScript
    1. 1.1. 2.1 script元素
    2. 1.2. 2.2 嵌入代码与外部文件
    3. 1.3. 2.3 文档模式
    4. 1.4. 2.4 noscript元素
  2. 2. 第三章 基本概念
    1. 2.1. 3.1 语法
    2. 2.2. 3.2 关键字和保留字
    3. 2.3. 3.3 变量
    4. 2.4. 3.4 数据类型
    5. 2.5. 3.5 操作符
    6. 2.6. 3.6 语句
    7. 2.7. 3.7 函数
  3. 3. 第四章 变量、作用域和内存问题
    1. 3.1. 4.1 基本类型和引用类型的值
    2. 3.2. 4.2 执行环境及作用域
    3. 3.3. 4.3 垃圾收集
  4. 4. 第五章 引用类型
    1. 4.1. 5.1 Object类型
    2. 4.2. 5.2 Array类型
    3. 4.3. 5.3 Date类型
    4. 4.4. 5.4 RegExp类型
    5. 4.5. 5.5 Function类型
    6. 4.6. 5.6 基本包装类型
    7. 4.7. 5.7 单体内置对象
  5. 5. 第六章 面向对象的程序设计
    1. 5.1. 6.1 理解对象
    2. 5.2. 6.2 创建对象
    3. 5.3. 6.3 继承
  6. 6. 第七章 函数表达式
    1. 6.1. 7.1 递归
    2. 6.2. 7.2 闭包
    3. 6.3. 7.3 模仿块级作用域
    4. 6.4. 7.4 私有变量
  7. 7. 第8章 BOM
    1. 7.1. 8.1 window对象
    2. 7.2. 8.2 location对象
    3. 7.3. 8.3 navigator对象
    4. 7.4. 8.4 screen对象
    5. 7.5. 8.5 history对象
  8. 8. 第九章 客户端检测
    1. 8.1. 9.1 能力检测
    2. 8.2. 9.2 怪癖检测
    3. 8.3. 9.3 用户代理检测
  9. 9. 第10章 DOM
    1. 9.1. 10.1 节点层次
    2. 9.2. 10.2 DOM操作技术
  10. 10. 第11章 DOM扩展
    1. 10.1. 11.1 选择符API
    2. 10.2. 11.2 元素遍历
    3. 10.3. 11.3 HTML5
    4. 10.4. 11.4 专有扩展
  11. 11. 第12章 DOM2和DOM3
    1. 11.1. 12.1 DOM变化
    2. 11.2. 12.2 样式
    3. 11.3. 12.3 遍历
    4. 11.4. 12.4 范围
  12. 12. 第13章 事件
    1. 12.1. 13.1 事件流
    2. 12.2. 13.2 事件处理程序
    3. 12.3. 13.3 事件对象
    4. 12.4. 13.4 事件类型
    5. 12.5. 13.5 内存和性能
    6. 12.6. 13.6 模拟事件

第二章 在HTMl中使用JavaScript

2.1 script元素

6个属性:

  • async:可选。表示立即下载脚本,但不妨碍其他正在加载的脚本。仅适用于外部加载的脚本
  • charset:可选。表示代码的字符集。因为大多数浏览器会忽略他的值,所以很少用。
  • defer:可选。表示脚本可以等文档解析完之后再加载。仅适用于外部加载的脚本
  • src:可选。引入要执行的代码
  • type:可选。考虑到约定俗成和最大限度的浏览器兼容性,目前type属性的值依旧是text/javascript。不过,他不是必须的,因为其默认值就是text/javascript。

只要不存在defer和async属性,浏览器都会按照script元素在页面中出现的先后顺序对他们依次进行解析。

2.1.1 标签的位置

放在标签之前

2.1.2 延迟脚本
有了defer=”defer”相当于告诉脚本立即下载,但延迟执行。

<script type="text/javascript" defer="defer" src=""></script>

(不可以简写,因为要兼容ie等一些浏览器)

  • 考虑到浏览器的兼容性,把脚本放在页面底部仍然是最佳选择。

2.1.3 异步脚本

<script type="text/javascript" async="async" src=""></script>
  • 异步脚本不要在下载期间修改DOM

2.1.4 在XHTML中的用法

  • 在xhtml中会把<号理解为一个新标签的开始,新标签后跟空格,就会导致错误,因此需要引入CData片段,才可以通过xhtml验证,而且对xhtml之前的浏览器也会平稳退化。
<script>
//<![CDATA[
    function compare(a,b){
        if(a < b){

        }else if(a > b){

        }else{

        }
    }
//]]>
</script>

2.2 嵌入代码与外部文件

优点:

  • 可维护性
  • 可缓存
  • 适应未来通过外部文件来包含javascript无需使用前面提到的xhtml或注释hack。html和xhtml外部文件的语法相同。

2.3 文档模式

混杂模式和标准模式

<!-- html 4.01 严格型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- xhtml 1.1 严格型 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- html 5 -->
<!DOCTYPE html>
<!-- html 4.01 过渡型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- xhtml 1.0 过渡型 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- html 4.01 框架集型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<!-- xhtml 1.0 框架集型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-frameset.dtd">

2.4 noscript元素

执行条件:

  • 浏览器不支持脚本
  • 浏览器支持脚本,但脚本被禁用

第三章 基本概念

3.1 语法

3.1.1 区分大小写

3.1.2 标识符

所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。

3.1.3 注释

//单行注释
/*
 *多行(块级)注释
 */

3.1.4 严格模式

ECMAScript 5 引入了严格模式(strict mode)的概念。他是一种不同的解析与执行模式。在严格模式下,ECMAScript 3 中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出异常。

在整个脚本中启动严格模式,可以在顶部添加
"use stritct"
指定函数在严格模式下执行
function doSomething(){
    "use strict";
    //函数体
}

3.1.5 语句

以分号结尾

3.2 关键字和保留字

3.3 变量

  • 松散型的,可以用来保存任何类型的数据
  • 使用var操作符定义的变量将成为定义该变量的作用域中的局部变量,在函数退出后就会被销毁
  • 可以使用一条语句定义多个变量,用逗号隔开即可

    var message = “hi”,

    found = false,
    age = 29;
    

3.4 数据类型

ECMAScript种有5种基本数据类型:Undefined、Null、Boolean、Number和String。还有一种复杂的数据类型:Object,Object由一组无序的名值对组成。

  • ECMAScript不支持任何创建自定义类型的机制

3.4.1 typeof操作符

检测给定变量的数据类型(除上面的几种类型外,还可能是function函数)

  • 特例:typeof null 返回”object”,因为null被认为是一个空的对象引用

3.4.2 Undefined 类型

对未初始化和未声明的变量执行typeof操作符都会返回undefined值

3.4.3 Null 类型

  • 如果定义的变量用于保存对象,最好将该变量初始化为null,这样只要检查null值就可以知道相应的变量是否已经保存了一个对象的引用,如

    if(car != null){
        //对car对象执行某些操作
    }    
    
  • 实际上undefined值是派生自null值的,因此

    alert(null == undefined);  //true
    

3.4.4 Boolean 类型

  • 要将一个值转换为其对应的Boolean值,可以调用转型函数Boolean(),如下:

    var message = "test";
    var messageAsBoolean = Boolean(message);
    
  • 各种数据类型及其对应的转换规则

3.4.5 Number 类型

  1. 浮点数
  • 八进制字面值的第一位必须是0,然后是八进制数字序列0~7。如果字面值中的数值超出了范围,那么前导0将被忽略,后面的数值将被当做十进制数值解析

    var num1 = 070   //八进制的56
    var num2 = 079   //无效的八进制数值——解析为79
    var num3 = 08    //无效的八进制数值——解析为8
    
  • 八进制字面量在严格模式下是无效的,会导致支持的JavaScript引擎抛出错误

  • 十六进制字面值的前两位必须是0x,后跟任何十六进制数字(0~9 及 A~F)。其中,字母A~F可以大写,也可以小写,如下:

    var hexNum1 = 0xA;   //十六进制的10
    var hexNum2 = 0x1f;  //十六进制的31 
    

- 浮点数的最高精度是17位小数,因此永远不要测试某个特定的浮点数值

  1. 数值范围
  • 超出数值范围,这个值将被自动转换成特殊的Infinity值,或 -Infinity
  • 判断一个数值是不是有穷的(即位于最大最小值之间),用isFinite()函数,如果是有穷的,则返回true
  1. NaN
  • NaN,任何数除以0会返回NaN;任何涉及NaN的操作都会返回NaN;NaN与任何值都不相等,包括NaN本身。用isNaN()判断某个参数是否“不是数值”
  1. 数值转换
    有三个函数:Number()、parseInt()和parseFloat()
  • Number()可以用于任何数据类型
  • parseInt()专门用于把字符串转换成数值,可以为这两个函数指定参数,避免不同版本ECMAScript解析的不一样

    var num1 = parseInt(“AF”,16) //175
    var num2 = parseInt(“AF”) //NaN

  • parseFloat()解析时第一个小数点是有效的,第二个小数点无效,会忽略前导的零,只解析十进制数,所以没有用第二个参数指定技术的用法。十六进制的字符串始终会被转换成0,如果字符串包含的是一个可解析为整数的数,会返回整数。

3.4.6 String 类型

  1. 字符字面量

    \n 换行
    \t 制表
    \b 空格
    \r 回车
    \f 进纸
    \ 斜杠
    \’ 单引号(’)
    \” 双引号(”)

  2. 字符串的特点

  • 字符串一旦创建,他们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后在用另一个包含新值的字符串填充该变量。
  1. 转换为字符串

    var age = 11;
    var ageAsString = age.toString(); //字符串”11”
    //toString()可以传递基数,输出不同进制的数
    //age.toString(2)
    //age.toString(8)
    //age.toString(10)
    //age.toString(16)

  2. 在不知道要转换的值是不是null或undefined的情况下,还可以使用转型函数String(),这个函数可以将任何类型的值转换为字符串。其规则如下:

  • 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果;
  • 如果值是null,则返回”null”;
  • 如果值是undefined,则返回”undefined”。
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;

alert(String(value1));  //'10',有toString()方法
alert(String(value2));  //'true',有toString()方法
alert(String(value3));  //'null'
alert(String(value4));  //'undefined'

3.4.7 Object 类型

var o = new Object();

Object的每个实例都具有下列属性和方法

  • Constructor:保存着用于创建当前对象的函数。
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中是否存在。其中作为参数的属性名propertyName必须是以字符串形式指定。eg:o.hasOwnProperty(“name”).
  • isPrototypeOf(object):用于检查传入的对象是否是另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于检查给定属性是否能够使用for-in语句来枚举。与hasOwnProperty()一样,参数名必须是字符串形式。
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应
  • toString():返回对象的字符串表示
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()的返回值相同。

3.5 操作符

3.5.1 一元操作符
3.5.2 位操作符

  1. 按位非(NOT)

    var num1 = 25;
    var num2 = ~num1;
    alert(num2);        //-26
    
    • 都转换成二进制数取反
    • 按位非操作的本质:操作数的负值减1


  2. 按位与(AND)

    var result = 25 & 3;
    alert(result);    //1
    
    • 两个数值的对应位都是1时才返回1,任何一位是0,结果就是0


  3. 按位或(OR)

    var result = 25 | 3;
    alert(result);    //27
    
    • 有一位是1就返回1,只有在两位都是0的情况下才返回0


  4. 按位异或(XOR)

    var result = 25 ^ 3;
    alert(result);    //26
    
    • 与按位或的不同之处在于,两个数值对应位上只有一个1时才返回1,如果两位都是1或都是0,则返回0


  5. 左移

    var oldValue = 2;              //等于二进制的10
    var newValue = oldValue << 5   //等于二进制的1000000,十进制的64
    
    • 向左移位后,原数值的右侧多出了5个空位。左移操作会以0来填充这些空位
    • 左移不会影响操作数的符号位。即:-2向左移动5位是-64,而非64


  6. 有符号的右移

    var oldValue = 64;              //等于二进制的1000000
    var newValue = oldValue >> 5    //等于二进制的10,十进制的2
    
    • 空位出现在原数值的左侧,符号位的右侧
    • 空位由符号位的值来填充


  7. 无符号的右移

    var oldValue = 64;              //等于二进制的1000000
    var newValue = oldValue >>> 5    //等于二进制的10,十进制的2
    
    • 对于正数来说,无符号右移的结果与有符号右移相同
    • 无符号右移是以0来填充空位的
    • 无符号右移操作符会把负数的二进制码当成正数的二进制码

      var oldValue = -64;              //等于二进制的1111111111111111111111111000000(不准确)
      var newValue = oldValue >>> 5    //等于十进制的134217726
      
    • 无符号右移操作会把这个二进制码(1111111111111111111111111000000)当成正数的二进制码,换算成十进制就是4294967273。如果把这个值右移5位,结果就是00000111111111111111111110.即十进制的134217726


3.5.3 布尔操作符

  1. 逻辑非

    alert(!"blue");     //false
    alert(!NaN);        //true
    
  2. 逻辑与

    • 不能在逻辑与操作中使用未定义的值,否则会导致错误
    • 是一个短路操作符
  3. 逻辑或

    • 同样不能在逻辑与操作中使用未定义的值,否则会导致错误
    • 也是短路操作符
    • 可以用逻辑或的这种行为来避免为变量赋null或undefined值。

      var myObject = preferredObject || backupObject;
      //如果preferredObject是null,则将backupObject的值赋给myObject
      

3.5.4 乘性操作符

  1. 乘法(*)
  2. 除法(/)
  3. 求模(余数)(%)

3.5.5 加性操作符

  1. 加法
  2. 减法

3.5.6 关系操作符

  • <,>,<=,>=
  • 大写字母的字符编码全部小于小写字母的字符编码

    var result = "23" < "3"   //true
    //因为字符转比较的是编码,2的字符编码是50,3是51
    var result = "23" < 3     //false 
    var result = "a" < 3      //false,因为"a"被转换成了NaN
    
  • 任何操作数与NaN进行关系比较,结果都是false

3.5.7 相等操作符

  1. 相等和不相等(==和!=)

    NaN == NaN   //false
    
  2. 全等和不全等(===和!==)

    null == undefined   //true
    null === undefined  //false
    

3.5.8 条件操作符

variable = boolean_expression ? true_value : flase_value;

3.5.9 赋值操作符

  • 复合赋值操作符(*= /= %= += -= <<= >>= >>>=)

3.5.10 逗号操作符

var num1=1,num2=2,num3=3;
var numm = (5,1,4,8,0);   //num的值为0

3.6 语句

3.6.1 if语句

3.6.2 do-while 语句

  • 后测试循环语句,即只有在循环体中的代码执行之后,才会测试出口条件。换句话说,在对表达是求值之前,循环体内的代码至少会被执行一次。

3.6.3 while语句

  • 在循环体内的代码被执行之前,就会对出口条件求值,因此,循环体内的代码有可能永远不会被执行。

    var i = 0;
    while(i < 10){
        i += 2;
    }
    

3.6.4 for 语句

3.6.5 for-in 语句

  • 一种精准的迭代语句,可以用来枚举对象的属性

    for(var proName in window){
        document.write(proName);
    }
    //显示BOM中window对象的所有属性
    
  • ECMAScript 对象的属性没有顺序。因此,通过for-in循环输出的属性名的顺序是不可预测的。具体来讲,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。

  • 因为如果要迭代的对象的变量值为null或undefined,for-in语句会抛出错误。ECMAScript5更正了这一行为;对于这种情况不再抛出异常,而只是不执行循环体。为了保证最大限度的兼容性,建议在使用for-in 循环前,先检测确认对象的值不是null或undefined。

3.6.6 label 语句

  • 使用label语句可以在代码红添加标签,以便将来使用。

    start:for(var i=0;i<count;i++){
        alert(i);
    }    
    
  • 这个start标签可以在将来由break或contrinue语句引用

  • 加标签的语句一般都要与for语句等循环语句配合使用

3.6.7 break和continue 语句

  • break语句会立即推出循环,强制继续执行循环后面的语句
  • continue语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。

    var num = 0;
    for(var i=1; i<10; i++){
        if(i%5==0){
            break;
        }
        num++;
    }
    alert(num);     //4
    
    var num = 0;
    for(var i=1; i<10; i++){
        if(i%5==0){
            countinue;
        }
        num++;
    }
    alert(num);     //8
    

3.6.8 with 语句

  • 将代码的作用域设置到一个特定的对象中。

    var qs = location.search.substring(1);
    var hostName = location.hostname;
    var url = location.href;
    
    with(location){
        var qs = search.substring(1);
        var hostName = hostname;
        var url = href;
    }
    //由于大量使用with语句会导致性能下降,同事也会给调试代码造成困难,因此不建议使用
    

3.6.9 switch 语句

if(i==25){
    alert("25");
}else if(i==35){
    alert("35");
}else if(i==45){
    alert("45");
}else{
    alert("Other");
}

//于此等价的switch语句如下:

switch(i){
    case 25:
        alert("25");
        break;
    case 35:
        alert("35");
        break;
    case 45:
        alert("45");
        break;
    default:
        alert("Other");
}
  • switch语句中使用任何数据类型,无论是字符串,还是对象都没有问题
  • 每个case值不一定是常量,也可以是变量,甚至是表达式
  • switch语句在比较值时使用的是全等操作符,因此不会发生类型转换

3.7 函数

function sum(num1,num2){
    return num1+num2;
    alert("hello");   //永远不会执行
}
//这个函数会在执行完return语句之后停止并立即退出。因此,位于return语句之后的任何代码都永远不会执行

3.7.1 理解参数

  • 在函数体内可以通过arguments对象来访问这个参数数组(第一个参数:arguments[0],第二个参数:argument[1],以此类推)
  • 使用length属性来确定传递进来多少个参数
  • 没有传递值得命名参数将自动被赋予undefined值

3.7.2 没有重载

  • 后定义的函数覆盖先定义的函数

第四章 变量、作用域和内存问题

4.1 基本类型和引用类型的值

  • 基本类型值:简单的数据段
  • 引用类型值:那些可能由多个值构成的对象

4.1.1 动态的属性

var person = new Object();
person.name = "Nicholas";
alert(person.name);   //"Nicholas";

var name = "Nicholas";
name.age = 27;
alert(name.age);      //undefined
  • 只能给引用类型值动态地添加属性,以便将来使用

4.1.2 复制变量值

4.1.3 传递参数

function addTen(num){
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count);      //20,没有变化
alert(result);     //30
//参数num与变量count互不认识,他们仅仅是具有相同的值

function setName(obj){
    obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name);   //"Nicholas"
//在这个函数内部,obj和person引用的是同一个对象。换句话说,即使这个对象是按值传递的,obj也会按引用来访问同一个对象。

function setName(obj){
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name);   //"Nicholas"
//即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了,这个局部对象会在函数执行完毕后立即被销毁

4.1.4 检测类型

检测基本数据类型用typeof,可以确定一个变量是字符串,数值,布尔值,还是undefined的最佳工具,但是在检测引用类型的值时,这个操作符用处不大。通常,我们并不想知道某个值是对象,而是想知道他是什么类型的对象。为此,可以用instanceof操作符:

alert(person instanceof Object);   //变量person是Object吗?
alert(colors instanceof Array);   //变量colors是Array吗?
alert(pattern instanceof RegExp);   //变量pattern是RegExp吗?
  • 所有引用类型的值都是Object的实例。因此在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true
  • 如果使用instanceof操作符检测基本类型的值,该操作符始终会返回false,因为基本类型不是对象
  • 在Safari 5及之前版本和Chrome 7及之前版本中使用typeof检测正则表达式时,会返回“function”。在IE和Firefox中,对正则表达式应typeof会返回“object”

4.2 执行环境及作用域

4.2.1 延长作用域链

- try-catch语句的catch块
- with语句

4.2.2 没有块级作用域

1. 声明变量
2. 查询标识符

4.3 垃圾收集

4.3.1 标记清楚

4.3.2 引用计数

- 循环引用问题

        var element = document.getElementById("some_element");
        var myObject = new Object();
        myObject.element = element;
        element.someObject = myObject;

        //用下面的方法可以消除循环
        myObject.element = null;
        element.someObject =null;

4.3.3 性能问题

4.3.4 管理内存

  • 解除引用:一旦数据不再有用,最好通过将其值设置为null来释放其引用

    function creatPerson(name){
        var localPerson = new Object();
        localPerson.name = name;
        return localPerson;
    }
    var globalPerson = createPerson("Nicholas");
    //手工解除globalPerson的引用
    globalPerson = null;
    

第五章 引用类型

5.1 Object类型

  • 创建Object实例的方式

    1. 使用new操作符后跟Object构造函数

      var person = new Object();
      person.name = "Nicholas";
      person.age = 29;
      
    2. 使用对象字面量表示法

      var person = {
          name : "Nicholas",
          age : 29
      };
      

      如果留空其花括号,则与new Object()相同

      var person = {};
      person.name = "Nicholas";
      person.age = 29;
      
  • 一般来说,访问对象属性时使用的都是点表示法:

    person.name
    
  • 也可以使用方括号法来访问,这种方式的优点是可以通过变量访问属性:

    var propertyName = “name”;
    alert(person[propertyName]); //“Nicholas”

5.2 Array类型

  • 利用length属性也可以方便地在数组末尾添加新项

    var colors = ["red","blue","green"];
    colors[colors.length] = "black";
    colors[colors.length] = "brown";
    

5.2.1 监测数组

  • 确定某个值到底是不是数组:

    if(Array.isArray(value)){

    //对数组执行某些操作
    

    }

5.2.2 转换方法

  • 三种转换为字符串的方法:toLocaleString(),toString()和valueOf()
  • valueOf()返回的是数组
  • toString()返回的是一个以逗号分割的字符串
  • toLocaleString()与其他两个方法唯一的不同之处在于,这一次为了取得每一项的值,调用的是每一项的toLocaleString()方法,而不是toString()方法
  • 以上三种方法,默认情况下都会以逗号分隔的字符串的形式返回数组项。而join()方法,则可用不同的分隔符来构建这个字符串,join()方法只接受一个参数,即用作分隔符的字符串。

    var colors = ["red","blue","green"];
    alert(colors.join(","))  //red,green,blue
    alert(colors.join("||"))  //red||green||blue
    

5.2.3 栈方法

  • 栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构,最新添加的项最早被移除
  • 栈的插入push()和移除pop(),只发生在一个位置——栈的顶部。

    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
    

5.2.4 队列方法

  • 队列是FIFO(First-In-First-Out,先进先出)
  • 从一个数组前端取得项的方法shift()
  • 使用shift()和push()方法,可以像使用队列一样使用数组

    var colors = new Array();
    var count = colors.push("red","green");
    alert(count);      //2
    
    count = colors.push("black");
    alert(count);      //3
    
    var item = colors.shift();    //取得第一项
    alert(item);            //"red"    
    alert(colors.length);   //2
    
  • 使用unshift()和pop()方法,可以从相反的方向来模拟队列,即在数组的前端添加项,从数组的末端移除项

    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
    

5.2.5 重排序方法

  • 有两个重排序的方法:reverse()和sort()
  • reverse()翻转数组项的顺序
  • sort()按升序排列数组项,该方法会调用每个数组项的toString()转型方法,然后比较的得到的字符串,以确定如何排序
  • 即使数组中的每一项都是数值,sort()方法比较的也是字符串

    var values = [0,1,5,10,15];
    values.sort();
    alert(values);    //0,1,10,15,5
    
    //sort()方法可以接受一个比较函数作为参数
    
    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);   //0,1,5,10,15
    
    //也可以通过比较函数产生降序排序结果,只要交换比较函数的返回值即可
    
    function compare(value1,value2){
        if(value1 < value2){
            return 1;
        }else if(value1>value2){
            return -1;
        }else{
            return 0;
        }
    }
    
    //对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用更简单的函数,即:
    function compare(value1,value2){
        return value2 - value1;
    }
    

5.2.6 操作方法

  • concat()方法可以给基于当前数组中的所有项创建一个新数组。

    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()方法基于当前数组中的一个或多个项创建一个新数组

    1. 一个参数时:返回从该参数指定位置开始到当前数组末尾的所有项
    2. 两个参数时:返回其实和结束位置之间的项,但不包括结束位置的项

      var colors = [“red”,”green”,”blue”,”yellow”,”purple”];
      var colors2 = colors.slice(1);//返回1到结束位置的项
      var colors3 = colors.slice(1,4);//返回1到3位置的项

      alert(colors2); //green,blue,yellow,purple
      alert(colors3); //green,blue,yellow

      //如果slice()方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置

      如:slice(-2,-1)与调用slice(3,4)结果一样
      

      //如果结束位置小雨起始位置,则返回空数组

  • splice()方法,主要用途是向数组的中部插入项

    1. 删除:可以删除任意数量的项,只需提供2个参数,eg:splice(0,2)会删除数组中的前两项
    2. 插入:可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)和要插入的项。eg:splice(2,0,”red”,”green”)会从当前数组的位置2开始插入字符串“red”和“green”.
    3. 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数和要插入的任意数量的项。传入的项数不必与删除的项数相等。eg:splice(2,1,”red”,”green”)会删除当前数组位置2的项,然后再从位置2开始插入字符串“red”和“green”

      //splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)            
      
      var colors = ["red","green","blue"];
      var removed = colors.splice(0,1);   //删除第一项
      alert(colors);     //green,blue
      alert(removed);   //red,返回数组中只包含一项
      
      rempved = 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.2.7 位置方法

  • indexOf():从数组的开头(位置0)开始向后查找
  • lastIndexOf()从数组的末尾开始向前查找
  • 这两个方法都接受两个参数,要查找的项和(可选的)表示查找起点位置的索引
  • 这两个方法都要返回查找的项在数组中的位置,在没有找到的情况下返回-1

    var numbers = [1,2,3,4,5,4,3,2,1];
    
    alert(numbers.indexOf(4));       //3
    alert(numbers.lastIndexOf(4));   //5
    
    alert(numbers.indexOf(4,4))      //5
    alert(numbers.lastIndexOf(4,4))  //3
    
    var person = {name:"Nicholas"};
    var people = [{name:"Nicholas"}];
    
    var morePeople = [person];
    
    alert(people.indexOf(person));      //-1
    alert(morePeople.indexOf(person));  //0
    

5.2.8 迭代方法

  • 5个迭代方法,每个方法接受两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响this的值。
  • 传入这些方法中的函数会接受三个参数:数组项的值、该项在数组中的位置和数组对象本身

    1. every():对数组中每一项运行给定函数,如果该函数对每一项都返回true,则返回true
    2. filter():对数组中每一项运行给定函数,返回该函数会返回true的项组成的数组
    3. forEach():对数组中每一项运行给定函数,这个方法没有返回值
    4. map():对数组中每一项运行给定函数,返回每次函数调用的结果组成的数组
    5. some():对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true
  • 以上方法都不会修改数组中的包含的值

    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){
        //执行某些操作
    })
    

5.2.9 缩小方法

  • 两个缩小数组的方法:reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值
  • reduce()方法从数组的第一项开始,逐个遍历到最后
  • reduceRight()则从数组的最后一项开始,向前遍历到第一项
  • 这两个方法都接受两个参数:一个在每一项上调用的函数和(可选的)作为缩小基础的初始值。
  • 传递给reduce()和reduceRight()的函数接受4个参数:前一个值、当前值、项的索引和数组对象
  • 这个函数返回的任何职都会作为第一个参数自动传给下一项

    var values = [1,2,3,4,5];
    var sum = values.reduce(function(pre,cur,index,array){
        return pre+cur;
    })
    alert(sum);     //15
    
    //reduceRight()的作用类似,只不过方向相反而已
    var values = [1,2,3,4,5];
    var sum = values.reduceRight(function(pre,cur,index,array){
        return pre+cur;
    })
    alert(sum);     //15
    

5.3 Date类型

  • 要创建一个日期对象,如下

    var now = new Date();
    
  • Date.parse()方法接受一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数

    //为2004年5月25日创建一个日期对象,如下:
    var someDate = new Date(Date.parse("May 25,2004"));
    //直接将表示日期的字符串传递给Date构造函数,也会在后台调用Date.parse(),即:下面这个与上面的效果一样
    var someDate = new Date("May 25,2004");//Tue May 25 2004 00:00:00 GMT+0800 (中国标准时间)
    
  • Date.UTC()返回表日期的毫秒数。其参数分别是年份、基于0的月份(1月是0,二月是1,以此类推)、月中的哪一天(1到31)、小时数(0到23)、分钟、秒以及毫秒数。这些参数中,只有前两个参数(年和月)是必须的。如果没有提供月中的天数,则假设天数为1;如果省略其他参数,则统统假设为0

    //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));
    
    //如果第一个参数是数值,Date构造函数会假设该值是日期中的年份,而第二个参数是月份,以此类推,因此上面的可以简写如下:
    var y2k = new Date(2000,0);
    var allFives = new Date(2005,4,5,17,55,55);//Thu May 05 2005 17:55:55 GMT+0800 (中国标准时间)
    
  • Data.now()方法,返回表示调用这个方法时的日期和时间的毫秒数

    //取得开始时间
    var start = Date.now();
    
    //调用函数
    doSomething();
    
    //取得停止时间
    var stop = Date.now();
        result = stop-start;
    
    //兼容性写法,在不支持Date.now()的浏览器中,使用+操作符把Date对象转换成字符串,也可以达到同样的目的
    var start = +new Date();
    doSomething();
    var stop = +new Date();
        result = stop-start;
    

5.3.1 继承的方法

  • Date类型也重写了toLocaleString()、toString()和valueOf()方法
  • toLocaleString()按照与浏览器设置的地区相适应的格式返回日期和时间,意味着时间格式中会包含AM或PM,但不包含时区信息。
  • toString()返回带有时区信息的日期和时间,时间一般以军用时间(小时的范围0到23)表示
  • 上面两个方法的差别仅在调试时有用,在显示日期和时间时没有什么价值
  • valueOf()方法,根本不返回字符串,而是返回日期的毫秒表示。因此可以方便使用比较操作符来比较日期

    var date1 = new Date(2007,0,1);   //"January 1,2007"
    var date2 = new Date(2007,1,1);   //"February 1, 2007"
    
    alert(date1 < date2);   //true
    

5.3.2 日期格式化方法

1. toDateString()——以特定于实现的格式显示星期几、月、日和年;
2. toTimeString()——以特定于实现的格式显示时、分、秒和时区;
3. toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
4. toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
5. toUTCString()——以特定于实现的格式完整的UTC日期。

5.3.3 日期/时间组件方法

5.4 RegExp类型

  • 正则表达式 var expression = / pattern / flags;

其中pattern是任何简单或复杂的正则表达式,可以包含字符类,限定符,分组,向前查找以及反向引用。
每个正则表达式都可带有一个或多个标志(flags),用以表明正则表达式的行为。

正则表达式的匹配模式支持下列3个标志
  • g:表示全局模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止
  • i:表示不区分大小写模式,即在确定匹配项时忽略模式与字符串的大小写
  • m:表示多行模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项

    因此,一个正则表达式就是一个模式与上述3个标志的组合体

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:

( [ { \ ^ $ | ? * + . } ] )

这些元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字符,就必须对他们进行转义。
传递给RegExp构造函数的两个参数都是字符串(不能把正则表达式字面量传递给RegExp构造函数),由于RegExp构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。
例如:

//匹配第一个“bat”或“cat”,不区分大小写
var pattern1 = /\[bc\]at/i;
var pattern2 = new RegExp("\\[bc\\]at","i");

//匹配所有以“at”结尾的3个字符的组合,不区分大小写
var pattern3 = /\.at/gi

5.4.1 RegExp实例属性

  • global:布尔值,表示是否设置了g标志
  • ignoreCase:布尔值,表示是否设置了i标志
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起
  • multiline:布尔值,表示是否设置了m标志
  • source:正则表达式的字符串表示,按照字面量形式而非传入都早函数中的字符串模式返回。

    var pattern1 = /\[bc\]at/i;
    alert(pattern1.global);     //false
    alert(pattern1.ignoreCase); //true
    alert(pattern1.lastIndex);  //0
    alert(pattern1.multiline);  //false
    alert(pattern1.source);        //"\[bc\]at"
    

5.4.2 RegExp 实例方法

  • 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]);      //" ang baby"
    
    //这个例子中的模式包含两个捕获组。最内部的是“and baby”
    
  • 第二个方法是test(),他接受一个字符串参数。在模式与该参数匹配的情况下返回true,否则,返回false。

    var text = "000-00-0000";
    var pattern = /\d{3}-\d{2}-\d{4}/;//匹配数字个数
    if(pattern.test(text)){
        alert('success');
    }
    

5.4.3 RegExp 构造函数属性

input,lastMatch,lastParen,LeftContent,multiline,rightContext

5.4.4 模式的局限性

5.5 Function类型

  • 函数通常是使用函数声明语法定义的,如下:

    function sum(num1,num2){
        return num1 + num2;
    }
    //这与下面使用函数表达式定义函数的方式几乎相差无几
    var sum = function(num1,num2){
        return num1 + num2;
    };
    差别:1.function关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没有必要使用函数名,通过变量sum即可引用函数
         2.还要注意函数末尾有个分号,就像声明其他变量一样
    
  • 函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字

    function sum(num1,num2){
        return num1+num2;
    }
    alert(sum(10,10));      //20
    
    var anotherSum = sum;
    alert(anotherSum(10,10));   //20
    
    sum = null;
    alert(anotherSum(10,10));   //20
    
  • 使用不带圆括号的函数名是访问函数指针,而非调用函数。本例中即使将sum设置为null,让他与函数“断绝关系”,但任然可以调用anotherSum()

5.5.1 没有重载(深入理解)

  • 两个同名函数,后者会覆盖前面的函数

5.5.2 函数声明与函数表达式

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用。
  • 至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

    alert(sum(10,10));
    function sum(num1,num2){
        return num1+num2;
    }//可以运行
    
    alert(sum(10,10));
    var sum = function(num1,num2){
        return num1 + num2;
    }//报错
    

5.5.3 作为值得函数

  • 不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

    function callSomeFunction(someFuntction,someArgument){
        return someFunction(someArgument);
    }
    //这个函数接受两个参数,第一个应该是一个函数,第二个应该是要传递给该函数的一个值
    
    function add10(num){
        return num + 10;
    }
    
    var result1 = callSomeFunction(add10,10);
    alert(result1);   //20
    
  • 要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对圆括号

  • 可以从一个函数中返回另一个函数

    function ceatComparisonFunction(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;
            }
        }
    }
    
    var data = [{name:"Zachary",age:28},{name:"Nicholas",age:29}];
    
    data.sort(ceatComparisonFunction("name"));
    alert(data[0].name);   //Nicholas
    
    data.sort(ceatComparisonFunction("age"));
    alert(data[0].name);   //Zachary
    

5.5.4 函数内部属性

  • 函数的内部,有两个特殊的对象:arguments和this
  • arguments是一个类数组对象,包含着传入函数中的所有参数
  • arguments对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数

    function factorial(num){
        if(num<=1){
            return 1;
        }else{
            return num * factorial(num-1)
        }
    }
    //函数的执行和函数名factorial紧紧耦合在一起了,为了消除耦合,可以使用arguments.callee
    
    function factorial(num){
        if(num<=1){
            return 1;
        }else{
            return num * arguments.callee(num-1)
        }
    }
    
    var trueFactorial = factorial;
    factorial = function(){
        return 0;
    }
    
    alert(trueFactorial(5));    //120
    alert(factorial(5));        //0
    
    //如果像原来的factorial()那样不使用arguments.callee,调用trueFactorial()就会返回0
    
  • this引用的是函数据以执行的环境对象(当在网页的全局作用域中调用函数时,this对象引用的就是window)

    window.color = "red";
    var o = {color:"blue"};
    
    function sayColor(){
        alert(this.color);
    }    
    
    sayColor();    //"red"
    
    o.sayColor = sayColor;
    o.sayColor();    //"blue"
    

5.5.5 函数属性和方法

  • 每个函数都包含两个属性: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
    
  • prototype 是保存他们所有实例方法的真正所在(诸如toString()和valueOf()等方法实际上都保存在其名下,只不过通过各自对象的实例访问罢了)

  • prototype属性是不可枚举的,因此使用for-in无法发现。
  • 每个函数都包含两个非继承的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值
  • apply()方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中第二个参数可以是Array的实例,也可以是arguments对象。

    function sum(num1,num2){
        return num1 + num2;
    }
    
    function callSum1(num1,num2){
        return sum.apply(this,arguments);       //传入window对象和arguments对象
    }
    
    function callSum2(num1,num2){
        return sum.apply(this,[num1,num2]);     //传入数组
    }
    
    alert(callSum1(10,10));    //20
    alert(callSum2(10,10));    //20
    
  • call()方法与apply()方法的作用相同,第一个参数是this值没有变化,变化的是其余参数都直接传递给函数,传递给函数的参数必须逐个列举出来

    function sum(num1,num2){
        return num1+num2;
    }
    
    function callSum(num1,num2){
        return sum.call(this,num1,num2);
    }
    
    alert(callSum(10,10));   //20
    

5.6 基本包装类型

  • 3个特殊的引用类型:Boolean、Number和String
  • 引用类型与基本包装类型的主要区别就是对象的生存期

5.6.1 Boolean类型

  • 创建Boolean对象,可以像下面这样调用Boolean构造函数并传入true或false值

    var booleanObject = new Boolean(true);
    
  • 布尔表达式中的所有对象都会被转换为true

    var falseObject = new Boolean(false);
    var result = falseObject && true;
    alert(result);  //true 
    
  • 基本类型与引用类型的布尔值有两个差别

    1. typeof操作符对基本类型返回“boolean”,而对引用类型返回“object”
    2. Boolean对象是Boolean类型的实例,所以instanceof操作符测试Boolean对象会返回true,而测试基本类型的布尔值则返回false

      alert(typeof falseObject);   //object
      alert(typeof falseValue);    //boolean
      alert(falseObject instanceof Boolean);   //true
      alert(falseValue instanceof Boolean);    //false
      
  • 建议永远不要使用Boolean对象

5.6.2 Number类型

  • 创建Number对象

    var numberObject = new Number(10);
    
  • toFixed()方法会按照指定的小数位返回熟知的字符串表示[存在兼容性问题]

    var num = 10;
    alert(num.toFixed(2));    //10.00
    
  • toExponential()用于栅格化数值,该方法返回以指数表示法表示的数值的字符串形式,也接受一个参数,而且该参数同样也是指定输出结果中的小数位数

    var num = 10;
    alert(num.toExponential(1));   //1.0e+1
    
  • 上面那个数值一般不必使用e表示法,表示一个数值最合适的格式,是使用toPrecision()方法,该方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)

    var num = 99;
    alert(num.toPrecision(1));    //1e+2
    alert(num.toPrecision(2));    //99
    alert(num.toPrecision(3));    99.0
    

5.6.3 String类型

  • 创建String对象

    var stringObject = new String("hello world");
    
  • String 类型的每个实例都有一个length属性,表示字符串中包含多个字符

    var stringValue = "hello world";
    alert(stringValue.length);    //11
    
  • String类型提供了很多方法,用于辅助完成对ECMAScript中字符串的解析和操作

    1. 字符方法

      • 两个用于访问字符串中特定字符的方法是:charAt()和charCodeAt(),这两个方法都接受一个参数,即基于0的字符位置
      • charAt()方法以单字符字符串的形式返回给定位置的那个字符

        var stringValue = "hello world";
        alert(stringValue.charAt(1));   //e
        
      • 如果你想得到的不是字符而是字符编码,就要使用charCodeAt()了

        var stringValue = "hello world";
        alert(stringValue.charCodeAt(1));   //101,小写字母e的字符编码
        
      • ECMAScript5还定义了另一个访问个别字符的方法【存在兼容性问题】

        var stringValue = "hello world";
        alert(stringValue[1]);    //e
        
    2. 字符串操作方法

      • concat()用于将一或多个字符串拼接起来,返回拼接得到的新字符串

        var stringValue = "hello ";
        var result = stringValue.concat("world");
        alert(result);      //hello world
        
      • concat()方法可以接受任意多个参数,就是说可以通过它拼接任意多个字符串

        var stringValue = "hello ";
        var result = stringValue.concat("world","!");
        alert(result);     //hello world!
        
      • 三个基于子字符串传创建新字符串的方法:slice(),substr()和substring(),这三个方法都返回被操作字符串的一个子字符串,而且也都接受一个或两个参数。参数一是指定子字符串的开始位置,参数二表示子字符串到哪里结束。

      • slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而substr()的第二个参数指定的则是返回字符个数。如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置。

        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
        
      • 在传递给这些方法的参数是负值的情况下,他们的行为就不尽相同了:

      • slice()方法会将传入的负值与字符串的长度相加
      • substr()方法将负的第一个参数加上字符串的长度,将负的第二个参数转换为0【ie可能有问题】
      • substring()方法会把所有负值参数都转换成0

        var stringValue = "hello world";
        alert(stringValue.slice(-3));             //rld
        alert(stringValue.substring(-3));         //hello world
        alert(stringValue.substr(-3));            //rld
        alert(stringValue.slice(3,-4));          //lo w
        alert(stringValue.substring(3,-4));      //hel,substring(3,0)由于这个方法会将较小的数作为开始的位置,将较大的数作为结束的位置,所以最终是substring(0,3)
        alert(stringValue.substr(3,-4));          //""空字符串,第二个参数相当于0,返回包含零个字符的字符串,也就是一个空字符串
        
    3. 字符串位置方法

      • indexOf()和lastIndexOf():这两个方法都是从一个字符串中搜索给定的子字符串,然后返回子字符串的位置(如果没有找到,返回-1)

        var stringValue = "hello world";
        alert(stringValue.indexOf("o"));     //4
        alert(stringValue.lastIndexOf("o")); //7
        
      • 这两个方法都可以接受可选的第二个参数,表示从字符串中的那个位置开始搜索。即indexOf()会从该参数指定的位置向后检索,忽略该位置之前的所有字符串;而lastIndexOf()则会从指定的位置向前搜索,忽略该位置之后的所有字符。

        var stringValue = "hello world";
        alert(stringValue.indexOf("o",6));     //7
        alert(stringValue.lastIndexOf("o",6));  //4
        
      • 在使用第二个参数的情况下,可以通过循环调用indexOf()或lastIndexOf()来找到所有匹配的子字符串

        var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
        var positions = new Array();
        var pos = stringValue.indexOf("e");
        
        while(pos > -1){
            positions.push(pos);
            pos = stringValue.indexOf("e",pos + 1);
        }
        
        alert(positions);      //3,24,32,35,52
        
    4. trim()方法:这个方法会创建一个字符串的副本,删除前置后缀的所有空格。

      var stringValue = "   hello world   ";
      var trimmedStringValue = stringValue.trim();
      alert(stringValue);           //"   hello world   ";
      alert(trimmedStringValue)     //"hello world"
      
      • 由于trim()返回的是字符串的副本,所以原始字符串中的前置和后缀空格会保持不变。【ie8及以下不支持此属性】
    5. 字符串大小写转换

      • toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()

        var stringValue = "hello world";
        alert(stringValue.toLocaleUpperCase());   //"HELLO WORLD"
        alert(stringValue.toUpperCase());         //"HELLO WORLD" 
        alert(stringValue.toLocaleLowerCase());   //"hello world"
        alert(stringValue.toLowerCase());         //"hello world"
        
      • toLocaleLowerCase()和toLocaleUpperCase()方法是针对特定地区的实现

      • 一般来说,在不知道自己的代码将在哪种语言环境中运行的情况下,还是使用针对地区的方法更稳妥些
    6. 字符串的模式匹配方法

      String类型定义了几个用于在字符串中匹配模式的方法。

      第一个是match(),在字符串上调用这个方法,本质上与调用RegExp的exec()方法相同。只接受一个参数,正则表达式或RegExp对象

      var text = "cat,bat,sat,fat";
      var pattern = /.at/g;
      
      //与pattern.exec(text)相同
      var matches = text.match(pattern);
      alert(matches.index);               //0
      alert(matches[0]);                    //"cat"
      alert(pattern.lastIndex);           //0
      

      第二个是search(),与match()方法传递的参数相同,由字符串或RegExp对象指定的一个表达式。该方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。

      var text = "cat,bat,sat,fat";
      var pos = text.search(/at/);
      alert(pos);        //1,找的是a的索引而不是cat
      

      第三个是replace()方法,接受两个参数,一个参数可以是RegExp对象或一个字符串,第二个参数可以是一个字符串或者一个函数。
      如果第一个参数是字符串,那么只会替换第一个子字符串,要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局(g)标志

      var 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,song,fong"
      

      最后一个方法是split(),这个方法基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个RegExp对象。
      这个方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。

      var colorText = "red,blue,green,yellow";
      var colors1 = coloeText.split(",");   //["red","blue","green","yellow"]
      var colors2 = coloeText.split(",",2);  //["red","blue"]
      var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
      
    7. localeCompare()方法,这个方法比较两个字符串,并返回下列值中的一个

      • 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定);
      • 如果字符串等于字符串参数,则返回0;
      • 如果字符串在字母表中应该排在字符串参数制后,则返回一个正数(大多数情况下是1,具体的值同样要视实现而定)。

        var stringValue = "yellow";
        alert(stringValue.localeCompare("brick"));    //1
        alert(stringValue.localeCompare("yellow"));   //0
        alert(stringValue.localeCompare("zoo"));      //-1
        
    8. fromCharCode()方法,这个方法的任务是接受一或多个字符编码,然后将它们转换成一个字符串。从本质上来看,这个方法与实例方法charCodeAt()执行的是相反的操作

      alert(String.fromCharCode(104,101,108,108,111));   //"hello"
      
    9. HTML方法

5.7 单体内置对象

  • 内置对象是指:由ECMAScript实现提供的,不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。意思是说,开发人员不必显示的实例化内置对象,因为他们已经实例化了。如:Object、Array和String。ECMA-262还定义了两个单体内置对象:Global和Math。

5.7.1 Global对象

  • Global对象不属于任何其他对象的属性和方法,最终都是他的属性和方法。之前介绍的isNaN()、isFinite()、parseInt()以及parseFloat()实际上都是Global对象的方法。此外,Global对象还包含其他一些方法。

    1. URI编码方法

      • Global对象的encodeURI()和encodeURIComponent()方法可以对URI进行编码,以便发送给浏览器。有效的URI中不包含某些字符,例如空格。这两个URI编码方法就可以对URI进行编码,他们用特殊的UTF-8编码替换所有无效的字符,从而让浏览器能够接受和理解
      • encodeURI()主要用于整个URI
      • encodeURIComponent()主要对URI中的某一段进行编码
      • 区别是encodeURI()不会对本身属于URI的特殊字符进行编码,如冒号、正斜杠、问号和井号,而encodeURIComponent()则会对它发现的任何非标准字符进行编码
      • 一般来说,我们使用encodeURIComponent()方法的时候要比使用encodeURI()更多,因为在实践中更常见的是对查询字符串参数而不是对基础URI进行编码
      • 与encodeURI()和encodeURIComponent()方法对应的两个方法分别是decodeURI()和decodeURIComponent(),即解码
    2. eval()方法*没看懂

    3. Global对象的属性
    4. window对象

      • 取得Global对象的方法:

        var global = function(){
            return this;
        }();
        

5.7.2 Mathd对象

  1. Math对象的属性

  2. min()和max()方法

    • 用于确定一组数值中的最小值和最大值。这两个方法都可以接受任意多个数值

      var max = Math.max(3,54,32,16);
      alert(max);     //54
      var min = Math.min(3,54,32,16);
      alert(min);     //3
      
    • 要找到数组中的最大值或最小值,可以像下面这样使用apply()方法

      var values = [1,2,3,4,5,6,7,8];
      var max = Math,max.apple(Math,value);
      //这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确地设置this值,然后,可以将任意术组作为第二个参数
      
  3. 舍入方法

    • Math.ceil()执行向上舍入,即他总是将数值向上舍入最最接近的整数
    • Math.floor()执行向下舍入,即他总是将数值向下舍入最最接近的整数
    • Math.round()执行标准舍入,即他总是将数值四舍五入为最接近的整数(数学课上学的)

      alert(Math.ceil(25.9));   //26
      alert(Math.ceil(25.5));   //26
      alert(Math.ceil(25.1));   //26
      
      alert(Math.round(25.9));   //26
      alert(Math.round(25.5));   //26
      alert(Math.round(25.1));   //25
      
      alert(Math.floor(25.9));   //25
      alert(Math.floor(25.5));   //25
      alert(Math.floor(25.1));   //25
      
  4. random()方法

    • 返回介于0和1之间的一个随机数,不包括0和1
    • 套用下面的公式,可以利用Math.random()从某个整数范围内随机选择一个值

      值 = Math.floor(Math.random() * 可能值得总数 + 第一个可能的值)
      
    • 如果想选择一个1到10之间的数值,可以像下面这样编写代码:

      var num = Math.floor(Math.random() * 10 + 1);
      //总共有10个可能的值(1到10),而第一个可能的值是1.而如果想要选择一个介于2到10 之间的值,就应该如下:
      var num = Math.floor(Math.radom() * 9 + 2);
      //从2数到10要9个数,因此可能值得总数是9,第一个可能值是2
      
    • 多数情况下,都可以通过一个函数来计算可能直的总数和第一个可能的值

      function selectFrom(lowerValue,upperValue){
          var choices = upperValue - lowerValue + 1;
          return Math.floor(Math.radom() * choices + lowerValue);
      }
      
      var num = selectFrom(2,10);
      alert(num);   //介于2和10之间(包括2和10)的一个数值
      
    • 利用这个函数,可以方便地从数组中随机取出一项

      var colors = ["red","green","blue","yellow","black","purple","brown"];
      var color = colors[selectFrom(0,colors.length-1)];
      alert(color);   //可能是数组中包含的任何一个字符串
      
  5. 其他方法

第六章 面向对象的程序设计

6.1 理解对象

6.1.1 属性类型

  1. 数据属性

    • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性
    • [[Enumerable]]:表示能否通过for-in循环返回属性
    • [[Writable]]:表示能否修改属性的值
    • [[Value]]:包含这个属性的数据值

      要修改属性默认的特性,必须使用Object.defineProperty()方法

  2. 访问器属性:不包含数据值,他们包含一对getter和setter函数

    • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性
    • [[Enumerable]]:表示能否通过for-in循环返回属性
    • [[Get]]:在读取属性时调用的函数。默认值为undefined。
    • [[Set]]:在写入属性时调用的函数。默认值为undefined。

      访问器属性不能直接定义,必须使用Object.defineProperty()来定义

6.1.2 定义多个属性

  • 由于为对象定义多个属性的可能性很大,ECMAScript 5有定义了一个Object.defineProperties()方法。利用这个方法可以通过描述符一次性定义多个属性

6.1.3 读取属性的特性

  • 使用ECMAScript 5的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符

6.2 创建对象

6.2.1 工厂模式

  • 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。考虑到在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。

    function createPerson(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 person1 = createPerson("Nicholas",29,"Software Engineer");
    var person2 = caeatePerson("Greg",27,"Doctor");
    
  • 工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

6.2.2 构造函数模式

  • ECMAScript中的构造函数可以用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。
  • 也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如,可以使用构造函数模式将前面的例子重写如下:

    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");
    
  • Person()函数取代了createPerson()函数。其不同之处有:

    - 没有显示地创建对象;
    - 直接将属性和方法赋给了this对象;
    - 没有return 语句。
    
  • 还有函数名Person使用的是大写字母P(按照惯例,构造函数始终都应该以一个大写字母开头)

  • 要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:

    • 创建一个新对象;
    • 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
    • 执行构造函数中的代码(为这个新对象添加属性);
    • 返回新对象。

      1. 将构造函数当做函数

        //当做构造函数使用
        var person = new Person("Nicholas",29,"Software Engineer");
        person.sayName();      //Nicholas
        
        //作为普通函数调用
        Person("Greg",27,"Doctor");    //添加到window
        window.sayName();   //"Greg"
        
        //在另一个对象的作用域中调用
        var o = new Object();
        Person.call(o,"Kristen",25,"Nurse");
        o.sayName();   //"Kristen"
        
      2. 构造函数的问题

        • 使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍
        • 不同实例上的同名函数是不等价的

          alert(person1.sayName == person2.sayName);   //false
          
        • 创建两个完成同样任务的Function实例的确没有必要,所以可以把函数定义转移到构造函数外部,如下

          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");
          
        • 问题1:全局函数只能被某个对象调用

        • 问题2:若需要很多方法,则要定义很多全局函数,就没有封装性可言了
        • 因此有了原型模式

6.2.3 原型模式

  • 我们创建的每一个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法
  • prototype就是通过调用构造函数而创建的那个对象实例的原型对象
  • 使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法
  • 即不必在欧早函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中

    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
    
    1. 理解原型对象

      • Person:一个空的构造函数
      • Person.prototype:指向了原型对象
      • Person1和Person2是Person的两个实例
      • 可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系

        alert(Person.prototype.isPrototypeOf(person1));   //true
        alert(Person.prototype.isPrototypeOf(person2));   //true
        //证明了person1和person2内部都有一个指向Person.prototype的指针
        
      • Object.getPrototypeOf():返回[[Prototype]]的值

        alert(Object.getPrototypeOf(person1) == Person.prototype);  //true  返回的对象实际上就是这个对象的原型
        alert(Object.getPrototypeOf(person1).name);   //"Nicholas"  取得了原型对象中name属性的值
        
      • 可以使用delete操作符删除实例属性,从而让我们能够重新访问原型中的属性

        delete person1.name;
        
      • 使用 hasOwnProperty 方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才会返回true

        person.name = "Greg";
        alert(person1.name);   //"Greg" 来自实例
        alert(person1.hasOwnProperty("name"));   //true
        
        delete person1.name;
        alert(person1.name);   //"Nicholas" 来自原型
        alert(person1.hasOwnProperty("name"));   //false
        
    2. 原型与in操作符

      • 有两种方式使用in操作符:单独使用和在for-in 循环中使用
      • 单独使用时:in 操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
      • 同时使用 hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中

        function hasPrototypeProperty(object,name){
            return !object.hasOwnProperty(name)&&(name in object);
        }
        
        function Person(){
        }
        
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
            alert(this.name);
        }
        
        var person = new Person();
        alert(hasPrototypeProperty(person,"name"));    //true 存在于原型中
        
        person.name = "Greg";
        alert(hasPrototypeProperty(person,"name"));    //false 存在于对象中
        
      • 在使用for-in循环时,返回的是所有能够通过对象反问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。

      • 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);     //"consructor,name,age,job,sayName"
        
    3. 更简单的原型语法

      • 为减少不必要的输入,也为了从视觉上更好的封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重新写整个原型对象

        function Person(){
        }
        
        Person.prototype = {
            name:"Nicholas",
            age:29,
            job:"Software Engineer",
            sayName = function(){
                alert(this.name);
            }
        };
        
      • 此时,constructor 属性不在指向Person了,虽然用instanceof操作符测试Object和Person仍然返回true,但constructor属性则等于Object而不等于Person了。如果constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值

        function Person(){
        }
        
        Person.prototype = {
            constructor:Person,
            name:"Nicholas",
            age:29,
            job:"Software Engineer",
            sayName: function(){
                alert(this.name);
            }
        };
        //以这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true。
        
    4. 原型的动态性

      • 原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实力上反映出来——即使是先创建了实例后修改原型也照样如此。

        var friend = new Person();
        Person.prototype.sayHi = function(){
            alert("Hi");
        };
        friend.sayHi();   //"Hi"  没有问题
        
      • 如果重写整个原型对象,就等于切断了构造函数与最初原型之间的联系

      • 谨记:实例中的指针仅指向原型,不指向构造函数

        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
        
      • 这个例子中,我们先创建了Person的一个实例,然后又重写了其原型对象,然后在调用friend.sayName()时发生率错误,因为friend指向的原型中不包含以该名字命名的属性

    5. 原生对象的原型

      • 可以修改原生对象的原型,但是不推荐这样做
    6. 原型对象的问题

      • 他省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值

        function Person(){
        }
        Person.prototype = {
            constructor:Person,
            name:"Nicholas",
            age:29,
            job:"Software Engineer",
            friend:["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
        
      • 实例一般都是要有属于自己的全部属性的

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","Court","Van"
    alert(person2.friends);     //"shelby","Court"
    alert(person1.friends === person2.friends);     //false
    alert(person1.sayName === person2.sayName);     //true
    
  • 这个例子中实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。

  • 修改了person1.friends,并不会影响到person2.friends,因为他们分别引用了不同的数组

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();
    
  • 只有在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"
    
  • 这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。

    function SpecialArray(){
    
        //创建数组
        var values = new Array();
    
        //添加值
        values.push.apply(values,arguments);
    
        //添加方法
        values.toPipedString = function(){
            return this.join("|");
        };
    
        //返回数组
        return values;
    }
    
    var colors = new SpecialArray("red","blue","green");  //调用SpecialArray构造函数
    alert(colors.toPipedString());  //"red|blue|green"
    
  • 构造函数返回的对象与构造函数外部创建的对象没有什么不同

6.2.7 稳妥构造函数模式

  • 稳妥对象:没有公共属性,而且其方法也不引用this的对象。
  • 稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用
  • 稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:

    1. 新创建对象的实例方法不引用this
    2. 不适用new操作符调用构造函数
    
  • 按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

    function Person(name,age,job){
    
        //创建要返回的对象
        var o = new Object();
        //可以在这里定义私有变量和函数
    
        //添加方法
        o.sayName = function(){
            alert(name);
        };
    
        //返回对象
        return o;
    }
    
  • 注意:以这种模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值。可以像下面使用稳妥的Person构造函数。

    var friend = new Person("Nicholas",29,"Software Engineer");
    friend.sayName();   //"Nicholas"
    
  • 与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有意义。

6.3 继承

  • ECMAScript只支持实现集成,而且其实现继承主要是依靠原型链来实现。

6.3.1 原型链

  • 利用原型让一个引用类型继承另一个引用类型的属性和方法
  • 构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
  • 如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假设另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与圆形的链条,这就是原型链的基本概念。
  • 实现原型链有一种基本模式,大致如下:

    function SuperType(){
        this.property = true;
    }
    
    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    
    function SubType(){
        this.subproperty = false;
    }
    
    //SubType 继承了 SuperType
    SubType.prototype = new SuperType();
    
    SubType.prototype.getSubValue = function(){
        return this.subproperty;
    };
    
    var instance = new SubType();
    alert(instance.getSuperValue());   //true
    
    1. 别忘记默认的原型

      • 所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、value()等默认方法的根本原因。
      • 一句话,SubType 继承了 SuperType,而SuperType继承了Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。
    2. 确定原型和实例的关系

      • 可以通过两种方式来确定原型和实例之间的关系

        1. 方式一:使用 instanceof 操作符

          alert(instance instanceof Object);        //true
          alert(instance instanceof SubType);       //true
          alert(instance instanceof SuperType);     //true
          //由于原型链的关系,我们可以说instance是Object、SuperType或SubType中任何一个类型的实例
          
        2. 方式二:使用 isPrototypeOf()方法。

          alert(Object.prototype.isPrototypeOf(instance));    //true
          alert(SubType.prototype.isPrototypeOf(instance));   //true
          alert(SuperType.prototype.isPrototypeOf(instance)); //true
          //只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
          
    3. 谨慎地定义方法

      • 给原型添加方法的代码一定要放在替换原型的语句之后。

        function SuperType(){
            this.property = true;
        }
        
        SuperType.prototype.getSuperValue = function(){
            return this.property;
        };
        
        function SubType(){
            this.subproperty = false;
        }
        
        //SubType 继承了 SuperType
        SubType.prototype = new SuperType();
        
        //添加新方法
        SubType.prototype.getSubValue = function(){
            return this.subproperty;
        };
        
        //重写超类型中的方法
        SubType.prototype.getSuperValue = function(){
            return false;
        };
        
        var instance = new SubType();
        alert(instance.getSuperValue());   //false
        
      • 还有一点,在通过原型链实现继承是,不能使用对象字面量创建原型方法。因为这样做就会重写原型链

        function SuperType(){
            this.property = true;
        }
        
        SuperType.prototype.getSuperValue = function(){
            return this.property;
        };
        
        function SubType(){
            this.subproperty = false;
        }
        
        //SubType 继承了 SuperType
        SubType.prototype = new SuperType();
        
        //使用字面量添加新方法,会导致上一行代码无效
        SubType.prototype = {
            getSubValue : function(){
                return this.subproperty;
            },
            someOtherMethod : function(){
                return false;
            }
        };
        
        var instance = new SubType();
        alert(instance.getSuperValue());   //error!    
        
      • 由于现在的原型包含的是一个Object实例,而非SuperType的实例,因此我们设想中的原型链已被切断—— SubType和SuperType之间已经没有关系了。

    4. 原型链的问题

      • 问题一:包含引用类型值的原型属性会被所有实例共享(这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因)

        function SuperType(){
            this.colors = ["red","blue","green"];
        }
        
        function SubType(){
        }
        
        //SubType 继承了 SuperType
        SubType.prototype = new SuperType();
        
        var instance1 = new SubType();
        instance1.colors.push("black");
        alert(instance1.colors);      //"red","blue","green","black"
        
        var instance1 = new SubType();
        alert(instance2.colors);      //"red","blue","green","black"
        
      • 问题二:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

      • 实例中很少会单独使用原型链。

6.3.2 借用构造函数

  • 在子类型构造函数的内部调用超类型构造函数
  • 函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数

    function SuperType(){
        this.colors = ["red","blue","green"];
    }
    
    function SubType(){
        //继承了 SuperType
        SuperType.call(this);
    }
    
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors);    //"red","blue","green","black"
    
    var instance2 = new SubType();
    alert(instance2.colors);      //"red","blue","green"
    
  • 通过使用call()方法(或apply()方法),我们实际上是在新创建的SubType实例的环境下调用了SuperType构造函数。这样,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。

    1. 传递参数

      function SuperType(){
          this.name = name;
      }    
      
      function SubType(){
          //继承了 SuperType,同事还传递了参数
          SuperType.call(this,"Nicholas");
      
          //实例属性
          this.age = 29;
      }
      
      var instance = new SubType();
      alert(insance.name);   //"Nicholas"
      alert(instance.age);   //29
      
    2. 借用构造函数的问题

      • 如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。
      • 而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式
      • 因此,借用构造函数的技术也是很少单独使用的

6.3.3 组合继承

  • 也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
  • 思路:使用原型链四线对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

    function SuperType(){
        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.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
    
  • 组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript最常用的继承模式

  • instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象

6.3.4 原型式继承

  • 这种方法并没有严格意义上的构造函数,想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到这个目的,给出了如下函数:

    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    
    var person = {
        name:"Nicholas",
        friends:["Shelby","Court","Van"]
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
    
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    
    alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
    
  • ECMAScript5 通过新增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()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性

    var person = {
        name:"Nicholas",
        friends:["Shelby","Court","Van"]
    }
    
    var anotherPerson = object.create(person,{
        name:{
            value:"Greg"
        }
    });
    
    alert(anotherPerson.name);   //"Greg"
    
  • 在没有必要兴师动众的创建构造函数,而只是想让一个对象与另一个对象保持类似的情况下,原型继承是完全可以胜任的。不过,包含引用类型值的属性始终都会共享相应地值,就像使用原型模式一样。

6.3.5 寄生式继承

  • 与原型是继承紧密相关的一种思路。
  • 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是他做了所有工作一样返回对象。

    function createAnother(original){
        var clone = object(original);   //通过调用函数创建一个新对象
        clone.sayHi = function(){       //以某种方式来增强这个对象
            alert("hi");
        };
        return clone;                   //返回这个对象
    }
    
  • 可以像下面这样来使用createAnother()函数:

    var person = {
        name:"Nicholas",
        friends:["Shelby","Court","Van"]
    };
    
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();   //"hi"
    
  • 在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式

  • 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似

6.3.6 寄生组合式继承

  • 组合继承最大的问题:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

    function SuperType(){
        this.name = name;
        this.colors = ["red","blue","green"];
    }
    
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    function SubType(name,age){
        SuperType.call(this.name);   //第二次调用 SuperType()
        this.age = age;
    }
    
    SubType.prototype = new SuperType();  //第一次调用 SuperType()
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
        alert(this.age);
    }
    
  • 有两组name和colors属性:一组在实例上,一组在SubType原型中。这就是调用两次SuperType构造函数的结果。解决办法——寄生组合式继承。

  • 所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
  • 思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
  • 本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型

    function inheritPrototype(subType,superType){
        var prototype = object(superType.prototype);   //创建对象
        prototype.constructor = subType;               //增强对象
        subType.prototype = prototype;                 //指定对象
    }
    
    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;
    }
    
    inheritPrototype(SubType,SuperType);
    
    SubType.prototype.sayAge = function(){
        alert(this.age);
    }
    
  • 这个例子的高效率体现在它之调用了一次 SuperType 构造函数,并因此避免了在 SubType.prtotype 上面创建不必要的、多余的属性。

  • 原型链还能保持不变
  • 还能正常使用 instanceof 和 isPrototypeOf()。

开发人员普遍认为 寄生组合式继承是引用类型最理想的继承方式

第七章 函数表达式

7.1 递归

用arguments.callee可以解决函数被重定义导致的错误,arguments.callee是一个指向正在执行的函数的指针

function factorial(num){
    if(num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num-1);
    }
}

但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果

function factorial = (function f(num){
    if(num <= 1){
        return 1;
    }else{
        return num * f(num-1);
    }
});

7.2 闭包

闭包是指有权访问另一个函数作用域中的变量的函数

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,建议在绝对必要时再考虑使用闭包

7.2.1 闭包与变量

  • 闭包只能取得包含函数中任何变量的最后一个值。
  • 闭包所保存的是整个变量对象,而不是某个特殊的变量

    function createFunctions(){
        var result = new Array();
        for(var i=0; i<10; i++){
            result[i] = function(){
                return i;
            }
        }
        return result;
    }
    
    createFunctions()[0](); //10
    createFunctions()[1](); //10
    ...
    createFunctions()[9](); //10
    
    //可以通过创建另一个匿名函数强制让闭包行为符合预期        
    
    function createFunctions(){
        var result = new Array();
        for(var i=0; i<10; i++){
            result[i] = function(num){
                return function(){
                    return num;
                };
            }(i)
        }
        return result;
    }
    
    createFunctions()[0](); //0
    createFunctions()[1](); //1
    ...
    createFunctions()[9](); //9        
    

7.2.2 关于this 对象

-通过把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了

var name = "The Window";
var object = {
    name:"My Object",
    getNameFunc:function(){
        var that = this;
        return function(){
            return that.name;
        }
    }
}
alert(object.getNameFunc()());  //"My Object"

7.2.3 内存泄漏

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        alert(element.id);
    };
}

//正确地写法
function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

7.3 模仿块级作用域

用作块级作用域(通常称为私有作用域)的匿名函数的语法如下

(function(){
    //这里是块级作用域
 })();

7.4 私有变量

7.4.1 静态私有变量

7.4.2 模块模式

7.4.3 增强的模块模式

第8章 BOM

8.1 window对象

8.1.1 全局作用域

  • 全局变量不能通过delete操作符删除,而直接在window对象上定义的属性可以

    var age = 29;
    window.color = "red";
    delete window.age;
    delete window.color;
    console.log("window.age:"+window.age);   //29
    console.log("window.color:"+window.color);  //undefined
    

8.1.2 窗口关系及框架

8.1.3 窗口位置

  • 跨浏览器取得窗口左边和上边的位置

    var leftPos = (typeof window.screenLeft == "number") ? window.screenLeft : window.screenX;
    var topPos = (typeof window.screenTop == "number") ? window.screenTop : window.screenY;
    

8.1.4 窗口大小

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • 由于各浏览器之间的差异,虽然无法确定浏览器窗口本身大小,却可以取得页面视口的大小

    var pageWidth = window.innerWidth,
    pageHeigth = window.innerHeight;
    
    if(typeof pageWidth != "number"){
        if(document.compatMode == "CSS1Compat"){//检测页面是否处于标准模式
            pageWidth = document.documentElement.clientWidth;
            pageHeigth = document.documentElement.clentHeight;
        } else{
            pageWidth = document.body.clientWidth;
            pageHeigth = document.body.clientHeight;
        }
    }
    
  • 由于与桌面浏览器间存在这些差异,最好是先检测一下用户是否在使用移动设备,然后再决定使用哪个属性

  • 使用resizeTo()和resizeBy()方法可以调整浏览器窗口的大小,这两个方法都接受两个参数,其中resizeTo()接收浏览器窗口的新宽度和新高度,而resizeBy()接收新窗口与原创看的宽度和高度之差。

8.1.5 导航和打开窗口

  • window.open()接收4个参数:要加载的URL,窗口目标,一个特性字符串,一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。
  • 通常只需传递第一个参数,最后一个参数只在不打开新窗口的情况下使用

    window.open("http://www.xcar.com.cn/","topFrame");
    
  • 调用上面的代码,就会在一个名叫“topFrame”的窗口或框架上加载这个URL。否则就会创建一个新窗口并将其命名为“topFrame”。

  • 第二个参数也可以是任何一个特殊的窗口名称:_self,_parent,_top,_blank。

    1. 弹出窗口

    2. 安全限制

    3. 弹出窗口屏蔽程序

8.1.6 间歇调用和超时调用

var timeoutId = setTimeout(function(){
    alert("Hello");
},1000)

clearTimeout(timeoutId);
  • 例子如下:

    var num = 0;
    var max = 10;
    var intervalId =null;
    function incrementNumber(){
        num++;
        console.log("num:"+num);
        if(num == max){
            clearInterval(intervalId);
            alert("Done");
        }
    }
    intervalId = setInterval(incrementNumber,500);
    
  • 这个例子也可以用超时调用来实现

    var num = 0;
    var max = 10;
    
    function incrementNumber(){
        num++;
        console.log("num:"+num);
        if(num < max){
            setTimeout(incrementNumber,500)
        }else{
            alert("Done");
        }
    }
    setTimeout(incrementNumber,500);
    
  • 因此最好不用使用间歇调用,以免发生冒泡事件。

8.1.7 系统对话框

  • alert() 警告对话框
  • confirm() 确认对话框,除了显示OK还会显示Cancel按钮
  • prompt() 提示框,除了显示OK还会显示Cancel按钮外,还会显示一个文本输入域。该方法接受两个参数:要显示给用户的文本提示和文本输入域的默认值(可以是一个空字符串)
  • window.print();显示打印对话框
  • window.find();显示查找对话框

8.2 location对象

8.2.1 查询字符串参数

function getQueryStringArgs(){
    var qs = (location.search.length > 0 ? location.search.substring(1):""),
    args = {},
    items = qs.length ? qs.split("&") :[],
    item = null,
    name = null,
    value = null,

    i = 0,
    len = items.length;

    for(i=0;i<len;i++){
        item = items[i].split("=");
        name = decodeURIComponent(item[0]);
        value = decodeURIComponent(item[1]);

        if(name.length){
            args[name] = value;
        }
    }
    return args;
}

//假设查询字符串是?q=javascript&num=10
var args = getQueryStringArgs();
console.log("args[q]:"+args["q"]);       //args[q]:javascript
console.log("args[num]:"+args["num"]);  //args[num]:10

8.2.2 位置操作

  • 使用assign()方法并为其传递一个URL

    location.assign("http://www.xcar.com.cn");
    
  • 通过上述任何一种方式修改URL后,浏览器的历史记录中就会生成一条新纪录,因此用户通过单击“后退”按钮就会导航到前一个页面,要禁用这种行为,可以使用replace()方法,这个方法只接受一个参数,即要导航到的URL。

    setTimeout(function(){
        location.replace("http://www.xcar.com.cn");
    },1000)
    
  • reload()方法,重新加载当前显示的页面,如果调用reload()时不传递任何参数,页面就会以最有效的方式重新加载。即:如果页面自上次请求后并没有改变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,则需要像下面这样为该方法传递参数true。

    location.reload();       //重新加载(有可能从缓存中加载)
    location.reload(true);   //重新加载(从服务器重新加载)
    
  • 位于reload()调用之后的代码可能会也可能不会执行,这样取决于网络延迟或系统资源等因素。为此,最好将reload()放在代码的最后一行。

8.3 navigator对象

  • navigator对象,现在已成为识别客户端浏览器的事实标准

8.3.1 检测插件

//检测插件(在IE中无效)
function hasPlugin(name){
    name = name.toLowerCase();
    for(var i = 0; i<navigator.plugins.length;i++){
        if(navigator.plugins[i].name.toLowerCase().indexOf(name) > -1){
            return true;
        }
    }
    return false;
}

//检测IE中的插件
function hasIEPlugin(name){
    try{
        new ActiveXObject(name);
        return true;
    }catch(ex){
        return false;
    }
}

//检测所有浏览器中的Flash
function hasFlash(){
    var result = hasPlugin("Flash");
    if(!result){
        result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash");
    }
    return result;
}

//检测所有浏览器中的QuickTime
function hasQuickTime(){
    var result = hasPlugin("QuickTime");
    if(!result){
        result = hasIEPlugin("QuickTime.QuickTime")
    }
    return result;
}

console.log(hasFlash());
console.log(hasQuickTime());

8.3.2 注册处理程序

8.4 screen对象

  • 用来表明客户端的能力,其中包括浏览器窗口外部的显示器的信息,如像素宽度和高度等

8.5 history对象

  • 保存着用户的上网记录,从窗口被打开的那一刻算起。

第九章 客户端检测

  • 检测web客户端的手段很多,而且各有利弊。但不到万不得已,就不要使用客户端检测。只要找到更通用的方法,就应该优先采用更通用的方法。

9.1 能力检测

  • 能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力。采用这种方式不必顾及特定的浏览器如何如何,只要确定连蓝旗支持特定的能力,就可以给出解决方案。

    function getElement(id){
        if(document.getElementById){
            return document.getElementById(id);
        }else if(document.all){
            return document.all[id];
        }else{
            throw new Error("No way to retrieve element!");
        }
    }
    
  • 要理解能力检测,首先必须理解两个重要的概念:1.先检测达成目的的最常用特性。2. 必须测试实际要用到的特性

9.1.1 更可靠的能力检测

//作者:Peter Michaux
var xhr = new ActiveXObject("Microsoft.XMLHttp");
function isHostMethod(object,property){
    var t = typeof object[property];
    return t== 'function' || (!!(t=='object'&& object[property]))|| t=='unknow';
}


result = isHostMethod(xhr,"open");   //true
result = isHostMethod(xhr,"foo");    //false

9.1.2 能力检测,不是浏览器检测

  • 在实际开发中,应该将能力检测作为确定下一步解决方案的依据,而不是用它来判断用户使用的是什么浏览器。

    //确定浏览器是否支持Netscape风格的插件
    var hasNSPlugins = !!(navigator.plugins&& navigator.plugins.length);
    
    //确定浏览器是否具有DOM1级规定的能力
    var hasDOM1 = !!(document.getElementById&& document.createElement && document.getElementsByTagName);
    

9.2 怪癖检测

  • 目标是识别浏览器的特殊行为。但与能力检测确认浏览器支持什么能力不同,怪癖检测是要知道浏览器存在什么缺陷(“怪癖”也就是bug)

9.3 用户代理检测

  • 通过检测用户代理字符串来确定实际使用的浏览器

9.3.1 用户代理字符串的历史
9.3.2 用户代理字符串检测技术
9.3.3 完整的代码
9.3.4 使用方法

第10章 DOM

10.1 节点层次

10.1.1 Node 类型

  • 节点nodeType属性:

    1. Node.ELEMENT_NODE(1);
    2. Node.ATTRIBUTE_NODE(2);
    3. Node.TEXT_NODE(3);
    4. Node.CDATA_SECTION_NODE(4);
    5. Node.ENTITY_REFERENCE_NODE(5);
    6. Node.ENTITY_NODE(6);
    7. Node.PROCESSING_INSTRUCTION_NODE(7);
    8. Node.COMMENT_NODE(8);
    9. Node.DOCUMENT_NODE(9);
    10. Node.DOCUMENT_TYPE_NODE(10);
    11. Node_DOCUMENT_FRAGMENT_NODE(11);
    12. Node.NOTATION_NODE(12).
  • 确定节点类型

    if(some.nodeType == Node.ELEMENT_NODE){ //在IE中无效
        alert("Node is an element.");
    }
    
  • 为了确保跨浏览器兼容,还是将nodeType与数值比较

    if(some.nodeType == 1){ 
        alert("Node is an element.");
    }
    
    1. nodeName 和 nodeValue属性

      • 对于元素节点,nodeName 中保存的始终都是元素的标签名,而nodeValue 的值则始终为null。
    2. 节点关系

      • 每个节点都有一个childNodes属性,其中保存着一个NodeList对象。
      • 访问NodeList中的节点——可以通过方括号,也可以使用item()方法

        var firstChild = someNode.childNodes[0];
        var senondChild = someNode.childNodes.item(1);
        var count = someNode.childNodes.length;
        
      • 每个节点都有一个parentNode属性,该属性指向文档树中的父节点。

      • 通过使用列表中每个节点的previousSibling属性和nextSibling属性,可以访问同一列表中的其他节点。
    3. 操作节点

      • appendChild(),向childNodes列表的末尾添加一个节点

        var returnedNode = someNode.appendChild(newNode);
        alert(returnedNode == newNode);        //true
        alert(someNode.lastChild == newNode);  //true
        
      • insertBefore()这个方法接受两个参数:要插入的节点和作为参照的节点。如果参照节点是null,则insertBefore()与appendChild()执行相同的操作

        var returnedNode = someNode.insertBefore(newNode,null);
        alert(newNode == someNode.lastChild);   //true
        
      • replaceChild()方法接受两个参数:要插入的节点和要替换的节点

        //替换第一个子节点
        var returnedNode = someNode.replaceChild(newNode,someNode.firstChild);
        
      • removeChild()方法只是移除节点,接受一个参数,即要移除的节点

        //移除第一个子节点
        var formerFirstChild = someNode.removeChild(someNode.firstChild);
        
    4. 其他方法

      • cloneNode()用于创建调用这个方法的节点的一个完全相同的副本。该方法接受一个布尔值参数,表示是否执行深复制。深复制:复制节点及其整个子节点树。复制后返回的节点副本属于文档所有,但并没有为他指定父节点,因此,整个节点副本就成了一个“孤儿”。
      • normalize()处理文档树中的文本节点。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。

10.1.2 Document 类型

1. 文档的子节点
2. 文档信息

    - document.title 包含着<title>元素中的文本
    - document.URL 包含页面完整的URL(即地址栏中显示的URL)
    - document.domain 包含页面的域名
    - document.referrer 保存着连接到当前页面的那个页面的URL

3. 查找元素

    - document.getElementById()为了避免兼容性问题,最好的办法是不让表单字段的name特性与其他元素的ID相同
    - document.getElementsByTagName()
    - namedItem()这个方法可以通过元素的name特性取得集合中的项
    - document.getElementsByName()返回带有给定name特性的所有元素,经常用来取得单选按钮

4. 特殊集合
5. DOM 一致性检测
6. 文档写入

    - write() 原样写入
    - writeln() 在字符串的末尾添加一个换行符(\n)
    - open()和close()分别用于打开和关闭网页的输出流

10.1.3 Element 类型

  • 标签名在不同的文档模式中,区分大小写,所以,最好在比较之前将标签名转换为相同的大小写形式

    if(element.tagName.toLowerCase() == "div"){
        //在此执行某些操作
    }
    
    1. HTML 元素
    2. 取得特性 getAttribute() 自定义特性应该加上data-前缀以便验证

      • 只有公认的(非自定义的)特性才会以属性的形式添加到DOM对象中(ie除外)
      • 由于一些差别,在通过JavaScript以编程方式操作DOM时,开发人员不经常使用getAttribute(),而是只使用对象的属性。只有在取得自定义特性值得情况下,才会使用getAttribute()方法。、
    3. 设置特性 setAttribute(),这个方法接受两个参数:要设置的特性名和值。

      • 通过这个方法设置的特性名会被统一转换为小写形式,即“ID”最终会变成“id”
      • 为DOM元素添加一个自定义的属性,该属性不会自动成为元素的特性

        div.mycolor = "red";
        alert(div.getAttribute("mycolor"));    //null(IE除外)
        
      • removeAttribute()用于彻底删除元素的特性,这个方法不仅会清除特性的值,而且也会从元素中完全删除特性。(ie6不支持)

    4. attributes 属性

    5. 创建元素

      • 使用 document.createElement()方法可以创建新元素。这个方法接受一个参数,原色的标签名。
      • 把新元素添加到文档树,可以使用appendChild(),insertBefore(),replaceChild()
      • 兼容低版本ie的写法

        if(client.browser.ie && client.browser.ie <=7){
        var iframe = document.createElement("<iframe name='myframe'></frame>");
        var input = document.createElement("<input type='checkbox'>");
        var button = document.createElement("<button type='reset'></button>");
        var radio1 = document.createElement("<input type='radio' name='choice' value='1'>");
        var radio2 = document.createElement("<input type='radio' name='choice' value='2'>");
        }    
        
    6. 元素的子节点

      • 不同浏览器对子节点的计算不一样,基于这个差别,在执行某项操作前,通常都要先检查一下nodeType属性

        for(var i=0,len=element.childNodes.length;i<len;i++){
            if(element.childNodes[i].nodeType == 1){
                //执行某些操作
            }
        }
        

10.1.4 Text 类型

  1. 创建文本节点

    - document.createTextNode()创建文本节点,该方法接受一个参数——要插入节点中的文本
    
        var element = document.createElement("div");
        element.className = "message";
    
        var textNode = document.createTextNode("Hello world!");
        element.appendChild(textNode);
    
        document.body.appendChild(element);
    
  2. 规范化文本节点

    - normalize()方法,能够将相邻文本节点合并的方法
    
        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!"
    
  3. 分割文本节点

    • Text()方法将一个文本节点分成两个文本节点,即按照指定的位置分割nodeValue值。

      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
      

10.1.5 Comment 类型

  • 注释在DOM中是通过Comment类型来表示的

10.1.6 CDATASection 类型

  • CDATASection类型只针对基于XML的文档,表示的是CDATA区域。

10.1.7 DocumentType 类型

  • DocumentType包含着与文档的doctype有关的所有信息。

10.1.8 DocumentFragment 类型

  • DOM规定文档片段(document fragment)是一种“轻量级”的文档。可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点

    var fragment = document.createDocumentFragment(); 
    var ul = document.getElementById("myList");
    var li = null;
    
    for(var i=0; i<3; i++){
        li = document.createElement("li");
        li.appendChild(document.createTextNode("item" + (i+1)));
        fragment.appendChild(li);
    }
    
    ul.appendChild(fragment);
    

10.1.9 Attr 类型

  • 元素的特性在DOM中以Attr类型来表示。

10.2 DOM操作技术

10.2.1 动态脚本

  • 动态加载外部JavaScript文件

    function loadScript(url){
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = url;
        document.body.appendChild(script);
    } 
    
    loadScript("client.js");
    
  • 行内方式,兼容各个浏览器的写法如下:

    function loadScriptString(code){
        var script = document.createElement("script");
        script.type = "text/javascript";
        try{
            script.appendChild(document.createTextNode(code));
        }catch(ex){
            script.text = code;
        }
        document.body.appendChild(script);
    }
    
    loadScriptString("function sayHi(){alert('hello'); } sayHi();");
    

10.2.2 动态样式

  • 动态加载外部样式

    function loadStyles(url){
        var link = document.createElement("link");
        link.rel = "stylesheet";
        link.type = "text/css";
        link.href = url;
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(link);
    }
    
    loadStyles("styles.css");
    
  • 行内方式

    function loadStylesString(css){
        var style = document.createElement("style");
        style.type = "text/css";
        try{
            style.appendChild(document.createTextNode(css));
        }catch(ex){
            style.stylesheet.cssText = css; //ie中小心使用stylesheet.cssText
        }
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(style);
    }
    
    loadStylesString("body{background-color:red}");
    

10.2.3 操作表格

  • 为table元素添加的属性和方法如下:

    - caption: 保存着对<caption>元素(如果有)的指针。
    - tBodies: 是一个<tbody>元素的HTMLCollection。
    - tFoot: 保存着对<tfoot>元素(如果有)的指针。
    - tHead: 保存着对<thead>元素(如果有)的指针。
    - rows: 是一个表格中所有行的元素的HTMLCollection。
    - createTHead(): 创建<thead>元素,将其放到表格中,返回引用。
    - createTFoot(): 创建<tfoot>元素,将其放到表格中,返回引用。
    - createCaption(): 创建<caption>元素,将其放到表格中,返回引用。
    - deleteTHead(): 删除<thead>元素。
    - deleteTFoot(): 删除<tfoot>元素。
    - deleteCaption(): 删除<caption>元素。
    - deleteRow(pos): 删除指定位置的行。
    - insertRow(pos): 向rows集合中的指定位置插入一行。
    
  • 为tbody元素添加的属性和方法如下:

    - rows: 保存着<tbody>元素中行的HTMLCollection。
    - deletesRow(pos): 删除指定位置的行。
    - insertRow(pos): 向rows集合中的指定位置插入一行,返回对新插入行的引用。
    
  • 为tr元素添加的属性和方法如下:

    - cells: 保存着<tr> 元素中单元格的HTMLCollection。
    - deleteCell(pos): 删除指定为的单元格。
    - insertCell(pos): 向cells集合中的指定位置插入一个单元格,返回对新插入单元格的引用。
    
  • 用这些属性和方法可以将代码简化

    var table = document.createElement("table");
    table.border = 1;
    table.width = "100%";
    
    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,1"));
    tbody.rows[1].insertCell(1);
    tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,1"));
    
    document.body.appendChild(table);
    

10.2.4 使用NodeList

  • 如果想要迭代一个NodeList,最好是使用length属性初始化第二个变量,然后将迭代器与该变量进行比较

    var divs = document.getElementByTagName("div"),
        i,
        len,
        div;
    
    for(i=0, len=divs.length; i<len; i++){
        div = document.createElement("div");
        document.body.appendChild(div);
    }
    
  • 初始化第二个变量len。由于len保存着对divs.length在循环开始时的一个快照,因此避免了divs.length不停地变化的问题。

第11章 DOM扩展

11.1 选择符API

11.1.1 querySelector()方法

11.1.2 querySelectorAll()方法

11.1.3 matchesSelector()方法

11.2 元素遍历

11.3 HTML5

11.3.1 与类相关的扩充

11.3.2 焦点管理

11.3.3 HTMLDocument的变化

11.3.4 字符集属性

11.3.5 自定义数据属性

11.3.6 插入标记

11.3.7 scrollIntoView()方法

11.4 专有扩展

11.4.1 文档模式

11.4.2 children属性

11.4.3 contains()方法

11.4.4 插入文本

11.4.5 滚动

第12章 DOM2和DOM3

12.1 DOM变化

12.1.1 针对XML命名空间的变化

12.1.2 其他方面的变化

12.2 样式

12.2.1 访问元素的样式

12.2.2 操作样式表

12.2.3 元素大小

12.3 遍历

12.3.1 NodeIterator

12.3.2 TreeWalker

12.4 范围

12.4.1 DOM中的范围

12.4.2 IE8及更早版本中的范围

第13章 事件

13.1 事件流

  • 事件流描述的是从页面中接收时间的顺序

13.1.1 事件冒泡

  • IE的事件流叫做事件冒泡
  • click事件在div上发生,然后click事件沿DOM树向上传播,直至传播到document对象

13.1.2 事件捕获

  • 事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
  • 事件捕获过程中document对象先接收到click事件,然后事件沿DOM树依次向下,一直传播到实际目标,即div元素。
  • 由于老版本浏览器不支持,因此不建议使用事件捕获

13.1.3 DOM事件流

  • 事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段
  • 在捕获阶段:事件从document到html再到body就停止了;
  • 在目标阶段:事件在div上发生,并在事件处理中被看成冒泡阶段的一部分
  • 冒泡阶段发生:事件又传播回文档

13.2 事件处理程序

13.2.1 HTML事件处理程序

  • 缺点:HTML与JavaScript代码紧密耦合
  • 应用JavaScript指定时间处理程序代替

13.2.2 DOM0级事件处理程序

  • 使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,事件处理程序是在元素的作用域中运行的

    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
        alert(this.id);    //"myBtn"
    }
    
  • 删除事件处理程序

    btn.onclick = null;
    

13.2.3 DOM2级事件处理程序

  • addEventListener(要处理的事件名,作为事件处理程序的函数,一个布尔值): 指定事件处理程序
  • removeEventListener(要处理的事件名,作为事件处理程序的函数,一个布尔值): 删除事件处理程序
  • 最后布尔值参数如果是true:表示在捕获阶段调用事件处理程序;false:表示 在冒泡阶段调用事件处理程序
  • 使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序

    var btn = document.getElementById("myBtn");
    btn.addEventListener("click",function(){
        alert(this.id);
    },false);
    btn.addEventListener("click",function(){
        alert("hello");
    },false);
    
  • 通过addEventListener()添加的匿名函数无法移除,必须起个名字

    var btn = document.getElementById("myBtn");
    var handler = function(){
        alert(this.id);
    };
    btn.addEventListener("click",handler,false);
    
    btn.removeEventListener("click",handler,false);  //有效
    
  • 大多数情况下,事件处理程序都是添加到冒泡阶段,可以最大限度的兼容各种浏览器

13.2.4 IE事件处理程序

  • attachEvent(事件处理程序名称,事件处理程序函数)
  • detachEvent(事件处理程序名称,事件处理程序函数)
  • 由于ie8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段

    var btn = document.getElementById("myBtn");
    btn.attachEvent("onclick",function(){
        alert("Clicked");
    });
    
  • 在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。

    var btn = document.getElementById("myBtn");
    btn.attachEvent("onclick",function(){
        alert(this === window);   //true
    });
    
  • 与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。

    var btn = document.getElementById("myBtn");
    btn.attachEvent("onclick",function(){
        alert(this === window);   //true
    });
    btn.attachEvent("onclick",function(){
        alert("hello");   
    });
    
  • 与DOM方法一样,添加的匿名函数将不能被移除

    var btn = document.getElementById("myBtn");
    var handler = function(){
        alert(this === window);   //true
    };
    btn.attachEvent("onclick",handler);
    
    btn.detachEvent("onclick",handler);
    

13.2.5 跨浏览器的事件处理程序

    function addHandler(a, c, b) {
    if (a == null) return;
    if (a.addEventListener) {
        addHandler = function(d, f, e) {
            if (d == null) return;
            d.addEventListener(f, e, false)
        }
    } else {
        if (a.attachEvent) {
            addHandler = function(d, f, e) {
                if (d == null) return;
                d.attachEvent("on" + f, e)
            }
        } else {
            addHandler = function(d, f, e) {
                if (d == null) return;
                d["on" + f] = e
            }
        }
    }
    addHandler(a, c, b)
}

function removeHandler(a, c, b) {
    if (a == null) return;
    if (a.removeEventListener) {
        removeHandler = function(d, f, e) {
            if (d == null) return;
            d.removeEventListener(f, e, false)
        }
    } else {
        if (a.detachEvent) {
            addHandler = function(d, f, e) {
                if (d == null) return;
                d.detachEvent("on" + f, e)
            }
        } else {
            addHandler = function(d, f, e) {
                if (d == null) return;
                d["on" + f] = e
            }
        }
    }
    removeHandler(a, c, b)
}

 addHandler(btn, 'mouseover', handler);    
 removeHandler(btn, 'mouseover', handler);    

13.3 事件对象

13.3.1 DOM中的事件对象

  • 无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象
  • 在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标
  • 如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值

    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);
        alert(event.target === document.getElementById("myBtn"));           //true
    };    
    
  • 在需要通过一个函数处理多个事件时,可以使用type属性

    var btn = document.getElementById("myBtn");
    var handler = function(event){
        switch(event.type){
            case "click":
                alert("Clicked");
                break;
    
            case "mouseover":
                event.target.style.backgroundColor = "red";
                break;
    
            case "mouseout":
                event.target.style.backgroundColor = "";
                break;
            }
    }
    
    btn.onclick = handler;
    btn.onmouseover = handler;
    btn.onmouseout = handler;
    
  • 阻止特定事件的默认行为,可以使用preventDefault()方法

  • 立即停止事件在DOM层次中的传播,可以使用 stopPropagation()方法,即取消进一步的事件捕获或冒泡

    var btn = document.getElementById("myBtn");
    btn.onclick = function(event){
        alert("Clicked");
        event.stopPropagation();
       }
    
       document.body.onclick = function(event){
        alert("Body clicked");//不会执行,因为已被event.stopPropagation();阻止
    }
    
  • 事件对象的 eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段

  • 捕获阶段:eventPhase=1
  • 事件处理程序处于目标对象上:eventPhase=2
  • 冒泡阶段调用的事件处理程序:eventPhase=3
  • 注意:尽管“处于目标”发生在冒泡阶段,但eventPhase一直等于2

    var btn = document.getElementById("myBtn");
    btn.onclick = function(event){
        alert(event.eventPhase);//2
    };
    
    document.body.addEventListener("click",function(event){
        alert(event.eventPhase);//1
    },true);//为true时是捕获阶段
    
    document.body.onclick = function(event){
        alert(event.eventPhase);//3
    };
    

13.3.2 IE中的事件对象

  • IE中的event对象同样也包含与创建它的事件相关的属性和方法
  • 因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为this会始终等于事件目标,所以最好还是使用event.srcElement比较保险

    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
        alert(window.event.srcElement === this);  //true
    }
    
    btn.attachEvent("onclick", function(event){
        alert(event.srcElement === this);  //false
    })
    
  • returnValue 属性相当于DOM中的preventDefault()方法,他们的作用都是取消给定事件的默认行为,与DOM不同的是,在此没有办法确定时间是否能被取消

    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
        window.event.returnValue = false;
    }
    
  • cancelBubble 属性与DOM中的 stopPropagation()方法作用相同,都是用来停止事件冒泡的。由于IE不支持事件捕获,因而只能取消事件冒泡

    var btn = document.getElementById("myBtn");
    btn.onclick = function(event){
        alert("Clicked");
        window.event.cancelBubble = true;
       }
    
       document.body.onclick = function(event){
        alert("Body clicked");//不会执行,因为已被window.event.cancelBubble = true;阻止
    }
    

13.3.3 跨浏览器的事件对象

var EventUtil = {
    addHandler: function(element,type,handler){
        //省略
    },
    getEvent: function(event){
        return event ? event : window.event;
    },
    getTarget: function(event){
        return event.target || event.srcElement;
    },
    preventDefault: function(event){
        if(event.preventDefault){
            event.preventDefault();
        } else{
            event.returnValue = false;
        }
    },
    removeHandler: function(element,type,handler){
        //省略
    },
    stopPropagation: function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        } else{
            event.cancelBubble = true;
        }
    }
};

btn.onclick = function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    EventUtil.preventDefault(event);
    EventUtil.stopPropagation(event);
};

13.4 事件类型

13.4.1 UI事件

  1. load事件

    EventUtil.addHandler(window, "load", function() {
        var image = document.createElement('img');
        EventUtil.addHandler(image, "load", function() {
            event = EventUtil.getEvent(event);
            alert(EventUtil.getTarget(event).src);
           });
        document.body.appendChild(image);
        image.src = "http://img1.xcarimg.com/news/14201/14934/608_20150602085635274371.jpg"
    })
    
  2. unload事件

    • 这个事件是在文档被完全卸载后触发,只要用户从一个页面切换到另一个页面,就会发生unload事件

      EventUtil.addHandler(window, "unload", function() {
          alert("Unloaded");
        })        
      
  3. resize

    • 当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。

      EventUtil.addHandler(window, "resize", function() {
          alert("Resized");
        })    
      
  4. scroll

    • 表示页面中相应元素的变化

      EventUtil.addHandler(window, "scroll", function() {
          if(document.compatMode == "CSS1Compat"){
              alert(document.documentElement.scrollTop);
          }else{
              alert(document.body.scrollTop);
          }
        });
      
    • document.compatMode 表示文档渲染方式,它有两种可能的返回值:BackCompat 标准兼容模式关闭 和CSS1Compat 标准兼容模式开启。

13.4.2 焦点事件

  • 焦点事件会在页面获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。

    1. blur:在元素失去焦点时触发
    2. focus:在元素获得焦点时触发

13.4.3 鼠标与滚轮事件

  1. click:在用户单击主鼠标按钮或者按下回车键时触发。
  2. dbclick:在用户双击鼠标按钮时触发。
  3. mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事。
  4. mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。
  5. mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。
  6. mousemove:当鼠标指针在元素内部移动时重复的触发。不能通过键盘触发这个事件
  7. mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。
  8. mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。
  9. mouseup:在用户释放鼠标按钮时触发。

    • 除了mouseenter和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。
    • 鼠标事件中还有一类滚轮事件。其实就是mousewheel事件。这个时间跟踪鼠标滚轮,类似于Mac的触控板。

      1. 客户区坐标位置

        • clientX和clientY表示事件发生时鼠标指针在视口中的水平和垂直坐标

          var btn = document.getElementById("myBtn");                
          EventUtil.addHandler(btn, "click", function(event) {
              event = EventUtil.getEvent(event);
              alert("坐标是:" + event.clientX + "," + event.clientY);
             })
          
      2. 页面坐标位置

        • 页面坐标通过事件对象的pageX和pageY

          var btn = document.getElementById("myBtn");                
          EventUtil.addHandler(btn, "click", function(event) {
              event = EventUtil.getEvent(event);
              alert("坐标是:" + event.pageX + "," + event.pageY);
             })
          
        • 在页面没有滚动的情况下,pageX和pageY的值与clientX和clientY的值相等

        • ie8及早期版本不支持事件对象上的页面坐标,兼容性写法如下

          EventUtil.addHandler(btn, "click", function(event) {
              event = EventUtil.getEvent(event);
              var pageX = event.pageX,
                  pageY = event.pageY;
          
                 if(pageX === undefined){
                  pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
                 }
          
              if(pageY === undefined){
                  pageY = event.clientY + (document.body.scrollTop|| document.documentElement.scrollTop);
              }
          
                  console.log("Page coordinates:"+ pageX +","+pageY);
          })
          
      3. 屏幕坐标位置

        • 通过 screenX 和 screenY 属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。

          var btn = document.getElementById("myBtn");                
          EventUtil.addHandler(btn, "click", function(event) {
              event = EventUtil.getEvent(event);
              alert("屏幕坐标是:" + event.screenX + "," + event.screenY);
             })
          
      4. 修改键

        • 虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键是Shift,Ctrl,Alt和Meta,他们经常被用来修改鼠标事件的行为。
        • DOM规定了四个属性,表示这些修改键的状态:shiftKey,ctrlKey,altKey和metaKey
        • 这些属性中包含的都是布尔值,如果相应地键被按下了,值为true,否则值为false

          EventUtil.addHandler(btn, "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");
              }
              console.log("Keys:"+keys.join("."));
          })
          
      5. 相关元素

        • 对于 mouseover 事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素
        • 对于 mouseout 事件而言,事件的主目标是失去光标的元素,而相关元素是获得光标的元素
        • 兼容性方法取得相关元素:

          var EventUtil = {
              //省略了其他代码
              getRelatedTarget:function(event){
                  if(event.relatedTarget){
                      return event.relatedTarget;
                  }else if(event.toElement){
                      return event.toElement;
                  }else if(event.fromElement){
                      return event.fromElement;
                  }else{
                      return null;
                  }
              }
              //省略了其他代码
          }
          
          EventUtil.addHandler(btn, "click", function(event) {
                 event = EventUtil.getEvent(event);
              var target = EventUtil.getTarget(event);
              var relatedTarget = EventUtil.getRelatedTarget(event);
              console.log("Mouse out of" + target.tagName + "to"+ relatedTarget.tagName);
          )}
          
      6. 鼠标按钮

        var EventUtil = {
            //省略了其他代码
        getButton: function(event){
            if(document.implementation.hasFeature("MouseEvents","2.0")){
                return event.button;
            } else{
            switch(event.button){
                case 0:
                case 1:
                case 3:
                case 5:
                case 7:
                    return 0;
                case 2:
                case 6:
                    return 2;
                case 4:
                    return 1;
                }
            }
        }
        //省略了其他代码
        }
        
        EventUtil.addHandler(btn, "mousedown", function(event) {
               event = EventUtil.getEvent(event);
            console.log(EventUtil.getButton(event));
        )}
        
      7. 更多的事件信息

      8. 鼠标滚轮事件

        • 跨浏览器的解决方案,创建一个能够取得鼠标滚轮增量值得方法

          var EventUtil = {
              //省略了其他代码
              getWheelDelta: function(event){
                  if(event.wheelDelta){
                      return (client.engine.opera&&client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
                  }else{
                      return -event.detail * 40;
                  }
              }
          }
          
          function handleMouseWheel(event){
              event = EventUtil.getEvent(event);
              var delta = EventUtil.getWheelDelta(event);
              console.log("delta:"+delta);
          }
          
          EventUtil.addHandler(document,"mousewheel", handleMouseWheel);
          EventUtil.addHandler(document,"DOMMouseScroll", handleMouseWheel);
          
      9. 触摸设备

      10. 无障碍性问题

13.4.4 键盘与文本事件

  • keydown:当用户按下键盘上的任意键触发。
  • keypress:当用户按下键盘上的字符键触发。
  • KeyPress主要用来捕获字符键包括:
    1. 数字(注意:包括Shift+数字的符号)、
    2. 字母(注意:包括大小写)、
    3. 小键盘等除了F1-12、SHIFT、Alt、Ctrl、Insert、Home、PgUp、Delete、End、PgDn、ScrollLock、Pause、NumLock、{菜单键}、{开始键}和方向键外的ANSI字符
  • keyup:当用户释放键盘上的键时触发

13.4.5 复合事件

13.4.6 变动事件

13.4.7 HTML5事件

13.4.8 设备事件

13.4.9 触摸与手势事件

13.5 内存和性能

13.5.1 事件委托

  • 对“事件处理程序过多”问题的解决方案就是事件委托
  • 我们可以为整个页面指定一个onclick事件处理程序,而不必给每一个可单击的元素分别添加事件处理程序

    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
       </ul>
    
  • 按照传统的做法,需要为他们添加3个事件处理程序

    var item1 = document.getElementById("goSomewhere");
       var item2 = document.getElementById("doSomething");
    var item3 = document.getElementById("sayHi");
    
    EventUtil.addHandler(item1,"click",function(event){
        location.href = "http://www.xcar.com.cn";
    })
    
    EventUtil.addHandler(item2,"click",function(event){
        document.title = "I changed the document's title";
    })
    
    EventUtil.addHandler(item3,"click",function(event){
        console.log("hi~");
    })
    
  • 使用事件委托,只需要在DOM树中尽量最高的层次上添加一个事件处理程序

    var list = document.getElementById("myLinks");
    
    EventUtil.addHandler(list,"click",function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    switch(target.id){
        case "doSomething":
            document.title = "I changed the document's title";
            break;
    
        case "goSomewhere":
            location.href = "http://www.xcar.com.cn";
            break;
    
        case "sayHi":
            console.log("hi~");
            break;
    }
    })
    
  • 这里,只委托了ul元素添加了一个onclick事件处理程序,由于所有列表项都是这个元素的子节点,而且他们的事件会冒泡,所以单击事件最终会被这个函数处理

13.5.2 移除事件处理程序

btn.onclick = function(){
    btn.onclick = null;
    //其他操作
}

13.6 模拟事件

13.6.1 DOM中的事件模拟

13.6.2 IE中的事件模拟

文章目录
  1. 1. 第二章 在HTMl中使用JavaScript
    1. 1.1. 2.1 script元素
    2. 1.2. 2.2 嵌入代码与外部文件
    3. 1.3. 2.3 文档模式
    4. 1.4. 2.4 noscript元素
  2. 2. 第三章 基本概念
    1. 2.1. 3.1 语法
    2. 2.2. 3.2 关键字和保留字
    3. 2.3. 3.3 变量
    4. 2.4. 3.4 数据类型
    5. 2.5. 3.5 操作符
    6. 2.6. 3.6 语句
    7. 2.7. 3.7 函数
  3. 3. 第四章 变量、作用域和内存问题
    1. 3.1. 4.1 基本类型和引用类型的值
    2. 3.2. 4.2 执行环境及作用域
    3. 3.3. 4.3 垃圾收集
  4. 4. 第五章 引用类型
    1. 4.1. 5.1 Object类型
    2. 4.2. 5.2 Array类型
    3. 4.3. 5.3 Date类型
    4. 4.4. 5.4 RegExp类型
    5. 4.5. 5.5 Function类型
    6. 4.6. 5.6 基本包装类型
    7. 4.7. 5.7 单体内置对象
  5. 5. 第六章 面向对象的程序设计
    1. 5.1. 6.1 理解对象
    2. 5.2. 6.2 创建对象
    3. 5.3. 6.3 继承
  6. 6. 第七章 函数表达式
    1. 6.1. 7.1 递归
    2. 6.2. 7.2 闭包
    3. 6.3. 7.3 模仿块级作用域
    4. 6.4. 7.4 私有变量
  7. 7. 第8章 BOM
    1. 7.1. 8.1 window对象
    2. 7.2. 8.2 location对象
    3. 7.3. 8.3 navigator对象
    4. 7.4. 8.4 screen对象
    5. 7.5. 8.5 history对象
  8. 8. 第九章 客户端检测
    1. 8.1. 9.1 能力检测
    2. 8.2. 9.2 怪癖检测
    3. 8.3. 9.3 用户代理检测
  9. 9. 第10章 DOM
    1. 9.1. 10.1 节点层次
    2. 9.2. 10.2 DOM操作技术
  10. 10. 第11章 DOM扩展
    1. 10.1. 11.1 选择符API
    2. 10.2. 11.2 元素遍历
    3. 10.3. 11.3 HTML5
    4. 10.4. 11.4 专有扩展
  11. 11. 第12章 DOM2和DOM3
    1. 11.1. 12.1 DOM变化
    2. 11.2. 12.2 样式
    3. 11.3. 12.3 遍历
    4. 11.4. 12.4 范围
  12. 12. 第13章 事件
    1. 12.1. 13.1 事件流
    2. 12.2. 13.2 事件处理程序
    3. 12.3. 13.3 事件对象
    4. 12.4. 13.4 事件类型
    5. 12.5. 13.5 内存和性能
    6. 12.6. 13.6 模拟事件