读书笔记(javascript 高级程序设计)

一. 数据类型:

1. undefined: 未声明和未初始化的变量,typeof 操作符返回的结果都是 undefined;(建议未初始化的变量进行显式赋值,这样当 typeof 返回 undefined 时就知道是未声明了,帮助定位问题)

2. null:建议,将即将保存但还未真正保存对象的变量,赋值为 null;

3. number: 保存浮点数需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机的将浮点数值转换为整数值;

4. NaN: isNaN() 用于确定其参数是否“不是数值”;eg: isNaN("blue") —— true (因为不是数值);

附: typeof 可以判断的类型:undefined/boolean/string/number/function;

5. parseInt(): 第二个参数可以指定进制,使参数按照指定进制转换,如不指定进制,则按照十进制转换;

 parseFloat() 只解析十进制值,他没有第二个参数指定基数的用法;

6. 转换为字符串: toString() 和 String();(null 和 undefined 没有 toString() 方法)

String() 可将任何类型的值转换为字符串; 转换规则如下:

如果值有 toString() 方法,则调用该方法(没有参数)并返回相应的结果 —— 如果值是 null,则返回 “null” —— 如果值是“undefined”, 则返回“undefined”;

7. 前 ++ 和后 ++:

前++和前--:eg:var age = 28; --age;//28(--age相当于 age = age - 1;age与(--age)整体的值也会变)

后++和后--:eg:

var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2; //22 (age的值减1,(age--)整体的值不会变)
var num4 = num1 + num2; //21

8. 位运算符: 可以提高性能(详见收藏);

9. == 和 !=: 比较 null 与 undefined 时,不能将它俩转换为任何值;

10. switch语句:switch语句在比较值时使用的是全等操作符,因此不会发生类型转换(如,“10” 不等于 10);

 二. 变量、作用域和内存问题:

1. 函数传参:

参数只能按值传递:当参数为基本数据类型时,直接进行复制操作;当参数为引用数据类型时,将引用地址进行复制给形参,如果在函数内部重新对引用类型的参数进行赋值,此时修改的引用变量参数为局部变量,这个局部的引用对象会在函数执行完毕后立即被销毁;(传参是复制,不能理解为是实参替换形参);函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同(局部变量,作用域链)。

基本类型值得传递如同基本类型变量得复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ES的概念来说,就是 arguments 的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的内部。

 2. 执行环境及作用域:

延长作用域链:虽然执行环境的类型总共只有两种——全局和局部(函数),但还是有其他办法来延长作用域链。这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:try-catch 语句的 catch 块;with 语句。

这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来说,会将指定的对象添加到作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

3. 垃圾回收机制:

  • 标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
  • 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。 (一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用

 三. 引用类型

1. instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
为了解决这个问题, ECMAScript 5 新增了 Array.isArray()方法。

if (Array.isArray(value)){
    //对数组执行某些操作
}

2. 调用数组的 toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。而调用 valueOf()返回的还是数组。

注:如果数组中的某一项的值是 null 或 undefined,那么该值在 join()、toLocalString()、toString() 和 valueOf() 方法返回的结果中以空字符串表示。

3. Date 类型的 valueOf()方法,则根本不返回字符串,而是返回日期的毫秒表示。因此,可以方便使用比较操作符(小于或大于)来比较日期值。

4. 解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

5. ECMAScript 5 也规范化了另一个函数对象的属性: caller这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null

function outer(){
    inner();
}
function inner(){
    alert(inner.caller);
}
outer();

以上代码会导致警告框中显示 outer() 函数的源代码。因为 outer() 调用了 inter(),所以 inner.caller 就指向 outer()。为了实现更松散的耦合,也可以通过 arguments.callee.caller 来访问相同的信息。

6. ECMAScript 5 中, prototype 属性是不可枚举的,因此使用 for-in 无法发现。

7. 在没有给函数明确指定this 值的情况下(无论是通过将函数添加为对象的方法,还是通过调用 call()apply()), this 值等于 Global 对象。而像这样通过简单地返回 this 来取得 Global 对象,在任何执行环境下都是可行的。

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

 四. 面向对象:

1. 要创建一个构造函数的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
(3) 执行构造函数中的代码(为这个新对象添加属性);
(4) 返回新对象。

2. 在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的( enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将[[Enumerable]]标记为 false 的属性)的实例属性也会在 for-in 循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。

3. 组合使用构造函数模式和原型模式:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
    alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性 constructor 和方法 sayName()则是在原型中定义的。而修改了 person1.friends(向其中添加一个新字符串),并不会影响到 person2.friends,因为它们分别引用了不同的数组。这种构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。

4. 动态原型模式:

通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

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);
        };
    }
}

这里只在 sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可
以说非常完美。其中, if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆if 语句检查每个属性和每个方法;只要检查其中一个即可。对于采用这种模式创建的对象,还可以使用 instanceof 操作符确定它的类型。

5. 寄生构造函数模式:

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");
alert(colors.toPipedString()); //"red|blue|green"

关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。

6. 稳妥构造函数模式:一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数。

function Person(name, age, job){
    //创建要返回的对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
        alert(name);
    };
    //返回对象
    return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"

变量 friend 中保存的是一个稳妥对象,而除了调用 sayName()方法外,没有别的方式可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。

与寄生构造函数模式类似,instanceof 操作符对这种对象也没有意义。

7. 在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样做就会重写原型链。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },
    someOtherMethod : function (){
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

 五. 函数表达式:

1. 后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建 compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用 compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。对于这个例子中 compare()函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

2. 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window;

3. 

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

在上面的代码中,通过把 element.id 的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄漏的问题。必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着 element。即使闭包不直接引用 element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把 element 变量设置为 null。这样就能够解除对 DOM 对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。

4. 递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可能会发生变化。

注:这两章精读;

六. BOM:

1. 全局变量不能通过 delete 操作符删除,而直接在 window 对象上的定义的属性可以。 因为使用 var 语句添加的 window 属性有一个名为[[Configurable]]的特性,这个特性的值被设置为false,因此这样定义的属性不可以通过delete 操作符删除。

2. BOM 的核心对象是 window,它表示浏览器的一个实例。在浏览器中, window 对象有双重角色,它既是通过 JavaScript 访问浏览器窗口的一个接口,又是 ECMAScript 规定的 Global 对象。这意味着在网页中定义的任何一个对象、变量和函数,都以 window 作为其 Global 对象,因此有权访问parseInt()等方法。

3. top parent:

(1)top:该变更永远指分割窗口最高层次的浏览器窗口。如果计划从分割窗口的最高层次开始执行命令,就可以用top变量。 top 对象始终指向最高(最外)层的框架,也就是浏览器窗口。使用它可以确保在一个框架中正确地访问另一个框架。因为对于在一个框架中编写的任何代码来说,其中的 window 对象指向的都是那个框架的特定实例,而非最高层的框架。
(2)opener:opener用于在window.open的页面引用执行该window.open方法的的页面的对象。例如:A页面通过window.open()方法弹出了B页面,在B页面中就可以通过opener来引用A页面,这样就可以通过这个对象来对A页面进行操作。 
(3)parent:parent用于在iframe,frame中生成的子页面中访问父页面的对象。例如:A页面中有一个iframe或frame,那么iframe 或frame中的页面就可以通过parent对象来引用A页面中的对象。这样就可以获取或返回值到A页面中。parent(父)对象始终指向当前框架的
直接上层框架。在某些情况下, parent 有可能等于 top;但在没有框架的情况下, parent 一定等于 top(此时它们都等于 window)。

(4)self:指的是当前窗口。它始终指向 window;实际上, self window 对象可以互换使用。引入 self 对象的目的只是为了与 top parent 对象对应起来,因此它不格外包含其他值。

  parent与opener的区别:

  parent指父窗口,在FRAMESET中,FRAME的PARENT就是FRAMESET窗口。 
  opener指用WINDOW.OPEN等方式创建的新窗口对应的原窗口。 
  parent是相对于框架来说父窗口对象
  opener是针对于用window.open打开的窗口来说的父窗口,前提是window.open打开的才有

  document.parentWindow.menthod()調用父頁面的方法

  附:Window对象、Parent对象、Frame对象、Document对象和Form对象的阶层关系:Window对象→Parent对象→Frame对象→Document对象→Form对象,如下: parent.frame1.document.forms[0].elements[0].value;

4. 窗口位置:

IESafariOpera Chrome 都提供了 screenLeft screenTop 属性,分别用于表示窗口相对于屏幕左边和上边的位置。

Firefox 则在screenX screenY 属性中提供相同的窗口位置信息, Safari Chrome 也同时支持这两个属性。

最终结果,就是无法在跨浏览器的条件下取得窗口左边和上边的精确坐标值。使用 moveTo() moveBy()方法倒是有可能将窗口精确地移动到一个新位置。这两个方法都接收两个参数,其中 moveTo()接收的是新位置的 x y 坐标值,而 moveBy()接收的是在水平和垂直方向上移动的像素数。

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

6. 调用 close()方法可以关闭新打开的窗口,但是,这个方法仅适用于通过 window.open()打开的弹出窗口。对于浏览器的主窗口,如果没有得到用户的允许是不能关闭它的。不过,弹出窗口倒是可以调用 top.close()在不经用户允许的情况下关闭自己。 

7. 如果是浏览器扩展或其他程序阻止的弹出窗口,那么 window.open()通常会抛出一个错误。因此,要想准确地检测出弹出窗口是否被屏蔽,必须在检测返回值的同时,将对 window.open()的调用封装在一个 try-catch 块中,如下所示:

var blocked = false;
try {
    var wroxWin = window.open("http://www.wrox.com", "_blank");
    if (wroxWin == null){
        blocked = true;
    }
} catch (ex){
    blocked = true;
}
if (blocked){
    alert("The popup was blocked!");
}

8. setTimeout()的第二参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。

9. window.location document.location 引用的是同一个对象。

10. 检测浏览器中是否安装了特定的插件是一种最常见的检测例程。对于非 IE 浏览器,可以使用 navigator 的 plugins 数组来达到这个目的。该数组中的每一项都包含下列属性:
name:插件的名字。
description:插件的描述。
filename:插件的文件名。
length:插件所处理的 MIME 类型数量。

每个插件对象本身也是一个 MimeType 对象的数组,这些对象可以通过方括号语法来访问。每个 MimeType 对象有4个属性:包含 MimeType 类型描述的 description、回指插件对象的 enabledPlugin、表示与 MIME 类型对应的文件扩展名的字符串 suffixes(以逗号分隔)和表示完整 MIME 类型字符串的 type。

10. 调用 replace() 方法可以导航到一个新 URL,同时该 URL 会替换浏览器历史纪录中当前显示的页面。

11. BOM 中还有两个对象: screen history,但它们的功能有限。 screen 对象中保存着与客户端显示器有关的信息,这些信息一般只用于站点分析。 history 对象为访问浏览器的历史记录开了一个小缝隙,开发人员可以据此判断历史记录的数量,也可以在历史记录中向后或向前导航到任意页面。

七. 客户端检测:

1. 能力检测:

//作者: Peter Michaux
function isHostMethod(object, property) {
    var t = typeof object[property];
    return t=='function' ||
        (!!(t=='object' && object[property])) ||
        t=='unknown';
}

目前使用 isHostMethod()方法还是比较可靠的,因为它考虑到了浏览器的怪异行为。不过也要注意,宿主对象没有义务保持目前的实现方式不变,也不一定会模仿已有宿主对象的行为。所以,这个函数——以及其他类似函数,都不能百分之百地保证永远可靠。作为开发人员,必须对自己要使用某个功能的风险作出理性的估计。

2. 用户代理检测:

以下是完整的用户代理字符串检测脚本,包括检测呈现引擎、平台、 Windows 操作系统、移动设备和游戏系统。

var client = function(){
//呈现引擎
var engine = {
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    //完整的版本号
    ver: null
};
//浏览器
var browser = {
    //主要浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,
    //具体的版本号
    ver: null
};
//平台、设备和操作系统
var system = {
    win: false,
    mac: false,
    x11: false,
    //移动设备
    iphone: false,
    ipod: false,
    ipad: false,
    ios: false,
    android: false,
    nokiaN: false,
    winMobile: false,
    //游戏系统
    wii: false,
    ps: false
};
//检测呈现引擎和浏览器
var ua = navigator.userAgent;
    if (window.opera){
        engine.ver = browser.ver = window.opera.version();
        engine.opera = browser.opera = parseFloat(engine.ver);
    } else if (/AppleWebKit/(S+)/.test(ua)){
        engine.ver = RegExp["$1"];
        engine.webkit = parseFloat(engine.ver);
        //确定是 Chrome 还是 Safari
        if (/Chrome/(S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.chrome = parseFloat(browser.ver);
        } else if (/Version/(S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.safari = parseFloat(browser.ver);
        } else {
        //近似地确定版本号
        var safariVersion = 1;
        if (engine.webkit < 100){
            safariVersion = 1;
        } else if (engine.webkit < 312){
            safariVersion = 1.2;
        } else if (engine.webkit < 412){
            safariVersion = 1.3;
        } else {
            safariVersion = 2;
        }
        browser.safari = browser.ver = safariVersion;
    }
} else if (/KHTML/(S+)/.test(ua) ||   /Konqueror/([^;]+)/.test(ua)){
    engine.ver = browser.ver = RegExp["$1"];
    engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);
    //确定是不是 Firefox
    if (/Firefox/(S+)/.test(ua)){
        browser.ver = RegExp["$1"];
        browser.firefox = parseFloat(browser.ver);
    }
} else if (/MSIE ([^;]+)/.test(ua)){
    engine.ver = browser.ver = RegExp["$1"];
    engine.ie = browser.ie = parseFloat(engine.ver);
}
//检测浏览器
browser.ie = engine.ie;
browser.opera = engine.opera;
//检测平台
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
//检测 Windows 操作系统
if (system.win){
    if (/Win(?:dows )?([^do]{2})s?(d+.d+)?/.test(ua)){
        if (RegExp["$1"] == "NT"){
            switch(RegExp["$2"]){
                case "5.0":
                    system.win = "2000";
                    break;
                case "5.1":
                    system.win = "XP";
                    break;
                case "6.0":
                    system.win = "Vista";
                    break;
                case "6.1":
                    system.win = "7";
                    break;
                default:
                    system.win = "NT";
                    break;
            }
        } else if (RegExp["$1"] == "9x"){
            system.win = "ME";
        } else {
            system.win = RegExp["$1"];
        }
    }
}
//移动设备
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
system.nokiaN = ua.indexOf("NokiaN") > -1;
//windows mobile
if (system.win == "CE"){
    system.winMobile = system.win;
} else if (system.win == "Ph"){
    if(/Windows Phone OS (d+.d+)/.test(ua)){;
        system.win = "Phone";
        system.winMobile = parseFloat(RegExp["$1"]);
    }
}
//检测 iOS 版本
if (system.mac && ua.indexOf("Mobile") > -1){
    if (/CPU (?:iPhone )?OS (d+_d+)/.test(ua)){
        system.ios = parseFloat(RegExp.$1.replace("_", "."));
    } else {
        system.ios = 2; //不能真正检测出来,所以只能猜测
    }
}
//检测 Android 版本
if (/Android (d+.d+)/.test(ua)){
    system.android = parseFloat(RegExp.$1);
}
//游戏系统
system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);
//返回这些对象
return {
    engine: engine,
    browser: browser,
    system: system
};
}();

 注:用户代理检测是客户端检测的最后一个选择。只要可能,都应该优先采用能力检测和怪癖检测。

在决定使用哪种客户端检测方法时,一般应优先考虑使用能力检测。怪癖检测是确定应该如何处理代码的第二选择。而用户代理检测则是客户端检测的最后一种方案,因为这种方法对用户代理字符串具有很强的依赖性。

八. DOM:

1. JavaScript 通过 Document 类型表示文档。在浏览器中, document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个 HTML 页面。而且, document 对象是 window 对象的一个属性,因此可以将其作为全局对象来访问。 Document 节点具有下列特征:
nodeType 的值为 9
nodeName 的值为"#document"
nodeValue 的值为 null
parentNode 的值为 null
ownerDocument 的值为 null
其子节点可能是一个 DocumentType(最多一个)、 Element(最多一个)、 ProcessingInstruction Comment

2. 除了 Document 类型之外, Element 类型就要算是 Web 编程中最常用的类型了。 Element 类型用于表现 XML HTML 元素,提供了对元素标签名、子节点及特性的访问。 Element 节点具有以下特征:
nodeType 的值为 1
nodeName 的值为元素的标签名;
nodeValue 的值为 null
parentNode 可能是 Document Element
其子节点可能是 ElementTextCommentProcessingInstructionCDATASection 或 EntityReference

要访问元素的标签名,可以使用 nodeName 属性,也可以使用 tagName 属性;这两个属性会返回相同的值(使用后者主要是为了清晰起见)。

3. 文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的 HTML 字符,但不能包含 HTML 代码。 Text 节点具有以下特征:
nodeType 的值为 3
nodeName 的值为"#text"
nodeValue 的值为节点所包含的文本;
parentNode 是一个 Element
不支持(没有)子节点。可以通过 nodeValue 属性或 data 属性访问 Text 节点中包含的文本,这两个属性中包含的值相同。对 nodeValue 的修改也会通过 data 反映出来,反之亦然。使用下列方法可以操作节点中的文本。
appendData(text):将 text 添加到节点的末尾。
deleteData(offset, count):从 offset 指定的位置开始删除 count 个字符。
insertData(offset, text):在 offset 指定的位置插入 text
replaceData(offset, count, text):用 text 替换从 offset 指定的位置开始到 offsetcount 为止处的文本。
splitText(offset):从 offset 指定的位置将当前文本节点分成两个文本节点。
substringData(offset, count):提取从 offset 指定的位置开始到 offset+count 为止处的字符串。

除了这些方法之外,文本节点还有一个 length 属性,保存着节点中字符的数目。而且,nodeValue.length data.length 中也保存着同样的值。

4. 理解 DOM 的关键,就是理解 DOM 对性能的影响。 DOM 操作往往是 JavaScript 程序中开销最大的部分,而因访问 NodeList 导致的问题为最多。 NodeList 对象都是“动态的”,这就意味着每次访问NodeList 对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少 DOM 操作。

5. 假设我们想为这个<ul>元素添加 3 个列表项。如果逐个地添加列表项,将会导致浏览器反复渲染(呈现)新信息。为避免这个问题,可以使用一个文档片段来保存创建的列表项,然后再一次性将它们添加到文档中。

6. document.hasFocus()方法 :通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。

var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true

 7. HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以 data-开头即可。

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

添加了自定义属性之后,可以通过元素的 dataset 属性来访问自定义属性的值。 dataset 属性的值是 DOMStringMap 的一个实例,也就是一个名值对儿的映射。在这个映射中,每个 data-name 形式的属性都会有一个对应的属性,只不过属性名没有 data-前缀(比如,自定义属性是 data-myname,那映射中对应的属性就是 myname)。还是看一个例子吧。

//本例中使用的方法仅用于演示
var div = document.getElementById("myDiv");
//取得自定义属性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;
//设置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
//有没有"myname"值呢?
if (div.dataset.myname){
    alert("Hello, " + div.dataset.myname);
}

如果需要给元素添加一些不可见的数据以便进行其他处理,那就要用到自定义数据属性。在跟踪链接或混搭应用中,通过自定义数据属性能方便地知道点击来自页面中的哪个部分。

8. scrollIntoView()scrollIntoViewIfNeeded()的作用对象是元素的容器,而 scrollByLines()scrollByPages()影响的则是元素自身。

//将页面主体滚动 5 行
document.body.scrollByLines(5);

//在当前元素不可见的时候,让它进入浏览器的视口
document.images[0].scrollIntoViewIfNeeded();

//将页面主体往回滚动 1 页
document.body.scrollByPages(-1);

 9. 在不确定某个给定的 CSS 属性拥有什么默认值的情况下,就可以使用这个方法。只要移除相应的属性,就可以为元素应用默认值。

如:
myDiv.style.removeProperty("border");

 10. DOM2 级遍历和范围”模块定义了两个用于辅助完成顺序遍历 DOM 结构的类型: NodeIterator TreeWalker这两个类型能够基于给定的起点对 DOM 结构执行深度优先( depth-first)的遍历操作。 DOM 遍历是深度优先的 DOM 结构遍历,也就是说,移动的方向至少有两个(取决于使用的遍历类型)。遍历以给定节点为根,不可能向上超出 DOM 树的根节点。以下面的 HTML 页面为例。

<!DOCTYPE html>
<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <p><b>Hello</b> world!</p>
    </body>
</html>    

何节点都可以作为遍历的根节点。 如果假设<body>元素为根节点,那么遍历的第一步就是访问<p>元素,然后再访问同为<body>元素后代的两个文本节点。不过,这次遍历永远不会到达<html><head>元素,也不会到达不属于<body>元素子树的任何节点。而以 document 为根节点的遍历则可以访问到文档中的全部节点。

(1)NodeIterator 类型是两者中比较简单的一个,可以使用 document.createNodeIterator() 方法创建它的新实例。 NodeIterator 类型的两个主要方法是 nextNode()previousNode()。顾名思义,在深度优先的 DOM 子树遍历中, nextNode()方法用于向前前进一步,而 previousNode()用于向后后退一步。在刚刚创建的 NodeIterator 对象中,有一个内部指针指向根节点。

var div = document.getElementById("div1");
var filter = function(node){
    return node.tagName.toLowerCase() == "li" ?
        NodeFilter.FILTER_ACCEPT :
        NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
    alert(node.tagName); //输出标签名
    node = iterator.nextNode();
}

(2)TreeWalker NodeIterator 的一个更高级的版本。创建 TreeWalker 对象要使用 document.createTreeWalker()方法。除了包括 nextNode() previousNode() 在内的相同的功能之外,这个类型还提供了下列用于在不同方向上遍历 DOM 结构的方法。
parentNode():遍历到当前节点的父节点;
firstChild():遍历到当前节点的第一个子节点;
lastChild():遍历到当前节点的最后一个子节点;
nextSibling():遍历到当前节点的下一个同辈节点;
previousSibling():遍历到当前节点的上一个同辈节点。

九. 事件:

1. 事件流描述的是从页面中接收事件的顺序:事件捕获和事件冒泡。

2. 在事件处理程序中通过 this 访问元素的任何属性和方法(包括默认属性及自定义属性)。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id); //"myBtn"
    alert(this.className);  // class名
};

 3. 可以删除通过 DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为 null 即可:

btn.onclick = null; //删除事件处理程序

如果你使用 HTML 指定事件处理程序,那么 onclick 属性的值就是一个包含着在同名 HTML 特性中指定的代码的函数。而将相应的属性设置为 null,也可以删除以这种方式指定的事件处理程序。

4. 只有在事件处理程序执行期间, event 对象才会存在;一旦事件处理程序执行完成, event 对象就会被销毁。 (事件处理程序即触发事件时执行的函数方法这一过程)

5. UI 事件:

(1) abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。

(2) unload 事件:这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生 unload 事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。

(3) resize:推荐使用 

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

 关于何时会触发 resize 事件,不同浏览器有不同的机制。 IESafariChrome Opera 会在浏览器窗口变化了 1 像素时就触发 resize 事件,然后随着变化不断重复触发。 Firefox 则只会在用户停止调整窗口大小时才会触发 resize 事件。由于存在这个差别,应该注意不要在这个事件的处理程序中加入大计算量的代码,因为这些代码有可能被频繁执行,从而导致浏览器反应明显变慢。浏览器窗口最小化或最大化时也会触发 resize 事件。

6. 鼠标事件:

触摸设备:

iOS Android 设备的实现非常特别,因为这些设备没有鼠标。在面向 iPhone iPod 中的 Safari 开发时,要记住以下几点。
不支持 dblclick 事件。双击浏览器窗口会放大画面,而且没有办法改变该行为。
轻击可单击元素会触发 mousemove 事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生 mousedownmouseup click 事件。轻击不可单击的元素不会触发任何事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了 onclick 事件处理程序的元素。
mousemove 事件也会触发 mouseover mouseout 事件。
两个手指放在屏幕上且页面随手指移动而滚动时会触发 mousewheel scroll 事件。

7. beforeunload 事件:
这个事件的意图是将控制权交给用户。显示的消息会告知用户页面行将被卸载(正因为如此才会显示这个消息),询问用户是否真的要关闭页面,还是希望继续留下来。

为了显示这个弹出对话框,必须将 event.returnValue 的值设置为要显示给用户的字符串(对IE Fiefox 而言),同时作为函数的值返回(对 Safari Chrome 而言)

EventUtil.addHandler(window, "beforeunload", function(event){
    event = EventUtil.getEvent(event);
    var message = "I'm really going to miss you if you go.";
    event.returnValue = message;
    return message;
});

 8. 触摸事件:touchend 事件发生时, touches 集合中就没有任何 Touch 对象了,因为不存在活动的触摸操作;此时,就必须转而使用 changeTouchs 集合。

在触摸屏幕上的元素时,这些事件(包括鼠标事件)发生的顺序如下:

(1) touchstart
(2) mouseover
(3) mousemove(一次)
(4) mousedown
(5) mouseup
(6) click
(7) touchend


9. 手势事件:

当两个手指触摸屏幕时就会产生手势,手势通常会改变显示项的大小,或者旋转显示项。有三个手势事件:
gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
gestureend:当任何一个手指从屏幕上面移开时触发。

每个手势事件的 event 对象都包含着标准的鼠标事件属性: bubblescancelableviewclientXclientYscreenXscreenYdetailaltKeyshiftKeyctrlKey metaKey。此外,还包含两个额外的属性: rotation scale。其中, rotation 属性表示手指变化引起的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转(该值从 0 开始)。而 scale 属性表示两个手指间距离的变化情况(例如向内收缩会缩短距离);这个值从 1 开始,并随距离拉大而增长,随距离缩短而减小。

 10. 事件委托:事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。 使用事件委托,只需在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.wrox.com";
            break;
        case "sayHi":
            alert("hi");
            break;
    }
});

 11. 内存与性能

(1)事件委托

(2)移除事件处理程序:每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的连接数量。

<div id="myDiv">
    <input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    //先执行某些操作
    btn.onclick = null; //移除事件处理程序
        document.getElementById("myDiv").innerHTML = "Processing...";
    };
</script>

注意,在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。

采用事件委托也有助于解决这个问题。如果事先知道将来有可能使用 innerHTML 替换掉页面中的某一部分,那么就可以不直接把事件处理程序添加到该部分的元素中。而通过把事件处理程序指定给较高层次的元素,同样能够处理该区域中的事件。

最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。

12. 模拟事件:模拟事件就如同浏览器创建的事件一样.

(1)可以在 document 对象上使用 createEvent()方法创建 event 对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。

(2)创建了 event 对象之后,还需要使用与事件有关的信息对其进行初始化。每种类型的 event 对象都有一个特殊的方法,为它传入适当的数据就可以初始化该 event 对象。不同类型的这个方法的名字也不相同,具体要取决于 createEvent()中使用的参数。

(3)模拟事件的最后一步就是触发事件。这一步需要使用 dispatchEvent()方法,所有支持事件的DOM 节点都支持这个方法。调用 dispatchEvent()方法时,需要传入一个参数,即表示要触发事件的 event 对象。

// 模拟鼠标事件
var
btn = document.getElementById("myBtn"); //创建事件对象 var event = document.createEvent("MouseEvents"); //初始化事件对象 event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //触发事件 btn.dispatchEvent(event);

键盘事件:KeyboardEvent

var textbox = document.getElementById("myTextbox"),
event;
//以 DOM3 级方式创建事件对象
if (document.implementation.hasFeature("KeyboardEvents", "3.0")){
    event = document.createEvent("KeyboardEvent");
    //初始化事件对象
    event.initKeyboardEvent("keydown", true, true, document.defaultView, "a",0, "Shift", 0);
}
//触发事件
textbox.dispatchEvent(event);

 十. 表单:

1. 只要表单中存在 type 为 submit 或 image的 button 或 input 任何一种按钮,那么在相应表单控件拥有焦点的情况下,按回车键就可以提交该表单。( textarea 是一个例外,在文本区中回车会换行)如果表单里没有提交按钮,按回车键不会提交表单。

阻止这个事件的默认行为就可以取消表单提交。

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "submit", function(event){
    //取得事件对象
    event = EventUtil.getEvent(event);
    //阻止默认事件
    EventUtil.preventDefault(event);
});

在以下面调用 submit()方法的形式提交表单时,不会触发 submit 事件,因此要记得在调用此方法之前先验证表单数据。

var form = document.getElementById("myForm");
//提交表单
form.submit();

 2. 每个表单字段都有两个方法: focus()blur() 。

3.  除了支持鼠标、键盘、更改和 HTML 事件之外,所有表单字段都支持下列 3 个事件。
blur:当前字段失去焦点时触发。
change:对于<input><textarea>元素,在它们失去焦点且 value 值改变时触发;对于<select>元素,在其选项改变时触发。
focus:当前字段获得焦点时触发。

当用户改变了当前字段的焦点,或者我们调用了 blur()focus()方法时,都可以触发 blur 和 focus 事件。

4. 选择文本:文本框都支持 select()方法,这个方法用于选择文本框中的所有文本。 —— select() 方法对应的,是一个 select 事件。在选择了文本框中的文本时,就会触发 select 事件。

5. 操作剪切板:

下列就是 6 个剪贴板事件。
beforecopy:在发生复制操作前触发。
copy:在发生复制操作时触发。
beforecut:在发生剪切操作前触发。
cut:在发生剪切操作时触发。
beforepaste:在发生粘贴操作前触发。
paste:在发生粘贴操作时触发
在实际的事件发生之前,通过 beforecopybeforecut beforepaste 事件可以在向剪贴板发送数据,或者从剪贴板取得数据之前修改数据。不过,取消这些事件并不会取消对剪贴板的操作——只有取消 copycut paste 事件,才能阻止相应操作发生。

要访问剪贴板中的数据,可以使用 clipboardData 对象:在 IE 中,这个对象是 window 对象的属性;而在 Firefox 4+Safari Chrome 中,这个对象是相应 event 对象的属性。但是,在 FirefoxSafari Chorme 中,只有在处理剪贴板事件期间 clipboardData 对象才有效,这是为了防止对剪贴板的未授权访问;在 IE 中,则可以随时访问 clipboardData 对象。为了确保跨浏览器兼容性,最好只在发生剪贴板事件期间使用这个对象。

这个 clipboardData 对象有三个方法: getData()setData()clearData()。 其中, getData() 用于从剪贴板中取得数据,它接受一个参数,即要取得的数据的格式。在 IE 中,有两种数据格式: "text""URL"。在 FirefoxSafari Chrome 中,这个参数是一种 MIME 类型;不过,可以用"text"代表"text/plain"setData()方法的第一个参数也是数据类型,第二个参数是要放在剪贴板中的文本。

var EventUtil = {
    //省略的代码
    getClipboardText: function(event){
        var clipboardData = (event.clipboardData ||    window.clipboardData);
        return clipboardData.getData("text");
    },
    //省略的代码
    setClipboardText: function(event, value){
        if (event.clipboardData){
            return event.clipboardData.setData("text/plain", value);
        } else if (window.clipboardData){
            return window.clipboardData.setData("text", value);
        }
    },
    //省略的代码
};

 6. 自动切换焦点:

(function(){
    function tabForward(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        if (target.value.length == target.maxLength){
            var form = target.form;
            for (var i=0, len=form.elements.length; i < len; i++) {
                if (form.elements[i] == target) {
                    if (form.elements[i+1]){
                        form.elements[i+1].focus();
                    }
                    return;
                }
            }
        }
    }
    var textbox1 = document.getElementById("txtTel1");
    var textbox2 = document.getElementById("txtTel2");
    var textbox3 = document.getElementById("txtTel3");
    EventUtil.addHandler(textbox1, "keyup", tabForward);
    EventUtil.addHandler(textbox2, "keyup", tabForward);
    EventUtil.addHandler(textbox3, "keyup", tabForward);
})();    

 7. 下拉列表select:

移动和重排选项:

使用 DOM appendChild()方法,就可以将第一个选择框中的选项直接移动到第二个选择框中。我们知道,如果为 appendChild()方法传入一个文档中已有的元素,那么就会先从该元素的父节点中移除它,再把它添加到指定的位置。

移动选项与移除选项有一个共同之处,即会重置每一个选项的 index 属性。
要将选择框中的某一项移动到特定位置,最合适的 DOM 方法就是 insertBefore()appendChild()方法只适用于将选项添加到选择框的最后。

8. 富文本:

(1) 要让文档中的 iframe 可以编辑,必须要将 designMode 设置为"on",但只有在页面完全加载之后才能设置这个属性。

<iframe name="edit" height="100" width="200"></iframe>

<script>
window.addEventListener('load', function() {
  frames['edit'].document.designMode = 'on';
});
</script>

 (2) 另一种编辑富文本内容的方式是使用名为 contenteditable 的特殊属性,可以把 contenteditable 属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。

<div class="editable" id="richedit" contenteditable></div>

 (3) 与富文本编辑器交互的主要方式,就是使用 document.execCommand()可以为 document.execCommand()方法传递 3 个参数:要执行的命令名称、表示浏览器是否应该为当前命令提供用户界面的一个布尔值和执行命令必须的一个值(如果不需要值,则传递 null)。

十一. HTML5:

1. 跨文档消息传送简称为 XDM,指的是在来自不同域的页面间传递消息。例如, www.wrox.com 域中的页面与位于一个内嵌框架中的 p2p.wrox.com 域中的页面通信。

XDM 的核心是 postMessage()方法。在 HTML5 规范中,除了 XDM 部分之外的其他部分也会提到这个方法名,但都是为了同一个目的:向另一个地方传递数据。对于 XDM 而言, 另一个地方指的是包含在当前页面中的<iframe>元素,或者由当前页面弹出的窗口。
postMessage()方法接收两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。第二个参数对保障安全通信非常重要,可以防止浏览器把消息发送到不安全的地方。

//注意:所有支持 XDM 的浏览器也支持 iframe 的 contentWindow 属性
var iframeWindow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("A secret", "http://www.wrox.com");

最后一行代码尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于"http://www.wrox.com"域。如果来源匹配,消息会传递到内嵌框架中;否则, postMessage()什么也不做。

接收到 XDM 消息时,会触发 window 对象的 message 事件。这个事件是以异步形式触发的,因此从发送消息到接收消息(触发接收窗口的 message 事件)可能要经过一段时间的延迟。触发 message 事件后,传递给 onmessage 处理程序的事件对象包含以下三方面的重要信息。
data:作为 postMessage()第一个参数传入的字符串数据。
origin:发送消息的文档所在的域,例如"http://www.wrox.com"。 

source:发送消息的文档的 window 对象的代理。这个代理对象主要用于在发送上一条消息的窗口中调用 postMessage()方法。如果发送消息的窗口来自同一个域,那这个对象就是 window

接收到消息后验证发送窗口的来源是至关重要的。就像给 postMessage()方法指定第二个参数,以确保浏览器不会把消息发送给未知页面一样,在 onmessage 处理程序中检测消息来源可以确保传入的消息来自已知的页面。基本的检测模式如下。

EventUtil.addHandler(window, "message", function(event){
    //确保发送消息的域是已知的域
    if (event.origin == "http://www.wrox.com"){
        //处理接收到的数据
        processMessage(event.data);
        //可选:向来源窗口发送回执
        event.source.postMessage("Received!", "http://p2p.wrox.com");
    }
});

event.source 大多数情况下只是 window 对象的代理,并非实际的 window 对象。换句话说,不能通过这个代理对象访问 window 对象的其他任何信息。记住,只通过这个代理调用postMessage()就好,这个方法永远存在,永远可以调用 。

2. 拖拽

(1)拖动某元素时,将依次触发dragstart、drag、dragend:

按下鼠标键并开始移动鼠标时,会在被拖放的元素上触发 dragstart 事件。此时光标变成不能放符号(圆环中有一条反斜线),表示不能把元素放到自己上面。拖动开始时,可以通过 ondragstart 事件处理程序来运行 JavaScript 代码。触发 dragstart 事件后,随即会触发 drag 事件,而且在元素被拖动期间会持续触发该事件。这个事件与 mousemove 事件相似,在鼠标移动过程中, mousemove 事件也会持续发生。 当拖动停止时(无论是把元素放到了有效的放置目标,还是放到了无效的放置目标上),会触发 dragend 事件。上述三个事件的目标都是被拖动的元素。默认情况下,浏览器不会在拖动期间改变被拖动元素的外观,但你可以自己修改。不过,大多数浏览器会为正被拖动的元素创建一个半透明的副本,这个副本始终跟随着光标移动。

(2)当某个元素被拖动到一个有效的放置目标上时,会依次发生:dragenter、dragover、dragleave drop:

只要有元素被拖动到放置目标上,就会触发 dragenter 事件(类似于 mouseover 事件)。紧随其后的是 dragover 事件,而且在被拖动的元素还在放置目标的范围内移动时,就会持续触发该事件。如果元素被拖出了放置目标, dragover 事件不再发生,但会触发 dragleave 事件(类似于 mouseout 事件)。如果元素被放到了放置目标中,则会触发 drop 事件而不是 dragleave 事件。上述三个事件的目标都是作为放置目标的元素。

(3)当遇到不可拖拽到的目标时:

var droptarget = document.getElementById("droptarget");
EventUtil.addHandler(droptarget, "dragover", function(event){
    EventUtil.preventDefault(event);
});
EventUtil.addHandler(droptarget, "dragenter", function(event){
    EventUtil.preventDefault(event);
});

// 在 Firefox 3.5+中,放置事件的默认行为是打开被放到放置目标上的 URL。为了让 Firefox 支持正常的拖放,还要取消 drop 事件的默认行为,阻止它打开 URL:
EventUtil.addHandler(droptarget, "drop", function(event){
    EventUtil.preventDefault(event);
});

(4)数据交换:dataTransfer对象。

dataTransfer 对象有两个主要方法: getData()setData()。不难想象, getData()可以取得由 setData()保存的值。 setData()方法的第一个参数,也是 getData()方法唯一的一个参数,是一个字符串,表示保存的数据类型,取值为"text""URL"。

//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//设置和接收 URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

IE只定义了"text""URL"两种有效的数据类型,而HTML5则对此加以扩展,允许指定各种 MIME 类型。考虑到向后兼容, HTML5 也支持"text""URL",但这两种类型会被映射为"text/plain""text/uri-list"

实际上, dataTransfer 对象可以为每种 MIME 类型都保存一个值。换句话说,同时在这个对象中保存一段文本和一个 URL 不会有任何问题。不过,保存在 dataTransfer 对象中的数据只能在 drop 事件处理程序中读取。如果在 ondrop 处理程序中没有读到数据,那就是 dataTransfer 对象已经被销毁,数据也丢失了。

在拖动文本框中的文本时,浏览器会调用 setData()方法,将拖动的文本以"text"格式保存在 dataTransfer 对象中。

(5)拖拽属性:dataTransfer 对象的两个属性: dropEffect 和 effectAllowed

dropEffect:

"none":不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值。
"move":应该把拖动的元素移动到放置目标。
"copy":应该把拖动的元素复制到放置目标。
"link":表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有 URL)。

dropEffect 属性只有搭配 effectAllowed 属性才有用。 effectAllowed 属性表示允许拖动元素的哪种 dropEffecteffectAllowed 属性:

"uninitialized":没有给被拖动的元素设置任何放置行为。
"none":被拖动的元素不能有任何行为。
"copy":只允许值为"copy"dropEffect
"link":只允许值为"link"dropEffect
"move":只允许值为"move"dropEffect
"copyLink":允许值为"copy""link"dropEffect
"copyMove":允许值为"copy""move"dropEffect
"linkMove":允许值为"link""move"dropEffect
"all":允许任意 dropEffect
必须在 ondragstart 事件处理程序中设置 effectAllowed 属性。
(6)可拖动:draggable

<!-- 让这个图像不可以拖动 -->
<img src="smile.gif" draggable="false" alt="Smiley face">
<!-- 让这个元素可以拖动 -->
<div draggable="true">...</div>

(7)其他:

HTML5 规范规定 dataTransfer 对象还应该包含下列方法和属性。
addElement(element):为拖动操作添加一个元素。添加这个元素只影响数据(即增加作为拖动源而响应回调的对象),不会影响拖动操作时页面元素的外观。在写作本书时, 只有 Firefox 3.5+实现了这个方法。
clearData(format):清除以特定格式保存的数据。实现这个方法的浏览器有 IEFireforx 3.5+Chrome Safari 4+
setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。这个方法接收的三个参数分别是要显示的 HTML 元素和光标在图像中的 xy 坐标。其中, HTML 元素可以是一幅图像,也可以是其他元素。是图像则显示图像,是其他元素则显示渲染后的元素。实现这个方法的浏览器有 Firefox 3.5+Safari 4+Chrome
types:当前保存的数据类型。这是一个类似数组的集合,以"text"这样的字符串形式保存着数据类型。实现这个属性的浏览器有 IE10+Firefox 3.5+Chrome

 3. 音视频:

<audio>元素还有一个原生的 JavaScript 构造函数 Audio,可以在任何时候播放音频。从同为 DOM 元素的角度看, Audio Image 很相似,但 Audio 不用像 Image 那样必须插入到文档中。只要创建一个新实例,并传入音频源文件即可。

var audio = new Audio("sound.mp3");
EventUtil.addHandler(audio, "canplaythrough", function(event){
    audio.play();
});

创建新的 Audio 实例即可开始下载指定的文件。下载完成后,调用 play()就可以播放音频。在 iOS 中,调用 play()时会弹出一个对话框,得到用户的许可后才能播放声音。如果想在一段音频播放后再播放另一段音频,必须在 onfinish 事件处理程序中调用 play()方法。

4. 历史状态管理

通过 hashchange 事件,可以知道 URL 的参数什么时候发生了变化,即什么时候该有所反应。而通过状态管理 API,能够在不加载新页面的情况下改变浏览器的 URL 。为此 ,需要使用history.pushState()方法,该方法可以接收三个参数:状态对象、新状态的标题和可选的相对 URL。

history.pushState({name:"Nicholas"}, "Nicholas' page", "nicholas.html");

执行 pushState()方法后,新的状态信息就会被加入历史状态栈,而浏览器地址栏也会变成新的相对 URL。但是,浏览器并不会真的向服务器发送请求,即使状态改变之后查询 location.href 也会返回与地址栏中相同的地址。

按下“后退”按钮,会触发 window 对象的 popstate 事件popstate 事件的事件对象有一个 state 属性,这个属性就包含着当初以第一个参数传递给 pushState() 的状态对象。

EventUtil.addHandler(window, "popstate", function(event){
    var state = event.state;
    if (state){ //第一个页面加载时 state 为空
        processState(state);
    }
});

十二. 错误处理与调试:

1. 如果提供 finally 子句,则 catch 子句就成了可选的( catch finally 有一个即可)。只要代码中包含 finally 子句,那么无论 try 还是 catch 语句块中的 return 语句都将被忽略。

function testFinally(){
    try {
        return 2;
    } catch (error){
        return 1;
    } finally {
        return 0;
    }
}

比如上例:调用这个函数只能返回 0。如果把 finally 子句拿掉,这个函数将返回 2

2. 错误类型(ECMA-262定义了 7 个):

Error:主要是浏览器抛出的;
EvalError:会在使用 eval()函数而发生异常时被抛出。
RangeError:会在数值超出相应范围时触发。
ReferenceError:在找不到对象的情况下,会发生 ReferenceError(这种情况下,会直接导致人所共知的"object expected"浏览器错误)。通常,在访问不存在的变量时,就会发生这种错误。
SyntaxError:当我们把语法错误的 JavaScript 字符串传入 eval()函数时,就会导致此类错误。
TypeError:在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致这种错误。归根结底还是由于在执行特定于类型的操作时,变量的类型并不符合要求所致。
URIError:在使用 encodeURI()decodeURI(),而 URI 格式不正确时,就会导致 URIError 错误。这种错误也很少见,因为前面说的这两个函数的容错性非常高。

var obj = x; //在 x 并未声明的情况下抛出 ReferenceError

eval("a ++ b"); //抛出 SyntaxError

var o = new 10; //抛出 TypeError
alert("name" in true); //抛出 TypeError
Function.prototype.toString.call("name"); //抛出 TypeError

// 处理错误类型
try {
    someFunction();
} catch (error){
    if (error instanceof TypeError){
    //处理类型错误
    } else if (error instanceof ReferenceError){
    //处理引用错误
    } else {
    //处理其他类型的错误
    }
}
var obj = x; //在 x 并未声明的情况下抛出 ReferenceError

eval("a ++ b"); //抛出 SyntaxError

var o = new 10; //抛出 TypeError
alert("name" in true); //抛出 TypeError
Function.prototype.toString.call("name"); //抛出 TypeError

// 处理错误类型
try {
    someFunction();
} catch (error){
    if (error instanceof TypeError){
    //处理类型错误
    } else if (error instanceof ReferenceError){
    //处理引用错误
    } else {
    //处理其他类型的错误
    }
}

 3. 抛出错误与使用 try-catch:

关于何时该抛出错误,而何时该使用 try-catch 来捕获它们,是一个老生常谈的问题。一般来说,应用程序架构的较低层次中经常会抛出错误,但这个层次并不会影响当前执行的代码,因而错误通常得不到真正的处理。如果你打算编写一个要在很多应用程序中使用的 JavaScript 库,甚至只编写一个可能会在应用程序内部多个地方使用的辅助函数,我都强烈建议你在抛出错误时提供详尽的信息。然后,即可在应用程序中捕获并适当地处理这些错误。说到抛出错误与捕获错误,我们认为只应该捕获那些你确切地知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理它们;而抛出错误的目的在于提供错误发生具体原因的消息。

4. 错误事件:

只要发生错误,无论是不是浏览器生成的,都会触发 error 事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误消息。像下面这样在事件处理程序中返回false,可以阻止浏览器报告错误的默认行为。

window.onerror = function(message, url, line){
    alert(message);
    return false;
};

通过返回 false,这个函数实际上就充当了整个文档中的 try-catch 语句,可以捕获所有无代码处理的运行时错误。这个事件处理程序是避免浏览器报告错误的最后一道防线,理想情况下,只要可能就不应该使用它。

5. url 错误:

// 不正确的url
http://www.yourdomain.com/?redir=http://www.someotherdomain.com?a=b&c=d

// 针对"redir="后面的所有字符串调用 encodeURIComponent()就可以解决这个问题
http://www.yourdomain.com/?redir=http%3A%2F%2Fwww.someotherdomain.com%3Fa%3Db%26c%3Dd

 对于查询字符串,应该记住必须要使用 encodeURIComponent()方法。 

十三. Ajax:

1. 在接收到响应之前还可以调用 abort()方法来取消异步请求:xhr.abort();调用这个方法后, XHR 对象会停止触发事件,而且也不再允许访问任何与响应有关的对象属性。在终止请求之后,还应该对 XHR 对象进行解引用操作。由于内存原因,不建议重用 XHR 对象。

2. 使用 GET 请求经常会发生的一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用 encodeURIComponent()进行编码,然后才能放到 URL 的末尾;而且所有名-值对儿都必须由和号( &)分隔,如下面的例子所示。

xhr.open("get", "example.php?name1=value1&name2=value2", true);

下面这个函数可以辅助向现有 URL 的末尾添加查询字符串参数:

function addURLParam(url, name, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(name) + "=" +  encodeURIComponent(value);
    return url;
}

 3. 进度事件:

4. Comet:

Comet 是一种服务器向页面推送数据的技术。 Comet 能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。

有两种实现 Comet 的方式: 长轮询。长轮询是传统轮询(也称为短轮询)的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。第二种流行的 Comet 实现是 HTTP 流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个 HTTP 连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。

5. 服务器发送事件(SSE:Server-Sent Events):

SSE API 用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。SSE 支持短轮询、长轮询和 HTTP 流,而且能在断开连接时自动确定何时重新连接。

6.Web Sockets : 

Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信。在 JavaScript 中创建了 Web Socket 之后,会有一个 HTTP 请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用 HTTP 升级从 HTTP 协议交换为 Web Socket 协议。也就是说,使用标准的 HTTP 服务器无法实现 Web Sockets,只有支持这种协议的专门服务器才能正常工作。

7. 安全:

为确保通过 XHR 访问的 URL 安全,通行的做法就是验证发送请求者是否有权限访问相应的资源。有下列几种方式可供选择:
要求以 SSL 连接来访问可以通过 XHR 请求的资源。
要求每一次请求都要附带经过相应算法计算得到的验证码。
请注意,下列措施对防范 CSRF 攻击不起作用。
要求发送 POST 而不是 GET 请求——很容易改变。
检查来源 URL 以确定是否可信——来源记录很容易伪造。
基于 cookie 信息进行验证——同样很容易伪造。

十四. 高级技巧:

1. 类型检测:

instanceof 操作符在存在多个全局作用域(像一个页面包含多个 frame)的情况下, 可能会判断错误,如:

var isArray = value instanceof Array;

以上代码要返回 truevalue 必须是一个数组,而且还必须与 Array 构造函数在同个全局作用域中。(别忘了, Array window 的属性。)如果 value 是在另个 frame 中定义的数组,那么以上代码就会返回 false

Web 开发中能够区分原生与非原生 JavaScript 对象非常重要。只有这样才能确切知道某个对象到底有哪些功能。这个技巧可以对任何对象给出正确的结论。

注:Object.prototpye.toString()本身也可能会被修改。本节讨论的技巧假设 Object.prototpye.toString()是未被修改过的原生版本。

2. 作用域安全的构造函数:

上面这段重写的代码中, 一个Rectangle 实例也同时是一个Polygon 实例, 所以Polygon.call() 会照原意执行,最终为 Rectangle 实例添加了 sides 属性。

3. 惰性载入函数:

惰性载入表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式,第一种就是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对 原 函数 的调 用 都不 用再 经 过执 行的 分 支了 。例 如 ,可以用 下面的方式使用惰性载入重写 createXHR()。

方式一:

 方式二:

 4.函数绑定:

function bind(fn, context){
    return function(){
        return fn.apply(context, arguments);
    };
}

只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及 setTimeout() setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。

5. 函数柯里化:

curry() 函数的主要工作是将被返回函数的参数进行排序。 curry() 的第一个参数是要进行柯里化的函数,其他参数是要传入的值。

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式。

function curry(fn){
    var args = Array.prototype.slice.call(arguments, 1);
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    };
}

 5. 防篡改对象:

(1) 不可扩展:Object.preventExtensions(),不能给对象添加新属性和方法;

(2) 密封对象:Object.seal(),密封对象不可扩展,而且已有成员的 [[Configurable]] 特性将被设置为 false,即不能删除属性和方法,不能使用 Object.defineProperty()把数据属性修改为访问器属性。检测:使用 Object.isSealed()方法可以确定对象是否被密封了。因为被密封的对象不可扩展,Object.isExtensible() 检测密封的对象也会返回 false

(3) 冻结对象:Object.freeze(),不可扩展,密封的,对象数据属性的 [[Writable]] 特性会被设置为 false。检测:Object.isFrozen() 方法用于检测冻结对象。冻结对象既是密封的又不可扩展,用 Object.isExtensible() Object.isSealed() 检测冻结对象将分别返回 false true

6. 高级定时器:关于定时器,指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。

注:setInterval():这种重复定时器的规则有两个问题: (1) 某些间隔会被跳过; (2) 多个定时器的代码执行之间的间隔可能会比预期的小。替代方案如下(链式调用 setTimeout):

setTimeout(function(){
    //处理中
    setTimeout(arguments.callee, interval);
}, interval);

7. Yielding Processes:

数组分块技术:基本的思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。基本的模式如下:

function chunk(array, process, context){
    setTimeout(function(){
        var item = array.shift();
        process.call(context, item);
        if (array.length > 0){
            setTimeout(arguments.callee, 100);
        }
    }, 100);
}

在数组分块模式中, array 变量本质上就是一个“待办事宜”列表,它包含了要处理的项目。使用 shift() 方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过 arguments.callee 调用同一个匿名函数。chunk()方法接受三个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境。

8. 函数节流:

function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId= setTimeout(function(){
        method.call(context);
    }, 100);
}

函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。

9. 自定义事件:

自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式如下:

function EventTarget(){
    this.handlers = {};
}
EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function(type, handler){
        if (typeof this.handlers[type] == "undefined"){
            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
    fire: function(event){
        if (!event.target){
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array){
            var handlers = this.handlers[event.type];
            for (var i=0, len=handlers.length; i < len; i++){
                handlers[i](event);
            }
        }
    },
    removeHandler: function(type, handler){
        if (this.handlers[type] instanceof Array){
            var handlers = this.handlers[type];
            for (var i=0, len=handlers.length; i < len; i++){
                if (handlers[i] === handler){
                    break;
                }
            }
            handlers.splice(i, 1);
        }
    }
};    

EventTarget 类型有一个单独的属性 handlers,用于储存事件处理程序。还有三个方法:addHandler() , 用于注册给定类型事件的事件处理程序; fire() ,用于触发一个事件;removeHandler(),用于注销某个事件类型的事件处理程序。

十五. 离线存储:

1. navigator.onLine 属性,这个属性值为 true 表示设备能上网,值为 false 表示设备离线。

2. HTML5 还定义了两个事件:online offline。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件。这两个事件在 window 对象上触发。

EventUtil.addHandler(window, "online", function(){
    alert("Online");
});
EventUtil.addHandler(window, "offline", function(){
    alert("Offline");
});

 3. cookie:

(1)当超过单个域名限制之后还要再设置 cookie,浏览器就会清除以前设置的 cookieIE Opera 会删除最近最少使用过的( LRULeast Recently Usedcookie,腾出空间给新设置的 cookieFirefox 看上去好像是随机决定要清除哪个 cookie,所以考虑 cookie 限制非常重要,以免出现不可预期的后果。浏览器中对于 cookie 的尺寸也有限制。大多数浏览器都有大约 4096B(加减 1)的长度限制。

  为了最佳的浏览器兼容性,最好将整个 cookie 长度限制在 4095B(含 4095)以内。尺寸限制影响到一个域下所有的 cookie,而并非每个 cookie 单独限制。如果你尝试创建超过最大尺寸限制的 cookie,那么该 cookie 会被悄无声息地丢掉。注意,虽然一个字符通常占用一字节,但是多字节情况则有不同。

(2)cookie 所有的 name 和 value 都需经过 URL 编码,所以使用时也必须使用 decodeURIComponent()来解码。
(3)由于 js 中读写入 cookie 都不是很直观,所以一般会写一些函数来简化 cookie 的功能:

 

 这些方法通过处理解析、构造 cookie 字符串的任务令在客户端利用 cookie 存储数据更加简单。

 (4)cookie:cookie 是存放在单个 cookie 中的更小段的数据,也就是使用 cookie 值来存储多个名称值对儿。子 cookie 最常见的的格式如下所示:

name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5

4. storage 事件:

Storage 对象进行任何修改,都会在文档上触发 storage 事件。当通过属性或 setItem() 方法保存数据,使用 delete 操作符或 removeItem()删除数据,或者调用 clear()方法时,都会发生该事件。这个事件的 event 对象有以下属性:

  • domain:发生变化的存储空间的域名。
  • key:设置或者删除的键名。
  • newValue:如果是设置值,则是新值;如果是删除键,则是 null
  • oldValue:键被更改之前的值。在这四个属性中, IE8 Firefox 只实现了 domain 属性。在撰写本书的时候, WebKit 尚不支持storage 事件。

以下代码展示了如何侦听 storage 事件: 

EventUtil.addHandler(document, "storage", function(event){
    alert("Storage changed for " + event.domain);
});

 5. IndexedDB:
IndexedDB 是一种类似 SQL 数据库的结构化数据存储机制。但它的数据不是保存在表中,而是保存在对象存储空间中。创建对象存储空间时,需要定义一个键,然后就可以添加数据。可以使用游标在对象存储空间中查询特定的对象。而索引则是为了提高查询速度而基于特定的属性创建的。有了以上这些选择,就可以在客户端机器上使用 JavaScript 存储大量数据了。但你必须小心,不要在客户端存储敏感数据,因为数据缓存不会加密。

var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB ||
window.webkitIndexedDB;

 注:

IndexedDB 的限制很多都与对 Web Storage 的类似。首先, IndexedDB 数据库只能由同源(相同协议、域名和端口)页面操作,因此不能跨域共享信息。换句话说, www.wrox.com p2p.wrox.com 的数据库是完全独立的。其次,每个来源的数据库占用的磁盘空间也有限制。 Firefox 4+目前的上限是每个源 50MB,而Chrome 的限制是 5MB。移动设备上的 Firefox 最多允许保存 5MB,如果超过了这个配额,将会请求用户的许可。Firefox 还有另外一个限制,即不允许本地文件访问 IndexedDBChrome 没有这个限制。

十六. API:

1. web 性能:

Web 计时机制的核心是 window.performance 对象。对页面的所有度量信息,包括那些规范中已经定义的和将来才能确定的,都包含在这个对象里面。 Web Timing 规范一开始就为 performance 对象定义了两个属性。其中, performance.navigation 属性也是一个对象,包含着与页面导航有关的多个属性, 如下所示。
redirectCount:页面加载前的重定向次数。
type:数值常量,表示刚刚发生的导航类型。
performance.navigation.TYPE_NAVIGATE (0):页面第一次加载。
performance.navigation.TYPE_RELOAD (1):页面重载过。
performance.navigation.TYPE_BACK_FORWARD (2):页面是通过“后退”或“前进”按钮打开的。
另外, performance.timing 属性也是一个对象,但这个对象的属性都是时间戳(从软件纪元开始经过的毫秒数),不同的事件会产生不同的时间值。

2. Web Worker:

随着 Web 应用复杂性的与日俱增,越来越复杂的计算在所难免。长时间运行的 JavaScript 进程会导致浏览器冻结用户界面,让人感觉屏幕“冻结”了。 Web Workers 规范通过让 JavaScript 在后台运行解决了这个问题。浏览器实现 Web Workers 规范的方式有很多种,可以使用线程、后台进程或者运行在其他处理器核心上的进程,等等。

实例化 Worker 对象并传入要执行的 JavaScript 文件名就可以创建一个新的 Web Worker关于 Web Worker,最重要的是要知道它所执行的 JavaScript 代码完全在另一个作用域中,与当前网
页中的代码不共享作用域。在 Web Worker 中,同样有一个全局对象和其他对象以及方法。但是, Web Worker 中的代码不能访问 DOM,也无法通过任何方式影响页面的外观。Web Worker 中的全局对象是 worker 对象本身。也就是说,在这个特殊的全局作用域中, this self 引用的都是 worker 对象。为便于处理数据, Web Worker 本身也是一个最小化的运行环境。

var worker = new Worker("stufftodo.js");

 关于 Web Worker,最重要的是要知道它所执行的 JavaScript 代码完全在另一个作用域中,与当前网页中的代码不共享作用域。在 Web Worker 中,同样有一个全局对象和其他对象以及方法。但是, Web Worker 中的代码不能访问 DOM,也无法通过任何方式影响页面的外观。Web Worker 中的全局对象是 worker 对象本身。也就是说,在这个特殊的全局作用域中, this 和 self 引用的都是 worker 对象。为便于处理数据, Web Worker 本身也是一个最小化的运行环境。

原文地址:https://www.cnblogs.com/momo798/p/7677220.html