JavaScript 概览 更新时间2014-0414-0837

一些概念

DOM(文档对象模型)是HTML和XML的应用程序接口(API)。
DOM Level1规划文档结构;DOM Level2扩展了对鼠标和用户界面事件等的支持;DOM Level3支持了XML1.0特性,包括XPath等。
还有其他语言发布了自己的DOM标准:可缩放矢量图形(SVG),同步多媒体集成语言(SMIL)等。
DOM描述了处理网页内容的方法和接口;BOM描述了与浏览器进行交互的方法和接口。

ECMAScript数据类型

5种原始类型:UndefinedNullBooleanNumberString
对应的使用typeof运算符返回:undefined、object、boolean、number、string。
undefined值实际上是从null派生而来,因此null==undefined。

所有ECMAScript数值必须在Number.MAX_VALUENumber.MIN_VALUE之间;
当计算的数大于Number.MAX_VALUE时候会被赋予值Number.POSITIVE_INFINITY,显示为Infinity
当计算的数小于Number.MIN_VALUE时候会被赋予值Number.NEGATIVE_INFINITY,显示为-Infinity
可以对任何数调用isFinite() ,已确保该数不是无穷大。
另外还存在一个特殊值NaN,表示非数,例如在类型转换失败时会变为非数,可以通过isNaN()来判断,另外NaN不等于NaN。

转换数字可以使用parseInt()parseFloat()方法,只能对String类型的调用这些方法,对其他类型的返回为NaN。
强制类型转换可以使用Boolean()Number()String()
Number(undefined)返回NaN;Number(null)返回0。

Object类具有的属性:constructorprototype
Object类具有的方法:hasOwnProperty()isPrototypeOf()propertyIsEnumerable()toString()valueOf()

 var o={};
alert(o.hasOwnProperty("toString")); /* false */
alert(Math.hasOwnProperty("cos")); /* true */

hasOwnProperty方法判断一个对象的非继承属性

当一个对象用于数字环境时,JavaScript首先调用该对象的valueOf()方法,
如果valueOf()方法返回对象本身,JavaScript会使用toString()方法把对象转换为一个字符串,然后再试图转换为对象。

 var o={
valueOf:function(){
return {}; /* 这里的值不能转换为数字,会尝试调用toString */
},
toString:function(){
return "10"; /* 这里如果返回10,那么最后输出就是10+10=20 */
}
};
o+10 /* 这里输出"1010" */

对象用于数字环境的默认方法调用,valueOf()以及toString()方法

String类的各种方法:

charCodeAt 字符代码
concat 连接一个或多个字符串,保持原始的String对象不变
indexOf 指定子串在另一个字符串中的位置
slice(start,end) 该方法截取子串,若参数是负数则会用字符串的长度加上参数;不改变String对象自身
substring 该方法截取子串,如果对于负参数,将当作0处理;不改变String对象自身
toUpperCase 转换大写
toLowerCase 转换小写
charAt 获取一个位置的字符串 

JavaScript中没有改变字符串中字符的语法、方法或属性,这并非疏漏,
因为在JavaScript中字符串作为一种基本类型,通过复制和传递是不可改变原内容,在比较运算中,字符串是通过传值来比较的。

instanceof运算符:和typeof运算符类似,但该运算符可以确认对象为某特定类型,例如"hello" instanceof String。

 var number_obj = Object(3);
number_obj instanceof Number;

使用Object()方法产生包装对象

运算

逻辑OR运算中如果一个运算数是对象,另一个是Boolean值,则返回该对象,例如 false||{} 输出的是对象。
两个字符串形式的数字进行比较时候,比较的是字符代码;但如果一个字符串数字和数字进行比较,那么字符串数字会先转换为数字,例如 "23"<3为false;
字符串和数字比较,字符串无法转换为有效数字,例如 "a"<3 返回为false,因为"a"尝试parseInt()返回为NaN,任何和NaN的关系运算都为false。
字符串和布尔值比较,例如 "1"==true ,先把true转换为1,然后字符串"1"也发现和数字比较,也转换为1,两个数字比较,最后得出相等。

对象检测属性存在的in运算符(通过delete删除的对象属性使用in运算符也不会检测到):

 var o={x:100,y:undefined};
if("y" in o){
o.y=200;
}
if("z" in o){
o.z=200;
}
alert(o.y);
alert(o.z);

检查对象属性是否存在

函数

使用new Function来定义一个函数,允许JavaScript代码被动态创建并在运行时编译。 
尽管可以使用Function构造函数创建函数,但用它定义函数比用传统方式要慢得多,类似于eval;另外不能访问局部作用域(总是被当作顶层的函数):

 var g="global";
function createFun(){
var g="local";
return new Function("v1","v2","return v1+v2+g;");
}
alert(createFun()("hell","o"));

Function()所创建的函数作为顶层函数

函数内标识符arguments是一个类似数组的对象,其中还定义了callee属性,用来引用当前正在执行的函数,可以允许对未命名的函数递归地调用自身:

 (function(x){
if(x<=1) return 1;
return x+arguments.callee(x-1);
})(100);

一个自调用匿名函数的递归加法实现

函数的length属性是只读特性,返回的是函数需要的实际参数的数目:

 var f=function(x,y,z){
if(arguments.callee.length!=arguments.length){
return 0;
}
return x+y+z;
};
alert(f(1,2));
alert(f(1,2,3));

判断函数调用的参数和实际需要的参数是否相同

对象

ECMA-262定义的本地类:

Object Function Array String Boolean
Number Date RegExp Error EvalError
RangeError ReferenceError SyntaxError TypeError URIError

Array对象可以使用join方法连接字符串,String对象可以用split方法转换字符串为数组。
Array对象还具有两个String类具有的方法,concatslice方法,返回的为新的数组,不改变原始数组,其中concat方法的参数如果存在数组,则会被展开(但不递归展开)。
Array对象还有push方法(返回数组长度)、pop方法(返回删除的值)、shift方法(返回删除的值)、unshift方法(返回数组的长度)。
Array对象可以使用reverse方法颠倒数组项顺序,sort方法将根据数据项的值按升序排序:

 var a=[33,4,111,222];
a.sort(); /* 1111,222,33,4 */
a.sort(function(a,b){
return a-b;
}); /* 4,33,22,111 */

数组的自定义排序

Array对象还有个splice方法,可以实现删除替换操作,该方法会改变原始数组,方法本身返回删除了的元素数组:

 var a=[1,2,3,4];
alert(a.splice(2,1,300,301)); /* 3 */
alert(a); /* 1,2,300,301,4 */

splice方法测试

Array对象有些额外的方法,例如indexOf()和lastIndexOf()方法、forEach()方法、map()方法、filter()方法,如果想实现兼容需要对这几个方法进行更多兼容性处理:

 if(!Array.prototype.map){
Array.prototype.map=function(f,scope){
var re=[];
for(var len=this.length,i=0;i<len;i++){
re.push(f.call(scope,this[i],i,this));
}
return re;
};
}
[1,2,3].map(function(v,i,a){
return v+i;
}); /* return [1,3,5] */

兼容性实现Array.map

Date对象可以使用UTC方法返回日期的毫秒表示,例如 new Date(Date.UTC(2012,1,29,8,30)); 为UTC的2012年2月29日8点30分。

Global对象是个特别的对象,设计上根本不存在,如isNaN等方法都是Global对象的方法。
Global对象中还有encodeURI编码链接,还有encodeURIComponent对所有非标准字符进行编码。
Global对象的eval方法,解释程序会把eval方法的参数解释为真正的ECMAScript语句,然后插入该函数所在的位置,因此eval方法可以调用局部变量:

 (function(){
var msg="hello world";
eval("alert(msg);var o={o:1};");
var re=eval("({a:1})");
alert(o.o); /* eval中定义的变量 */
alert(re.a); /* eval可以返回一个变量 */
})();

eval调用局部变量 & 定义变量 & 返回变量

Math对象存在一个ceil向上舍入函数、floor方法向下舍入函数、round方法四舍五入函数。
Math对象配合random方法可以生成随机数,例如生成1到10之间的数: Math.floor(Math.random()*10+1);

继承

使用callapply方法可以实现对象冒充。

可以通过一个中间对象来实现继承:

 function extend(sb,sp){
var bridge=function(){};
bridge.prototype=sp.prototype;
var o=new bridge();
sb.prototype=o;
o.constructor=sb;
sb.superclass=sp;
return sb;
}
function class1(nm){
this.nm=nm;
}
class1.prototype.getnm=function(){
return this.nm;
};
function class2(nm){
class2.superclass.call(this,nm);
}
extend(class2,class1);
ao=new class2("zxf");
ao.getnm();

中间对象实现继承

属性继承只在读取属性值的时候发生,而当写入属性值的时候不会发生。

如上的中间对象继承方式,在ES5中内置了Object.create()方法,若没有第二个参数的情况下相当于:

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style rel="stylesheet" type="text/css">
</style>
<script type="text/javascript">
// 利用某个对象创建派生类的原型对象.
Object.create = Object.create || function(o){
function F(){};
// 注意IE下会忽略toString和valueOf方法的prototype
F.prototype = o;
return new F();
};
// 拷贝.
function extend(dest, source){
for(var p in source){
dest[p] = source[p];
}
return dest;
}
// 派生.
function inherit(chd, Parent, proto){
function f(){
// 继承父类的特权成员.
parent.apply(this,argument);
// 执行自身的构造函数.
chd.apply(this,argument);
}
// 这里的create可能是原生也可能是兼容处理的,因此不使用第二个参数.
f.prototype = Object.create(Parent.prototype,{});
// 设置派生类的构造函数.
f.prototype.constructor = f;
// 添加子类特有的原型成员.
extend(f.prototype, proto);
// 继承父类的类成员.
extend(f, Parent);
return f;
} window.onload=function(){
function A(){}
A.prototype={test:"a test"};
function B(){};
B.prototype=Object.create(A.prototype);
var b=new B();
document.body.innerHTML=b.test;
};
</script>
</head>
<body></body>
</html> Object.create实现继承

Object.create实现继承

另外在ES6中,__proto__ 属性已经规范化,因此一个类的对象可以通过__proto__访问到原型。

浏览器中的JavaScript

有时候会使用document.write()方法或innerHTML属性来输出其他脚本,这时候应该输出一个</script>标记来终止所创建的脚本,
因为HTML解析器不理解JavaScript代码,如果它看到代码中的"</script>"字符串,即使</script>标记出现在引号中,HTML解析器也会认为当前运行脚本结束。

 document.write("<script>");
document.write("alert('hello world');");
document.write("</"+"script>");

在脚本中输出脚本结束符需要拆分标记

URL中可以使用javascript:伪协议来执行javascript,因此可以将这种方式用于书签中,实现特定的Bookmarklets。

javascript:var cks=document.cookie,acks=cks.split(";"),info="";for(var s in acks){info+=acks[s]+"\n\n\n";}alert(info);

一个查看当前页面Cookie的书签片段

浏览器厂商的兼容性问题处理:例如Firefox,因为IE下支持document.all,
Firefox之后也添加了对document.all的支持,但是如果使用document.all来进行浏览器检测,Firefox会假装不支持。

 if(document.all||typeof document.all){
alert("document.all is ok.")
}
else{
alert("document.all?");
}
alert(document.all);

Firefox对于document.all的兼容性问题

IE的HTML条件注释,可以用于额外对IE支持不良好的JavaScript进行处理,IE10开始不支持条件注释。

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
</style>
<!--[if IE]>
<script type="text/javascript">
alert("IE");
</script>
<![endif]-->
<!--[if gt IE 6]>
<script type="text/javascript">
alert("gt IE 6");
</script>
<![endif]-->
<body>
</body>
</html>

IE的HTML条件注释

JavaScript在操作URL的时候容易被注入HTML或脚本,也称跨站脚本XSS,因此在通过JavaScript设计实现的时候特别要注意安全问题。
例如JavaScript使用document.location.search获取值并通过document.write写HTML,会产生安全问题。

BOM浏览器对象模型 基础

XHTML中引入javascript要注意需要CDATA段才能识别,另外要兼容不支持XHTML的浏览器(可能不识别CDATA段),因此嵌入XHTML的代码可能如下:

 <script type="text/javascript">
//<![CDATA[
function add(a,b){
return a+b;
}
//]]>
</script>

XHTML的javascript嵌入

window对象中有top对象和parent对象和self对象来指明窗口对象,还有frames属性包含这个窗口的帧。
如果给帧指定了名字,那对帧的引用会存储到它的父window对象的一个新属性中。

window.open()方法打开新窗口,存在四个参数(URL,新窗口名字,特性字符串,是否新载入页面替换当前页面),并可以通过window.close()来关闭窗口。
一个新窗口可以通过window.opener属性引用打开它的窗口,如果非JavaScript代码创建,那么它的opener属性是null。
一个新窗口关闭后,通过window.closed属性检测窗口是否关闭,因为即使窗口关闭了,代表它的window对象还存在。

 window.open("http://www.baidu.com","_blank","height=100,width=100,top=10,left=10");

window.open()使用

window对象可以通过alert()方法弹出显示框;通过confirm()方法弹出确定框;通过prompt()方法弹出文本输入框。

setTimeout()方法设置延迟执行,其中参数可以用字符串也可以直接传递函数;取消未执行的setTimeout()可以使用clearTimeout()方法。
setInterval()方法可以设置重复执行,同样的可以通过clearInterval()方法来停止执行。
有时候会使用setTimeout()方法注册一个延迟0微秒后调用的方法,这样浏览器会在完成文档的状态更新之后调用方法。

 (function(){
var x=10;
setTimeout("alert(typeof x);",1000); /* undefined */
setTimeout(function(){alert(typeof x)},2000); /* number */
})();

setTimeout执行作用域

window.history对象有go()方法,可以传递前进或后退的页面数,更简单的可以使用history.back()和history.forward()方法。

window.document对象比较特殊,即属于BOM又属于DOM。

window.location对象包含protocolhostportpathnamesearchhash以及href等属性。
window.location.replace()方法可以跳转到新页面并且从浏览器历史中删除原页面记录;
window.location.reload()方法可以从服务端重新加载当前页面。
window.location对象和document.location对象实际上是等同的:window.location===document.location。

 /* 获取#后的内容,#不存在则捕获空字符串,否则捕获#后字符串 */
function gethash(uri){
return uri.replace(/^[^#]*#?(.*)$/,'$1');
}
/* location.hash在获取例如#test?this_will_ignore这样的hash时会存在问题 */

location.hash存在的兼容问题以及修复

window.navigator对象包含大量WEB浏览器的信息,通常使用navigator.userAgent来判断客户端环境。

window.screen对象可以获取部分用户系统信息,例如screen.availHeight可获取屏幕可以使用的像素高度,screen.height可以获取屏幕高度。

window对象上有一组简单的属性来获取有关窗口的大小和位置信息,例如window.screenX等。
但旧版本的IE将这些窗口的几何属性放置在HTML文档的body元素上,而声明了DOCTYPE的IE6开始把这些属性放置在document.documentElement元素上。

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body div{background: #eee;height:1000px;}
</style>
<script>
function getWinInfo(){
var wininfo={};
var de=document.documentElement&&document.documentElement.clientWidth?document.documentElement:null;/* IE with DOCTYPE */
de=de?de:(document.body.clientWidth?document.body:null); /* IE without DOCTYPE */
if("screenX" in window){
// 浏览器位置.IE9开始支持.
wininfo.screenX=window.screenX;
wininfo.screenY=window.screenY;
wininfo.TEST0=true;
}
else if(window.screenLeft){
// IE.
wininfo.screenX=window.screenLeft;
wininfo.screenY=window.screenTop;
}
if(de){
// 视图大小.不包括滚动条.非IE浏览器也支持.
wininfo.innerWidth=de.clientWidth;
wininfo.innerHeight=de.clientHeight;
}
else if(window.innerWidth){
// 实际上这里的分支不会执行,加深理解.
// 视图大小.不包括滚动条.IE9支持,但IE理解为包含滚动条了.
wininfo.innerWidth=window.innerWidth;
wininfo.innerHeight=window.innerHeight;
wininfo.TEST1=true;
}
if("pageXOffset" in window){
// 滚动位置.IE9开始支持.
wininfo.pageXOffset=window.pageXOffset;
wininfo.pageYOffset=window.pageYOffset;
wininfo.TEST2=true;
}
else{
// 滚动位置.
wininfo.pageXOffset=de.scrollLeft;
wininfo.pageYOffset=de.scrollTop;
}
// 文档大小,不包括滚动条部分.
wininfo.scrollWidth=de.scrollWidth;
wininfo.scrollHeight=de.scrollHeight; var show="";
for(var s in wininfo){
show+=s+"="+wininfo[s]+"\n";
}
alert(show);
}
window.onload=function(){
document.body.firstChild.onclick=function(){
getWinInfo();
}
}
</script>
<body><div>click</div></body>
</html>

窗口几何属性的可移植实现

window对象中存在scrollBy()来进行文档的相对滚动和scrollTo()来进行文档的指定像素滚动;
另外可以通过focus()方法设置元素键盘焦点来使文档滚动到元素可见位置;
更多现代浏览器支持n.scrollIntoView()方法来使元素可见,该方法可以在任何HTML元素上工作;
另外还可以通过锚链接来滚动到指定位置,例如window.location.hash="#top",若不想打乱浏览历史,则可以window.location.replace("#top")来实现。

DOM文档对象模型 基础

document.write()方法可以在HTML中插入文本,但如果一个<script>标签包含defer属性,那就不能包含对document.write()的调用,
defer属性告诉浏览器,把脚本的调用延迟到文档完全载入后调用。

 var w=window.open(null,"_win_name");
var d=w.document;
d.open(); /* 非必需 */
d.write("<h1>","hello","</h1>");
d.close();

写文档,document.open() document.close() document.write()

document对象还定义了cookiedomainlastModifiedlocationreferrertitleURL等属性。
document对象还定义了一些文档集合,如anchorsformsimageslinks等文档集合,
如果通过name属性命名这些集合中的元素,例如<form name="myform">,那么可以通过document.myform的方式引用该表单,
如果一个文档中有多个文档元素具有相同值的name属性,那么document._myname属性就会是一个保存这些元素的数组。

DOM定义了Node的接口,定义了不同类型节点的12个常量,例如Node.ELEMENT_NODE值为1,Node.TEXT_NODE值为3,还定义了一些特性和方法:

nodeName 节点的名字
nodeValue 节点的值
nodeType 节点的类型常量值
ownerDocument 这个节点所属的文档
firstChild 指向在childNodes列表中的第一个节点
lastChild 指向在childNodes列表中的最后一个节点
childNodes 所有子节点的列表
previousSibling 前一个兄弟节点,如果这个节点已经是第一个兄弟节点,则为null
nextSibling 后一个兄弟节点,如果这个节点已经是最后一个兄弟节点,则为null
hasChildNodes() 判断childNodes是否包含一个或多个节点
attributes Element节点的Attr对象
appendChild(node) 将node节点添加到childNodes的末尾
removeChild(node) 从childNodes中删除node
replaceChild(newnode,oldnode) 将childNodes中的oldnode替换成newnode
insertBefore(newnode,refnode) 在childNodes中的refnode之前插入

访问HTML节点可以通过document.documentElement来获取。
在判断TEXT类型节点的时候,不同浏览器有不同的表现,例如IE忽略元素之间的空白,而FireFox认为元素间的空白都是TEXT节点。
可以使用node.nodeType属性来检测节点类型,返回的为数字,也可以用Node.ELEMENT_NODE等常量来比较(IE9以前的IE浏览器不支持,Node未定义)。

可以使用node.getAttribute()、node.setAttribute()以及node.removeAttribute()来处理节点特性。
IE使用setAttribute方法时,有时候变更并不会总是正确的反应出来,
例如对 niframe.setAttribute("frameborder",0) 时,IE无效,需要 niframe.frameBorder=0 才有效。

访问指定节点可以使用getElementsByTagName(),当参数是星号的时候,IE6之前版本并不返回所有元素,需要使用document.all来代替。
访问指定节点可以使用getElementsByName(),IE6下存在一个BUG,会返回id也等于给定名称的元素,另外IE6仅仅检查<input/>和<img/>元素。
访问指定节点可以使用getElementById(),IE6下存在一个BUG,如果给定的id匹配某个元素的name特性,IE6会返回这个元素。
实际中可能希望实现通过类名来选择节点,如下是一个兼容的实现方式(参考司徒正美document.getElementsByClassName的理想实现):

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body div{background: #eee;}
</style>
<script>
window.onload=function(){
var getElementsByClassName = function(searchClass, node, tag) {
node = node || document;
tag = tag || "*";
if(document.getElementsByClassName){
var nodes = node.getElementsByClassName(searchClass),
result = [];
for(var i = 0; node = nodes[i++];){
if(tag == "*" || node.tagName === tag.toUpperCase()){
result.push(node);
}
}
return result;
}
else{
var classes = searchClass.split(" "),
// IE6之前不支持getElementsByTagName的*参数.
elements = (tag === "*" && node.all)? node.all : node.getElementsByTagName(tag),
patterns = [],
result = [];
// 生成正则匹配.
var i = classes.length;
while(--i >= 0){
patterns.push(new RegExp("(^|\\s)" + classes[i] + "(\\s|$)"));
}
// 遍历元素.
var j = elements.length;
while(--j >= 0){
var current = elements[j];
var match = false;
for(var k=0, kl=patterns.length; k<kl; k++){
match = patterns[k].test(current.className);
if(!match){
break;
}
}
if(match){
result.push(current);
}
}
return result;
}
}
alert(getElementsByClassName("a b"));
};
</script>
<body>
<div class="a">a</div>
<div class="a b">a b</div>
<div class="a b c">a b c</div>
</body>
</html>

getElementByClassName

创建节点可以通过createElement()来创建节点,通过createTextNode()来创建文本节点,
通过appendChild()添加到DOM树,也可以使用removeChild()、replaceChild()、insertBefore()进行操作。

使用createDocumentFragment()可以创建一个文档片段,最后再插入文档
(注意使用文档片段的方式创建script节点并添加到DOM树,可能导致script中引用文件的脚本不执行)。

Table有些DOM API能够协助建立表格,例如createTHead()、createTFoot()等等。

DOM Level2中可以使用createNodeIterator以及createTreeWalker来遍历DOM树(IE系列从IE9开始支持)(待完成)。

DOM动态样式

使用元素的style属性可以获取文档元素的CSS2Properties对象,
通过这种方式只能设置元素的内联样式,读取这些属性将返回元素的style属性所设置的CSS属性值。
JavaScript中的CSS命名时需要将连字符删除,并将之后的字母大写;另外某些关键词需要修改:
CSS的float样式在JavaScript中为n.style.cssFloat。,IE678为n.style.styleFloat

如果希望查询应用到给定元素的完整样式集合的方法,可以使用W3C的window.getComputedStyle()方法,该方法的第二个参数是任何CSS伪元素,
IE非标准化的支持API为currentStyle,因此跨平台的方式可能如下:

 var n=document.body;
if(n.currentStyle){
alert(n.currentStyle.marginTop);
}
else{
alert(window.getComputedStyle(n,null).marginTop);
}
alert(n.style.marginTop);/* 这里获取的并不一定为最终样式 */

获取body元素的marginTop

使用元素的className属性可以修改文档元素关联的CSS类。

DOM2标准为<link>和<style>标签都定义了一个disabled属性,如果设置为true,则相关的样式表就会被关闭。

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css" id="ss0">
body div{background: #eee;}
</style>
<script>
window.onload=function(){
setTimeout(f,1000);
function f(){
document.getElementById("ss0").disabled=true;
}
} </script>
<body>
<div>背景色1秒后消失</div>
</body>
</html>

演示:1秒后关闭一个样式表的显示

动态添加CSS样式表可以通过W3C的insertRule()方法;IE兼容的方法为addRule(),如下为一个动态添加CSS样式的例子:

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css" id="ss0">
</style>
<script>
window.onload=function(){
var hd=document.getElementsByTagName("head")[0];
var stn=document.createElement("style");
stn.setAttribute("type","text/css");
hd.appendChild(stn);
var st=stn.styleSheet?stn.styleSheet:stn.sheet;
var ls=st.cssRules?st.cssRules.length:st.rules.length;
if(st.insertRule){/* W3C API IE9开始支持 */
st.insertRule("body{background:blue;}",ls);
}
else if(st.addRule){
st.addRule("body","background:green;",ls)
}
} </script>
<body>
<div>JS方式添加CSS样式规则</div>
</body>
</html>

动态添加CSS来修改背景颜色

DOM事件处理

DOM1中并未定义事件,DOM2中才定义了一小部分子集,完整的事件在DOM3中定义。

冒泡型事件流基本思路是:事件按照最特定的事件目标到最不特定的事件目标的顺序触发。
IE6中事件可以从div->body->html->document流动;Firefox中事件可以从div->body->html->document->window流动。

DOM同时支持冒泡型事件和捕获型事件,捕获型事件先发生,
例如一个div点击的事件流可能是:window->document->body->div->body->document->window。
DOM事件模型中,文本节点也可以触发事件(在IE中不会)。

通过HTML属性或者JavaScript属性设置的事件,会根据它的返回值说明事件的后续影响,
例如onsubmit事件返回false可以阻止真正的表单提交,其他还有onclick等。

通过HTML属性定义的事件句柄,它所执行的作用域链会延伸到到触发的对象的冒泡对象上。

 n.onclick=function(){
with(document){
with(this.form){
with(this){
alert(123);
}
}
}
};

一个表单中input元素的onclick="alert(123)"的作用域链等价实现

最通常的事件处理函数绑定:

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
*{margin: 0px;padding: 0px;}
#c{margin:0 100px;background: yellow;}
</style>
<script type="text/javascript">
//<![CDATA[
window.onload=function(){
var n=document.getElementById("c");
n.onclick=function(){
alert("hello world");
}
}
//]]>
</script>
</head>
<body>
<div id="c">
单击这里
</div>
</body>
</html>

最通常的事件处理函数绑定

IE中使用attachEvent()和detachEvent()方法来添加删除事件处理函数,
事件可以被重复添加,不支持捕获,作用域为"window",添加①②③后的执行顺序为③②①。
DOM中使用addEventListener()和removeEventListener()方法来移除事件处理函数(IE9开始支持),
使用该方法添加的事件不会重复添加,支持捕获,作用域为"this",添加①②③后的执行顺序为①②③。
addEventLister()和removeEventLister()方法存在三个参数,第一个参数说明事件名,不需要"on"开头,最后一个参数为true时用于捕获阶段。

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
*{margin: 0px;padding: 0px;}
#c{margin:0 100px;background: yellow;}
</style>
<script type="text/javascript">
//<![CDATA[
var handler={};
if(document.addEventListener){
handler.add=function(n,tp,f){
n.addEventListener(tp,f,false);
};
handler.remove=function(n,tp,f){
n.removeEventListener(tp,f,false);
};
}
else if(document.attachEvent){
handler.add=function(n,tp,f){
// 重复注册事件句柄将被忽略.同DOM的addEventListener逻辑相同.
if(handler._find(n,tp,f) != -1){
return;
}
var wraphandler=function(e){
if(!e){
e=window.event;
}
// 对IE的事件对象进行兼容处理.
var event={
_event:e,
type:e.type,
target:e.srcElement,
currentTarget:n,
relatedTarget:e.fromElement?e.fromElement:e.toElement, clientX:e.clientX,
clientY:e.clientY,
screenX:e.screenX,
screenY:e.screenY,
altKey:e.altKey,
ctrlKey:e.ctrlKey,
shiftKey:e.shiftKey,
charCode:e.keyCode,
stopPropagation:function(){this._event.cancelBubble=true;},
preventDefault:function(){this._event.returnValue=false;}
}
// 作用域和事件对象参数调整后触发.
f.call(n,event);
};
// 这里使用IE原生事件添加方法进行添加.
n.attachEvent("on"+tp,wraphandler);
var h={
n:n,
tp:tp,
f:f,
wraphandler:wraphandler
};
// 获取节点所在的文档.
var d=n.document||n;
// 获取节点所在的文档的窗口.
var w=d.parentWindow;
// 唯一句柄.
var id=handler._uid();
if(!w._allhandlers) w._allhandlers={};
if(!n._handlers) n._handlers=[];
// 这里会记录所有事件相关的对象.
w._allhandlers[id]=h;
// 这里会在节点上记录事件相关对象的句柄.
n._handlers.push(id);
// onunload的时候删除所有事件句柄,防止IE的内存泄漏.
if(!w._onunloadHandlerRegistered){
w._onunloadHandlerRegistered = true;
w.attachEvent("onunload", handler._removeallhandler);
}
};
// 删除事件对象.
handler.remove=function(n,tp,f){
var i=handler._find(n,tp,f);
if(i==-1)return;
var d=n.document||n;
var w=d.parentWindow;
var handlerid=n._handlers[i];
var h=w._allhandlers[handlerid];
n.detachEvent("on"+tp,h.wraphandler);
n._handlers.splice(i,1);
delete w._allhandlers[handlerid];
}
// 查找.
handler._find=function(n,tp,f){
var handlers=n._handlers;
if(!handlers) return -1;
var d=n.document||n;
var w=d.parentWindow;
for(var i=handlers.length-1;i>=0;i--){
var handlerid=handlers[i];
var h=w._allhandlers[handlerid];
if(h.tp==tp&&h.f==f){
return i;
}
}
return -1;
};
// 遍历删除关联的所有事件对象.
handler._removeallhandler=function(){
var w=this;
for(var id in w._allhandlers){
var h=w._allhandlers[id];
h.n.detachEvent("on"+h.tp,h.wraphandler);
delete w._allhandlers[id];
}
};
// UID产生器
handler._counter=0;
handler._uid=function(){return "h"+handler._counter++;}
}
window.onload=function(){
var n=document.getElementById("c");
handler.add(n,"click",function(e){
alert("hello world1"+e.target);
});
handler.add(n,"click",function(e){
alert("hello world2"+e.target);
});
handler.add(n,"click",function(e){
alert("hello world3"+e.target);
});
}
//]]>
</script>
</head>
<body>
<div id="c">
单击这里
</div>
</body>
</html> 兼容实现事件的添加删除,这里未考虑attachEvent导致的执行顺序的问题

兼容实现事件的添加删除,这里未考虑attachEvent导致的执行顺序的问题

如上实现的方式会发现在attachEvent和addEventListener触发添加的事件的顺序是不同的,因此可以换一种思路实现:
对于只支持attachEvent方法的环境,可以通过节点的一个对象来记录所关联的事件类型,
每个事件类型只通过attachEvent关联一个方法,并维护一个事件数组,之后添加的同类型事件直接添加到事件数组中,遍历执行。

DOM事件对象

在IE中事件对象是window对象的一个属性event,在DOM中,事件对象是事件处理函数的第一个参数。

IE和DOM中相同的事件对象属性:

属性 描述 更多说明
type 事件名称,返回例如"click"或"mouseover"  
keyCode 表示按下按钮的数字代号(IE对于keydown/keyup事件)  
shiftKey true表示按下shift键  
altKey true表示按下alt键  
ctrlKey true表示按下ctrl键  
clientX 事件发生时鼠标在客户端区域的x坐标  IE外浏览器需要window.pageXOffset
clientY 事件发生时鼠标在客户端区域的y坐标  IE外浏览器需要window.pageYOffset 
screenX 相对于整个计算机屏幕的鼠标x坐标  
screenY 相对于整个计算机屏幕的鼠标y坐标  
button 对于特定的鼠标事件,表示按下的鼠标按钮,用数字表示  W3C为0左1中2右,IE为142

IE和DOM中不同的事件对象属性以及方法:

描述 IE DOM 更多说明
获取目标 e.srcElement e.target 可能与currentTarget不同
获取字符代码 e.keyCode e.charCode 然后可以通过String.fromCharCode()方法获取实际字符,另外可以通过e.isChar属性判断按下的键是否包含字符
阻止事件默认行为 e.returnValue=false; e.preventDefault(); 相当于DOM0事件模型中返回false
停止事件冒泡 e.cancelBubble=true; e.stopPropagation();  
鼠标指针来自去向 e.fromElement、e.toElement e.relatedTarget e.relatedTarget?e.relatedTarget:(e.type=="mouseover"?e.fromElement:e.toElement);

DOM中使用e.currentTarget属性引用注册了事件的对象。
IE兼容实现中在涉及拖放等鼠标事件的时候,需要使用setCapture()和releaseCapture()方法来捕获鼠标事件,
当setCapture()调用的元素的鼠标事件被中断,则会触发onlosecapture事件。

DOM2中还可以把对象注册为事件句柄(IE9开始支持),只需要给对象定义一个handleEvent()方法并传递,模拟实现如下:

 function regObjEventHandler(n,tp,listener,captures){
n.addEventListener(tp,function(event){listener.handleEvent(event);},captures);
}

为对象注册事件的模拟

DOM事件细节

keypress事件在W3C中,按下不能打印的功能按键(如Enter、F1)也会触发,但IE兼容实现中只有当按键有可打印字符或者一个控制字符时才触发。
通用规则是keydown事件对于功能按键,例如Backspace等来说是最有用的,keypress事件对于可打印按键来说是最有用的。
IE兼容实现中,Alt按键组合被认为是无法打印,不会产生keypress事件。
通常需要实现一个Keymap键盘映射,在HTML元素上配置Keymap;在元素上配置键盘映射则注册onkeydown和onkeypress事件。

Cookie存储

Cookie的属性expires指定cookie的生存期,为一个GMT格式的日期;也可以通过新的属性max-age取代,max-age使用秒来设置cookie的生存期,max-age设置为0来删除cookie。
Cookie的属性path指定了于cookie关联的网页,通常设置为"/"。
Cookie的属性domain指定了cookie关联的域名,对于存在子域的网站来说这个属性很有必要。
Cookie的属性secure指定了cookie是否只在HTTPS下传输,为一个布尔值。

创建一个cookie可能的样子如下(cookie值不能含有分号、逗号或空白符):

 var dt=new Date();
dt.setFullYear(dt.getFullYear()+1);
document.cookie="version="+encodeURIComponent(document.lastModified)+"; expires="+dt.toGMTString();

简单创建一个Cookie

 function getCookie(nm){
var sreg="(?:^|;[ ]*)"+nm+"=([^;]*);?";
var ore=new RegExp(sreg);
if(ore.test(document.cookie)){
return decodeURIComponent(RegExp.$1);
}
else{
return null;
}
}

读取一个Cookie

隐藏框架通信

父窗口可以对iframe的URL进行读写,firame可以对父窗口的URL进行写(跨域则不可读取),通过改变URL的hash部分来实现数据传递。
子iframe可以通过setInterval()来监听是否有数据,也可以通过父窗口修改iframe的大小,来触发iframe的onresize事件来通知;
跨域情况下,子iframe将想要传递的数据写入父窗口的URL的hash后,父窗口通过setInterval()来监听是否有数据传输进来。
另外window.onhashchange事件是很不错的监听选择,但IE67不支持。
对于URL长度的限制问题,可以通过分段的方式解决。

AJAX异步通信

XMLHttpRequest对象的创建,需要兼容IE的ActiveXObject:

 var ctXHR=function(){
try{return new XMLHttpRequest();}catch(e){}
try{return new ActiveXObject("Msxml2.XMLHTTP.6.0");}catch(e){}
try{return new ActiveXObject("Msxml2.XMLHTTP.3.0");}catch(e){}
return null;
};

创建XHR对象

创建好XHR对象后通过xhr.open("GET",uri,true)方法来打开发送请求。
之后可以通过xhr.setRequestHeader("If-Modified-Since","12/21/2011")等来设置请求头;另外可以通过xhr.getResponseHeader来获取接收后的头信息。
最后通过xhr.send(parm)来发送AJAX请求。
可以通过xhr.onreadystatechange=function(){}来异步触发响应。

XMLHttpRequest对象的属性:

属性 说明
onreadystatechange AJAX状态改变时候会触发该回调函数
readyState 0表示未调用open()方法,1表示正在加载未调用send()方法,2表示已经调用send()方法但未收到响应,3表示接收到部分响应,4表示接收完毕或者中断
status 服务端HTTP码,例如200
statusText HTTP状态码响应文本,例如404 not found
responseText 服务端的响应,作为一个字符串
responseXML 服务端的响应,表示为XML,服务器必须使用text/xml来标识

对于POST方法,发送的时候可以设置头信息为:

 xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

POST方式设置编码方式

对于AJAX的取消,可以通过xhr.abort()方法来取消,若超时之前已经AJAX执行完毕,要记得clearTimeout(timer)来取消定时器。

上一篇:Ajax 密码验证


下一篇:cocos2dx进阶学习之CCBI文件