一些概念
DOM(文档对象模型)是HTML和XML的应用程序接口(API)。
DOM Level1规划文档结构;DOM Level2扩展了对鼠标和用户界面事件等的支持;DOM Level3支持了XML1.0特性,包括XPath等。
还有其他语言发布了自己的DOM标准:可缩放矢量图形(SVG),同步多媒体集成语言(SMIL)等。
DOM描述了处理网页内容的方法和接口;BOM描述了与浏览器进行交互的方法和接口。
ECMAScript数据类型
5种原始类型:Undefined、Null、Boolean、Number、String;
对应的使用typeof运算符返回:undefined、object、boolean、number、string。
undefined值实际上是从null派生而来,因此null==undefined。
所有ECMAScript数值必须在Number.MAX_VALUE和Number.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类具有的属性:constructor、prototype;
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类具有的方法,concat和slice方法,返回的为新的数组,不改变原始数组,其中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);
继承
使用call和apply方法可以实现对象冒充。
可以通过一个中间对象来实现继承:
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对象包含protocol、host、port、pathname、search、hash以及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对象还定义了cookie、domain、lastModified、location、referrer、title、URL等属性。
document对象还定义了一些文档集合,如anchors、forms、images、links等文档集合,
如果通过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)来取消定时器。