一. 数据类型:javascript
1. undefined: 未声明和未初始化的变量,typeof 操做符返回的结果都是 undefined;(建议未初始化的变量进行显式赋值,这样当 typeof 返回 undefined 时就知道是未声明了,帮助定位问题)php
2. null:建议,将即将保存但还未真正保存对象的变量,赋值为 null;html
3. number: 保存浮点数须要的内存空间是保存整数值的两倍,所以 ECMAScript 会不失时机的将浮点数值转换为整数值;前端
4. NaN: isNaN() 用于肯定其参数是否“不是数值”;eg: isNaN("blue") —— true (由于不是数值);java
附: typeof 能够判断的类型:undefined/boolean/string/number/function;node
5. parseInt(): 第二个参数能够指定进制,使参数按照指定进制转换,如不指定进制,则按照十进制转换;android
parseFloat() 只解析十进制值,他没有第二个参数指定基数的用法;ios
6. 转换为字符串: toString() 和 String();(null 和 undefined 没有 toString() 方法)web
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. 垃圾回收机制:
三. 引用类型:
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 操做符。以这种方式调用构造函数实际上会经历如下 4 个步骤:
(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. 窗口位置:
IE、 Safari、 Opera 和 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;
其子节点多是 Element、 Text、 Comment、 ProcessingInstruction、 CDATASection 或 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 指定的位置开始到 offset+ count 为止处的文本。
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 事件,不一样浏览器有不一样的机制。 IE、 Safari、 Chrome 和 Opera 会在浏览器窗口变化了 1 像素时就触发 resize 事件,而后随着变化不断重复触发。 Firefox 则只会在用户中止调整窗口大小时才会触发 resize 事件。因为存在这个差异,应该注意不要在这个事件的处理程序中加入大计算量的代码,由于这些代码有可能被频繁执行,从而致使浏览器反应明显变慢。浏览器窗口最小化或最大化时也会触发 resize 事件。
6. 鼠标事件:
触摸设备:
iOS 和 Android 设备的实现很是特别,由于这些设备没有鼠标。在面向 iPhone 和 iPod 中的 Safari 开发时,要记住如下几点。
不支持 dblclick 事件。双击浏览器窗口会放大画面,并且没有办法改变该行为。
轻击可单击元素会触发 mousemove 事件。若是此操做会致使内容变化,将再也不有其余事件发生;若是屏幕没有所以变化,那么会依次发生 mousedown、 mouseup 和 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 对象都包含着标准的鼠标事件属性: bubbles、cancelable、 view、 clientX、 clientY、 screenX、 screenY、 detail、 altKey、 shiftKey、ctrlKey 和 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:在发生粘贴操做时触发
在实际的事件发生以前,经过 beforecopy、 beforecut 和 beforepaste 事件能够在向剪贴板发送数据,或者从剪贴板取得数据以前修改数据。不过,取消这些事件并不会取消对剪贴板的操做——只有取消 copy、 cut 和 paste 事件,才能阻止相应操做发生。
要访问剪贴板中的数据,可使用 clipboardData 对象:在 IE 中,这个对象是 window 对象的属性;而在 Firefox 4+、 Safari 和 Chrome 中,这个对象是相应 event 对象的属性。可是,在 Firefox、Safari 和 Chorme 中,只有在处理剪贴板事件期间 clipboardData 对象才有效,这是为了防止对剪贴板的未受权访问;在 IE 中,则能够随时访问 clipboardData 对象。为了确保跨浏览器兼容性,最好只在发生剪贴板事件期间使用这个对象。
这个 clipboardData 对象有三个方法: getData()、 setData()和 clearData()。 其中, getData() 用于从剪贴板中取得数据,它接受一个参数,即要取得的数据的格式。在 IE 中,有两种数据格式: "text"和"URL"。在 Firefox、 Safari 和 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 属性表示容许拖动元素的哪一种 dropEffect, effectAllowed 属性:
"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):清除以特定格式保存的数据。实现这个方法的浏览器有 IE、Fireforx 3.5+、Chrome 和 Safari 4+。
setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。这个方法接收的三个参数分别是要显示的 HTML 元素和光标在图像中的 x、 y 坐标。其中, 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;
以上代码要返回 true, value 必须是一个数组,并且还必须与 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,浏览器就会清除之前设置的 cookie。 IE 和 Opera 会删除最近最少使用过的( LRU, Least Recently Used) cookie,腾出空间给新设置的 cookie。 Firefox 看上去好像是随机决定要清除哪一个 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 对象有如下属性:
如下代码展现了如何侦听 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 还有另一个限制,即不容许本地文件访问 IndexedDB。 Chrome 没有这个限制。
十六. 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 自己也是一个最小化的运行环境。