1、prototype属性一些注意事项
实例对象和prototype没有prototype属性,这一点一定要注意,prototype属性是构造函数才有的属性;实例对象和prototype对象都只有一个_prototype_属性
function A()
{
}
var a=new A();
console.log(a.hasOwnProperty('prototype'));
//__prototype_存在于实例对象和原型之间,而实例自身没有prototype属性!
console.log(a.prototype===A.prototype);
//a对象没有prototype属性但是有_prototype_属性,所以a.prototype为undefined
//而A.prototype是有值的!
console.log(A.prototype);
//该对象有constructor和_prototype_属性
函数重载与函数引用:
而且函数没有重载的概念,在创建第二个函数的时候实际上覆盖了引用第一个函数的变量!而且我们要记住,函数的名字仅仅是一个包含指针的变量而已,因此即使是在不同的环境中执行,全局的sayColor和o.sayColor仍然是同一个函数,如下例:
window.color="red";
var o={color:"blue"};
function sayColor()
{
console.log(this.color);
}
sayColor();
//全局执行
o.sayColor=sayColor;//函数引用是指针
o.sayColor();
//上下文是o
console.log(sayColor===o.sayColor);
//是同一个函数
arguments的callee和caller属性:
caller:除了Opera早期版本不支持以外,其它浏览器都支持该属性,该属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数那么返回null
function test()
{
console.log(test.caller);
//为了实现松散耦合,我们可以用arguments.callee.caller
}
test();
//全局调用caller是null
在严格模式下,访问arguments.callee会导致错误,ECMAScript5还定义了arguments.caller但在严格模式下也会导致错误,而在非严格模式下始终是undefined;严格模式下不能为caller赋值,否则也会导致错误!
函数属性和方法:
每一个函数都有两个属性,length表示函数希望接受的命名参数的个数;prototype属性,该属性无法通过for..in遍历出来
每一个函数都有两个非继承而来的方法:call方法和apply方法。具体用法可以参考Js apply方法详解
2、constructor属性是prototype对象才有的属性
如果是实例对象,那么他的constructor是从他的prototype中继承而来的,而且constructor就是他的构造函数
function Person(){}
var person=new Person();
var person1=new Person();
console.log(person.constructor);
//打印function Person(){},也就是实例对象的construtor是其构造函数!
console.log(person.hasOwnProperty('constructor'));
//打印false,constructor属性在prototype中
console.log(person.constructor===person1.constructor);
//打印true,都是同一个函数对象
如果是函数,那么他的constructor只能从Function.prototype中继承
function Person(){}
console.log(Person.constructor);
//对于函数名来说,他是通过new Function()来构造的,所以他的我们先找Function.prototype
//然后从Function.prototype中寻找其constructor为function Function(){}!
console.log(Function.prototype.hasOwnProperty('constructor'));
//只有prototype才有constructor属性,打印true!
console.log(Person instanceof Function);
//打印true,因为构造函数function Function(){}在Person的原型链上面!
instanceof查找关系的时候关注的是_prototype_属性,而不是prototype属性,如果A instanceof B就是判断A是否是B对象的实例,也就是说B是否在A对象的原型链上面,但是对于Function来说有一个特例,他会得到Function instanceof Function为true,但是其它不可能!
function Person(){}
console.log(Person instanceof Person);
//函数Person没有一个_prototype_属性指向Person.prototype!
console.log(Function instanceof Function);
//对于function Function(){}函数来说,他有一个_prototype_
//属性指向Function.prototype,这是特例!此处打印true!
通过该图,我们可以清楚的看到,在查找Object instanceof Function时候,Object这个函数通过_prototype_属性可以找到Function.prototype;而Function instanceof Object可以看到,Function函数通过_prototype_查找到Object.prototype,所以两者都返回true;同时有一个特别的地方是,对于Function函数来说,通过_prototype_可以查找到Function.prototype,所以也是返回true!(您也可以研读"Object instanceof Function 还是 Function instance of Object,是真是假,一一道来")
3、如何获取元素的原型对象以及如何判断是否是实例的原型对象
function Person(){}
var person1=new Person();
//在原型判断的时候要记住是通过_prototype_而不是prototype属性完成的
console.log(Person.prototype.isPrototypeOf(person1));
//打印true
console.log(person1.prototype);
//回忆一下上面的内容,实例和原型对象只有_prototype_没有prototype
//打印undefined
console.log(Object.getPrototypeOf(person1));
//打印原型对象
通过isPrototypeOf判断,通过Object.getPrototypeOf获取原型对象
4、通过delete来解决实例属性对原型属性的屏蔽
function Person(){}
Person.prototype.name="ql";
var person1=new Person();
person1.name="fkl";
console.log(person1.name);
//实例属性屏蔽了原型属性,打印fkl
delete person1.name;
//恢复到原型的访问
console.log(person1.name);
//打印ql
Object.getOwnPropertyDescriptor只能用于实例对象,要想获得原型属性描述符,必须直接在原型对象上调用给方法(参考"
JS中的克隆与数据属性和访问器属性")
5、窗口关系和框架
top:始终指向最外层的框架也就是浏览器窗口,使用它可以确保在一个框架中正确的访问另外一个框架,因为对于在一个框架中编写的任何带来来说,其中的window对象都是那个框架的特定实例而非最高层的框架
parent:指向当前框架的直接上层框架,也就是上一层的frame,在某些情况下parent可能等于top,但是在没有框架下parent===top===window!注意:除非最高处窗口是通过window.open打开的,否则window对象的name属性不包含任何值
self:始终指向window,实际上window===self,引入self的目地是为了与top/parent对象对应起来,他不包含格外的值
注意:存在框架的情况下,浏览器存在多个Gloabl对象,每个框架中定义的全局变量会自动成为框架中window的属性,由于每个window对象都包含原生的类型的构造函数,因此每一个框架都有一套自己的构造函数,这些构造函数一一对应,但是并不相等,因此如top.Object!==top.frames[0].Object,这回影响到跨框架传递对象使用instanceof!同时,如果用了框架,那么不再需要body元素了!
在这里我们学习一下防止自己的页面被frame的思路:
try{
top.location.href;
//跨域的时候我的页面不能访问顶层框架的hostname,否则报错!
if (top.location.hostname != window.location.hostname) {
top.location.href =window.location.href;
}
}
//表示跨域了(跨域时候不能访问hostname/href等所有的信息),
//这时候对top进行URL重定向!
catch(e){
top.location.href = window.location.href;
}
具体信息可以参见"
防止网页被嵌套到框架中的方法"一文!
6、窗口位置
var leftpos=(typeof window.screenLeft=='number')?window.screenLeft:window.screenX;
var topPos=(typeof window.screenTop=='number'?window.screenTop:window.screenY)
//FF使用的screenX,因为FireFox中有一个"x"!表示获取窗口相对于屏幕左边和上面的位置!
//Opera虽然也支持screenX和screenY但是和screenLeft/screenTop不对应不建议使用!
7、窗口大小和可视区域大小
//我们虽然无法获取到浏览器窗口本身的大小却可以取得页面视口的大小!
var pageWidth=window.innerWidth,
pageHeight=window.innerHeight;
if ( typeof pageWidth !="number")
if ( document.compatMode== "CSSICompat"){
pageWidth=document.documentElement.clientWidth;
pageHeight=document.documentElement.clientHeight;
} else{
pageWidth=document.body.clientWidth;
pageHeight=document.body.clientHeight;
}
}
8、导航和弹出窗口
<input type="button" value='Click'/>
<input type="button" id="close" value='close'/>
window.open方法返回的是对新打开的窗口的引用
var win=null;
$('input')[0].onclick=function()
{
win= window.open('http://www.baidu.com','_blank','height=400,width=400,top=10,left=10,resizable=yes');
//第二个参数可以是_self,_parent,_top,_blank等如果是
// window.open('www.baidu.com','topFrame');那么相当于<a href="www.baidu.com" target="topFrame"/>
//如果有一个名子为topFrame的框架或者窗口那么就会在该框架和窗口加载URL,否则创建新窗口命名为topFrame!
win.resizeTo(1000,1000);
//改变大小
win.moveTo(100,100);
//移动位置
console.log(win.opener==window);
//指向调用window.open的方法的窗口和框架
win.opener=null;
//将opener设置为null就是告诉浏览器新创建的标签页不需要和打开他的标签页进行通信,因此可以在独立的进程中运行
//标签页之间的联系一旦切断就无法恢复!
}
$("#close")[0].onclick=function()
{
win.close();
//关闭通过window.open打开的窗口,关闭窗口以后窗口的引用还在,但是除了检测closed属性以外没有任何用处
console.log(win.closed);
//打印true
}
弹出窗口屏蔽程序
var blocked=false;
try
{
var win=window.open('http://localhost:8080',"_blank");
//如果是浏览器内置的屏蔽程序阻止的弹出框,那么window.open很可能返回null
if(win==null)
blocked=true;
}
//如果是浏览器扩展或者其它程序阻止的弹出窗口那么抛出错误
catch (e)
{
blocked=true;
}
if(blocked)
alert('blocked');
检测弹出窗口是否被屏蔽只是一方面,他并不会阻止浏览器显示与被屏蔽的弹出窗口有关的消息!
9、对话框
if(confirm('Are you sure?'))
{
//true表示单机了OK,false表示单机了cancel或者单机了右上角的X按钮!
alert("You are sure!");
}else{
alert('cancelled');
}
下面是弹出对话框
var result=prompt('What is your name?',"qinliang");
//第一个参数是提示文本,第二个参数是默认值,可以是空字符串
if(result!==null)
{
//如果单机了ok那么返回文本输入域内容,单机了cancel或者没有单机OK而是
//通过其它方法关闭对话框那么就是null!
console.log('welcome'+result);
}
查找和打印窗口
window.print();
//打开打印窗口
window.find()
//查找窗口
这两个对话框是异步的,所以可以将控制权立刻交还给脚本,既然是异步那么Chrome的对话框计数器不会将他们计算在内,所以他们也不会受到用户禁用对话框后续显示的影响!这些对话框都是同步和模态的,也就是说显示这些对话框的时候代码会停止运行,而关掉对话框代码会恢复运行!
10、location对象的一些注意事项
使用location对象可以使用下面方式来修改浏览器的位置:(document.location===window.location)
(1)location.assign('http://www.baidu.com')此时立即打开URL并在浏览器历史记录中产生一条记录,如果是location.href或者window.location设置一个URL,也会以该值调用assign方法
(2)window.location/location.href="http://www.baidu.com"这个与显示调用assign方法是一样的效果
(3)修改location的其它任何属性都会以新的URL重新加载页面,hash除外
注意:通过上面任何一种方式修改URL之后,浏览器的历史记录就会生成一条新的记录,因此通过单机后退按钮可以回到前一个页面,如果要禁止这种行为可以用replace方法,该方法只是接受一个参数也就是要导航到的URL,结果虽然会导致浏览器的位置改变,但是不会在历史记录中产生历史记录
location.replace('http://www.baidu.com');//后退按钮失效
(4)reload方法是重新加载当前页面,如果reload时候没有传入参数那么就会以最有效的方法加载页面,也就是说如果页面从上次请求以来没有修改那么就会从浏览器缓存中重新加载,如果要强制从服务器加载就需要传入参数true
location.reload(true);
//从服务器重新加载
location.reload();
//有可能从缓存中加载
位于reload之后的代码可能会也可能不会执行,这要取决于网络延迟或系统资源等因此,因此最好将reload放在代码最后一行!可以参见
图1,
图2
11、检测插件
plugins集合有一个refresh方法用于刷新plugins集合以反映最新安装的插件,这个方法接受一个参数表示是否应该重新加载页面的一个布尔值,如果将该值设置为true则会重新加载包含插件的所有的页面,否则只更新plugins集合,不重新加载页面
function hasPlugin(name)
{
name=name.toLowerCase();
//name是插件名称description是描述,filename是文件名
//length是处理的MIME类型数量!
for(var i=0;i<navigator.plugins.length;i++)
{
if(navigator.plugins[i].name.toLowerCase().indexOf(name)>-1)
{
return true;
}
}
return false;
}
//IE中检测插件的唯一方式就是使用专有的ActiveXObject类型,并尝试创建一个
//特地插件的实例,IE是以COM实现插件的,而COM对象使用唯一标识符来标志,因此
//要检测特定的插件就必须知道其COM标识符!
function hasIEPlugin(name)
{
try
{
new ActiveXObject(name);
return true;
}//try..catch是因为创建位置的COM会产生错误!
catch (e)
{
return false;
}
}
function hasFlash()
{
var result=hasPlugin('Flash');
if(!result)
{
result=hasIEPlugin('ShockwaveFlash.ShockwaveFlash')
}
return result;
}
console.log(hasFlash());//打印true!
12、注册处理程序
//FF4之前只允许使用registerContentHandler方法中使用三个MIME类型,也就是application/rss+xml,application/atom+xml
//application/vnd.mozilla.maybe.feed这三个MIME作用都一样即为RSS和ATOM注册事件处理程序
navigator.registerContentHandler("application/rss+xml","http://localhost:8080?feed=%s",'some reader');
//第一个参数为MIME类型,第二个参数为可以处理该MIME类型的页面的URL以及应用程序的名称!%s为浏览器自动插入
navigator.registerProtocolHandler('mailto','http://www.somemailclient.com?cmd=%s','some mail client');
//这两个方法是在HTML5中定义的,可以让一个站点指明他可以处理特定类型的信息,随着RSS阅读器和在线电子邮件程序
//的兴起,注册处理程序就为像使用桌面应用程序一样默认使用这些在线应用程序提供了一种方式!
FF2虽然实现了registerProtocolHandler,但是该方法还不能用,FF3完整实现了该方法!
13、history对象
用history.go()方法实现页面的后退或者前进
history.go(1);
//前进一页
history.go(-1);
//后退
history.go('baidu.com');
//传入字符串表示跳转到历史记录中包含该字符串的第一个位置
//可能是后退,也可能是前进
history.back();
//后退
history.forward();
//前进
if(history.length===0)
{
//length保存历史记录的数量,包括所有历史记录(前进和后退)
//如果创建自定义的"后退"/"前进"按钮的时候就需要用length!
}
14、能力检测时候需要注意的地方
function hasCreateElement()
{
return typeof document.createElement=='function';
//IE8之前的createElement返回的为object,因为IE8之前的
//是通过COM而不是JScript实现的!(getElementById等也是一样的!)
}
alert(hasCreateElement());
IE8之前都是通过COM实现的而不是JSCript实现的,所以typeof返回的都是object!
function isHostMethod(object,property)
{
var t=typeof object[property];
return t=='function'||(!!(t=='object'&&object[property]))||t=='unknown';
}
var xhr=new ActiveXObject('Microsoft.XMLHttp');
//ActiveX对象和其它对象的行为差异很大,如果不实用typeof检测某个
//属性会导致错误!
alert(typeof xhr.open);
//IE用typeof检测会导致返回'unknown'!
alert(isHostMethod(xhr,'open'));
//打印true!
if(xhr.open)
{
//导致错误
alert('open exist');
}
对于ActiveXObject来说,IE直接检测他的方法会导致报错,而用typeof会返回'unknown',所以要用到上面这种准确的检测机制!
var isFF=!!(navigator.vendor && navigator.vendorSub);
//错误,SF也实现了同样的属性
var isIE=!!(document.all && document.uniqueID);
//错误,相当于假设其它浏览器都不会这么实现,同时IE中也会一直保存!
不要认为支持了特定的属性就是特定的浏览器了!
var hasDontEnumQuirk=function()
{
var o={toString:function(){}};
for(var prop in o)
{
if(prop=='toString')
{
return false;
//如果返回false表示for..in被迭代出来,那么就表示实例属性出现在
//for..in中了,表示没有怪癖!
}
}
return true;
}();
console.log(hasDontEnumQuirk);
//打印false表示没有这个怪癖!
怪癖检测:如果实例中有一个属性和[[Enumerable]]为false的属性名相同,那么该属性不出现在for..in中(检测分为:能力检测,怪癖检测,用户代理检测),在服务器端通过检测用户代理字符串来确定用户使用的浏览器是一种常用而广泛接受的做法,但是在客户端,用户代理检测一般被看作是一种万不得已采用的做法,优先级在能力检测和怪癖检测之后!
我们通过下面形式来获取浏览器的引擎:
var client=function()
{
var engine={
ie:0,
gecko:0,
webkit:0,
khtml:0,
opera:0,
ver:null//保存具体的浏览器版本
};
return {
engine:engine
};
}();
var engine=client.engine;
var ua=navigator.userAgent;
//第一步,我们识别Opera,因为其用户代理字符串都不会将自己标记为Opera
if(window.opera)
{
engine.ver=window.opera.version();
//获取表示浏览器版本的字符串,而这也是确定opera版本号的最佳方法
//要确定更早版本的Opera可以用用户代理字符串,因为那时候的版本还
//不支持隐藏身份,不过在Opera在2007年已经是9.5,所以不太可能在7.6之前版本
engine.opera=parseFloat(engine.ver);
}
//第二步检测webkit,因为webkit用户代理字符串包含gecko进而khtml,但是AppleWebkit
//字符串是webkit浏览器独一无二的
//格式:"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"
else if(/AppleWebKit\/(\S+)/.test(ua))
{
engine.ver=RegExp['$1'];
//正则表达式的静态属性!
engine.webkit=parseFloat(engine.ver);
}else if(/KHTML\/(\S+)/.test(ua)||/Konqueror\/([^;]+)/.test(ua))
{
//第三步检测KTHML,KHTML的版本号和webkit版本好在用户代理格式上差不多
//因此可以用类似的正则表达式,此外,由于Konqueror3.1以及更早版本不包含
//KTHML版本号,因此用Konqueror版本来代替([^;]表示不是分好的所有字符)
engine.ver=RegExp['$1'];
engine.khtml=parseFloat(engine.ver);
}else if(/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua))
{
//第四步检测Gecko,但是版本号出现在rv:后面!([^\)]+)表示非")"所有字符!
//格式为:"Mozilla/5.0 (Windows NT 6.1; rv:42.0) Gecko/20100101 Firefox/42.0"
//与SF和webkit一样,FF和Gecko版本号也不一定严格对应
engine.ver=RegExp['$1'];
engine.gecko=parseFloat(engine.ver);
}else(/MSIE ([^;]+)/.test(ua))
{
//格式为:"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
engine.ver=RegExp['$1'];
engine.ie=parseFloat(engine.ver);
}
对于SF和Chrome浏览器都是用了webkit内核,但是因为他们的JS引擎是不相同的,所以我们如果要确定到底是chrome浏览器还是SF那么需要用下面的代码:
//确定是Chrome还是SF(当确定了是webkit内核以后)
if(/Chrome\/(\S+)/.test(ua))
{
browser.ver=RegExp['$1'];
//Chrome版本号在Chrome后面
browser.chrome=parseFloat(browser.ver);
}else if(/Version\/(\S+)/.test(ua))
{
//SF的版本号在Version后面
browser.ver=RegExp['$1'];
browser.safari=parseFloat(browser.ver);
}else
{//对于SF3一下版本需要将webkit版本号近似的映射为SF版本号!
var sfversion=1;
if(engine.webkit<100)
{
sfversion=1;
}else if(engine.webkit<312)
{
sfversion=1.2;
}else if(engine.webkit<412)
{
sfversion=1.3;
}else
{
sfversion=2;
}
browser.safari=browser.ver=sfversion;
}
当我们确定了是Gecko引擎以后如果还需要确定是否是获取浏览器我们需要用如下代码:
if(/Firefox\/(\S+)/.test(ua))
{
browser.ver=RegExp['$1'];
browser.firefox=parseFloat(browser.ver);
}
当然只有在确定了引擎是Gecko时候才能这么做!如果还需要确定浏览器运行的平台,那么可以用navigator.platform来完成!
1、事件类型
UI事件:DOMActivate/load/unload/abort/error/select/resize/scroll等
load:根据DOM2级规范,应该在document而非window上面触发load事件,但是所有浏览器都在window上面实现了这个事件以保证向后兼容;对于img的onload事件来说最重要的是在指定src之前先指定事件,而且只要指定了src就会开始下载图片不用等到添加到文档中;对于script元素也支持load事件,但是只有当script的src设置了同时也把该元素添加到文档中才会开始下载文件,所以说对于script来说指定src和事件处理程序的顺序就不重要了;IE/Opera还执行link的load事件,但是只有设置了href和添加到文档中才会开始下载文件!
unload:Opera/Chrome中任何情况都不会执行onunload,兼容性差;onunload,onbeforeunload刷新关闭时候用,但是后者在前面执行,而且可以阻止onload执行
window.onbeforeunload=function()
{
return "xxx";
}
在优快云中,用于保存草稿的用法就是用onbeforeunload事件,根据DOM2级规范,应该在body元素而不是window对象上面触发unload事件,不过所有浏览器都在window上面触发unload以确保向后兼容;这个事件在文档被完全卸载后触发,只要用户从一个页面切换到另外一个页面就会触发unload,通常用于清除引用避免内存泄漏!
resize:这个事件在window上面触发,因此可以通过JS或者body元素的onresize指定
window.onresize=function(e)
{
}
在兼容DOM的浏览器中传入的event有一个target属性,值为document,而IE8之前未提供。IE/SF/Chrome/Opera会在浏览器变化1像素就触发,然后随着便会不断触发,但是FF会在用户停止调整窗口大小才触发resize事件,因此不要在该事件中加入大量代码避免频繁执行
scroll:该事件在window上面触发,在混杂模式下和标准模式下可以检测滚动的距离
window.onscroll=function(e)
{
if(document.compatMode==='CSS1Compat')
{
console.log(document.documentElement.scrollTop);
}else
{
//混杂模式下用document.body
console.log(document.body.scrollTop);
}
}
焦点事件:blur/DOMFocusIn/DomFocusOut/focus/focusin/focusout
var isSupport=document.implementation.hasFeature('FocusEvent','3.0');
IE推出了focusin/focusout而Opera退出了DOMFocusIn/DOMFocusout以解决focus/blur冒泡问题
鼠标和滚轮事件:
mouseenter/mouseleave不冒泡,而且在移动到后代元素上面也不会触发;mouseout/mouseover和前者相反
var isSupported=document.implementation.hasFeature('MouseEvents','2.0');
//2.0不包括dbclick,mouseenter,mouseleave!
如果需要检测是否支持上面所有的事件可以用:
var isSupported=document.implementation.hasFeature('MouseEvent','3.0');
注意:这里是MouseEvent而不是MouseEvents
mouseover/mouseout鼠标事件中可以获取到相关元素
var event=e?e:window.event;
if(event.relatedTarget)
{
return event.relatedTarget;
//relatedTarget值对于mouseover/mouseout才包含值,对于其它事件是null
//IE8之前不支持relatedTarget
}else if(event.toElement)
{
//在mouseout事件中toElement保存了相关元素
return event.toElement;
}else if(event.fromElement)
{
//在mouseover事件中相关元素是fromElement
return event.fromElement;
}else
{
return null;
}
如果是鼠标按钮,我们可以通过button而映射到不同的鼠标按键
function getButton(event)
{
if(document.implementation.hasFeature('MouseEvents','2.0'))
{
return event.button;
//对于mousedown/mouseup事件来说在event对象上button属性
//表示按下或者释放的按钮,DOM的button属性可能是3个值
//0表示鼠标主键,1表示中间键,2表示鼠标次按钮。
//IE8之前也提供了button!
}else
{
switch(event.button)
{
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
//把0,1,3,5,7映射为左键
case 2:
case 6:
return 2;
//把2,6映射为右键
case 4:
return 1;
//把4映射为左键!
}
}
}
在onmouseup中,button表示释放的那个按钮,此外如果不是按下或者释放鼠标主键,Opera不会触发mouseup/mousedown!DOM2规范还规定了event有一个detail属性用于给出相关事件更多信息,对于鼠标事件来说表示在给定位置上发生了多少次单机,如果在一个位置上单机后移动到另外一个位置那么detail重置为0!IE还提供了altLeft/ctrlLeft/offsetX/offsetY/shiftLeft等,但是只有IE支持他们而且提供的信息也没有什么价值!
鼠标滚轮事件用于确定用户鼠标滚动的方向
//在IE8中mousewheel事件冒泡到document,在其它浏览器冒泡到
//window对象,wheelDelta属性如果向上滚动那么是120倍数,否则是
//-120倍数
document.onmousewheel=function(e)
{
var event=e?e:window.event;
//大多数情况下知道正负号就可以了,但是Opera<9.5是刚好相反的
var delta=(client.engine.opera && client.engine.opera<9.5)?
-event.wheelDelta:event.wheelDelta;
}
由于mousewheel事件非常流行而且所有浏览器都支持,因此HTML5加入了该事件,在火狐中用DOMMouseScroll代替
//在IE8中mousewheel事件冒泡到document,在其它浏览器冒泡到
//window对象,wheelDelta属性如果向上滚动那么是120倍数,否则是
//-120倍数
function getWheelDelta(event)
{
if(event.wheelDelta)
{
var delta=(client.engine.opera && client.engine.opera<9.5)?
-event.wheelDelta:event.wheelDelta;
return delta;
}else
{
//如果是FF那么用detail来获取数据,因为他获取到的是3的倍数,同时
//方向相反
return -event.detail*40;
}
}
document.onmousewheel=function(e)
{
var event=e?e:window.event;
getWheelDelta(e);
}
//FF使用了DOMMouseScroll来代替mousewheel
document.onDOMMouseScroll=function(e)
{
var event=e?e:window.event;
getWheelDelta(e);
}
键盘和文本事件:
keyDown:用户按下任意键的时候触发,而且按住不放重复触发
keypress:用户按下字符键才会触发,按住不放重复触发
keyup:用户释放键的时候触发,所有元素都支持上面三个事件,但是在文本框输入的时候常用
文本事件textInput对keypress的补充,用意在于将文本显示给用户之前更容易拦截文本,在文本插入到文本框之前会触发textInput!
keyPress和textInput的区别:
任何可以获取焦点的元素可以触发keypress,但是只有可编辑区才触发textInput;textInput要按下可以输入实际字符键的时候才触发,但keypress在按住那些影响文本显示的键也会触发;textInput主要考虑字符,于是event.data是用户输入的字符,同时IE对该event对象还提供了inputMethod属性表示文本输入到文本框的方式,如键盘,粘贴等
function getCharCode(event)
{
//IE9等提供了charcode只在keypress时候才包含值,而且值是ASCII
//此时keyCode是0也可能等于所按键的键码,IE8之前和Opea在keyCode
//中保存ASCII
if(typeof event.charCode=='number')
{
return event.charCode;
}else
{
return event.keyCode;
}
}
我们建议还是使用charCode,keyCode不使用key和char
function getKey(e)
{
//DOM3不再包含charCode而是两个新属性key和char
//其中key是字符串,如果是字符键结果就是字符,如果是功能键
//就是键名如Shift!而char在按下字符键的时候行为与key相同,按下
//非字符键是null
var event=e?e:window.event;
//不推荐使用key,keyIdentifier,char
var identifier=event.key||event.keyIdentifier;
if(identifier)
{
console.log(identifier);
}
}
复合事件:
var isSupport=document.implementation.hasFeature('CompositionEvent','3.0');
var textbox=$('input')[0];
//复合事件可以允许用户输入在物理键盘上找不到额字符,包括compositionStart
//compositionupdata,compositonEnd,每一个事件的event有一个data属性,如果是
//发生在访问的事件,那么包含正在编辑的文本,update时候表示正在插入的新字符
//end时候表示此次输入会话中插入的所有字符!
textbox.oncomositionend=function(e)
{
var e=e?e:window.event;
console.log(e.data);
}
变动事件:
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
假设我们要移除ul元素,首先触发ul上的DOMNodeRemoved,relatedNode是document.body;第二步是ul元素上触发DOMNodeRemovedFromDocument;第三步在身为ul子元素的每一个li元素和文本节点上触发DOMNodeRemovedFromDocument事件;第四步在document.body上触发DOMSubtreeModified事件,因为ul是body的直接子元素!
注意:DOMNodeRemoved事件的目标也就是target属性是被删除的节点,而event.relatedNode包含的是对父元素的引用,这个事件触发时候元素还没有从父节点删除,因此其parentNode仍然是父节点(和event.relatedNode相同),这个事件会冒泡,因而可以在DOM的任何层次上面处理他!但是DOMNodeRemovedFromDocument事件不会冒泡,所以事件要直接绑定到自身上才行,这个事件的目标是相应的子节点或者那个被移除的节点,除此之外event不包含任何信息!DOMSubtreeModified事件,这个事件的目标是被移除节点的父节点,此时的event不包含任何信息!
DOMNodeRemoved,DOMNodeInserted,DOMSubtreeModified都是冒泡的;DOMNodeInsertedIntoDocument,DOMNodeRemovedFromDocument不冒泡!
2、操作样式表
var supportStyleSheet=document.implementation.hasFeature('StyleSheets','2.0');
//判断浏览器是否支持DOM2级样式表
console.log(supportStyleSheet);
//CSSStyleSheet继承自styleSheet,后者作为一个基础接口来定义非CSS样式表
//从StyleSheet继承而来的属性包括disabled可以设置为true从而禁止样式表
href如果是link包含的那么就是URL,否则为null;media当前样式表支持的所有的媒体类型的集合,有一个length属性和item方法,如果集合是空列表表示样式表适用于所有的媒体,IE中media是一个反映link/style元素的media特征值的字符串!
<link rel='styleSheet' href="style.css" media="all">
IE中打印all,而chrome浏览器中打印一个集合
var sheet=document.styleSheets[0];
alert(sheet.media);
//IE中打印all
title表示ownerNode中的title属性值,也就是在link中设定的title属性值;type就是"type/css";而ownerNode就是指向当前样式表的节点的指针,样式表可以是HTML中通过link或者style引入的,如果样式表通过其它样式表通过@import引入那么就是null,IE不支持;parentStyleSheet在当前样式表是通过@import导入的情况下,这个属性是一个指向导入它的样式表的指针。注意:除了disabled以外都是只读的,但是CSSStyleSheet也支持下面的方法:
cssRulers:IE用rules代替
ownerRule:IE不支持,如果样式表通过@import导入这个属性表示一个指针指向表示导入的规则,否则为null
deleteRule:IE用removeRule
insertRule:IE用addRule
用document.styleSheets获取页面所有的样式表,包括内联的和外部引入的
var sheet=null;
for(var i=0,len=document.styleSheets.length;i<len;i++)
{
sheet=document.styleSheets[i];
//对于内联的style那么打印null!也可以通过item方法
console.log(sheet.href);
}
所有浏览器会包含style和rel特效被设置为stylesheet的link引入的样式表,IE/Opera也包含rel被设置为alternate stylesheet的link元素;除了使用document.stylesheet也可以直接通过style/link元素取得CSSStyleSheet对象,DOM规定了一个包含CSSStyleSheet对象的属性叫做sheet,IE用styleSheet代替:
function getStyleSheet(elem)
{
return elem.sheet||elem.styleSheet;
//IE用styleSheet
}
var link=document.getElementsByTagName('link')[0];
//取得第一个link引入的样式表
var sheet=getStyleSheet(link);
console.log(sheet===document.styleSheets[0]);
//打印true
通过该方法和通过document.styleSheets获取到的对象是相同的!
我们通过上面的提供的CSSStyleSheet提供的方法来创建和移除样式
function insertRule(sheet,selectorText,cssText,position)
{
if(sheet.insertRule)
{
//其它浏览器第一个参数是样式文本,第二个参数是插入的位置!
sheet.insertRule(selectorText+"{"+cssText+"}",position);
}else if(sheet.addRule)
{
//IE用addRule,第一个参数是选择器名称,第二个是CS样式
//第三个表示插入规则的位置,最多可以通过addRule添加4095样式
sheet.addRule(selectorText,cssText,position);
}
}
IE用addRule来添加元素,而其它浏览器用insertRule来完成!如果要添加的规则很多建议用动态加载样式表的技术
function deleteRule(sheet,index)
{
if(sheet.deleteRule)
{
sheet.deleteRule(index);
//IE用removeRule/addRule
}else if(sheet.removeRule)
{
sheet.removeRule(index);
}
}
我们操作CSS规则
var sheet=document.styleSheets[1];
//只是获取样式表,还要通过cssRules/rules获取规则!
var rules=sheet.cssRules||sheet.rules;
//IE用rules属性取得规则集合
var rule=rules[0];
console.log(rule.selectorText);
//获取".box"
console.log(rule.style.cssText);
//打印"width: 100px; height: 100px; background-color: blue;"
cssText对于IE始终会大写;style返回CSSStyleDeclaration,我们来对比一下cssText和style.cssText属性
console.log(rule.style.cssText);
//打印"width: 100px; height: 100px; background-color: blue;"
console.log(rule.cssText);
//打印.box { width: 100px; height: 100px; background-color: blue; }
cssText和style.cssText属性相同,但是并不完全相同。前者包含括号,同时是只读的!
访问元素的样式或者删除我们通过style设置的样式:
在标准模式下所有度量都必须指定一个度量单位,在混杂模式下可以设置为style.width="20"浏览器会自动加入px,但是标准模式下会导致设为20无效,所以在实践中最好始终指定度量单位。
$('.box')[0].style.cssText="border:1px solid red;background-color:yellow";
//我们重新设置cssText那么表示所有规则都被重写了
$('.box')[0].style.cssText="font-size:10px;";
//这时候上面的border和backgroundc-color都丢失了!
设置cssText是为元素引用多项变化最快的方式,因为可以一次性的应用所有变化
var dom=$('.box')[0];
for(var i=0,len=dom.style.length;i<len;i++)
{
var prop=dom.style.item(i);//或者style[i]
var value=dom.style.getPropertyValue(prop);
console.log("prop="+prop+"=value"+value);
}
如果是需要移除属性可以用removeProperty,设置属性添加优先权可以用setProperty(name,value,priority)其中优先权可以是import或者空字符串!(获取优先权可以用getPropertyPriority,通过getPropeprtyValue返回包含两个属性的CSSValue对象,这两个属性是cssText/cssValueType,其中cssValueType中0表示继承,1表示基本的值,2表示值列表,3表示自定义的值)注意:这些方法都只能操作在style中定义的属性
我们可以通过getComputedStyle或者currentStyle获取计算属性,但是对于向border这一类综合属性可能不会反正正确的值
var dom=$('.box')[0];
var computedStyle=document.defaultView.getComputedStyle(dom,null);
console.log(computedStyle.border);
//边框属性可能会也可能不会返回实际的border规则,Opera会其它浏览器不会
console.log(computedStyle.borderLeftWidth);
//即使computedStyle.border不会在所有浏览器中返回值但是computedStyle
//.borderLeftWidth会返回值
框架引入了contentDocument属性
var iframe=$('#iframe');
var iframeDoc=iframe.contentDocument||iframe.contentWindow.document;
//所有浏览器都支持contentWindow属性
document类型添加了一个importNode方法,用途在于从一个文档中取得节点,然后将其导入到另一个文档,使其成为文档的一部分。需要注意的是:每一个节点都有一个ownerDocument属性表示所属的文档,如果调用appendChild传的节点属于不同的文档就会导致错误,但是在调用importNode时候传入一个不同文档的节点就会返回一个新的节点,这个节点所有权归当前文档所有
var newNode=document.importNode(oldNode,true);
document.body.appendChild(newNode);
//和cloneNode一样第二个参数表示是否复制所有的子节点
如何获取文档的窗口
var parentWin=document.defaultView||document.parentWindow;
//IE中用parentWindow
为document.implementation对象添加了createDocument,createDocumentType,createHTMLDocument方法,见
该图
var htmldoc=document.implementation.createHTMLDocument('New Doc');
console.log(htmldoc.title);
//打印"New Doc"
console.log(typeof htmldoc.body);
//打印object
documentType我们添加了三个属性分别是publicId,systemId,internalSubset
console.log(document.doctype.publicId);
console.log(document.doctype.systemId);
console.log(document.doctype.intenalSubset);
Node类型添加了isSupported和isSameNode,isEqualNode
if(document.body.isSupported('HTML','2.0'))
{
//和hasFeature方法一样,用于确定当前节点具有什么能力
//两个参数分别为:特征名,特征版本号
}
创建的两个元素相等但是不相同
var div1=document.createElement('div');
div1.setAttribute('class','box');
var div2=document.createElement('div');
div2.setAttribute('class','box');
console.log(div1.isSameNode(div1));
//打印true
console.log(div1.isEqualNode(div2));
//打印true
console.log(div1.isSameNode(div2));
//打印false
通过设置getUserData/setUserData用于为DOM节点添加额外的数据,是DOM3的方法
var div=document.createElement('div');
div.setUserData("name",'qinliang',function(operation,key,value,src,dest)
{
if(operation==1)
//1表示复制,2表示导入,3表示删除,4表示重命名
//删除节点时候源节点是null,除在复制节点以外目标节点都是null
{
dest.setUserData(key,value,function(){})
}
});
//复制节点
var newDiv=div.cloneNode(true);
console.log(newDiv.getUserData('name'));
3、文档范围
var supportRange=document.implementation.hasFeature('Range','2.0');
console.log(supportRange);
//在兼容DOM的浏览器中这个方法是document对象的
var alsoSupportRange=(typeof document.createRange=='function');
//新创建的范围直接与创建他的文档关联起来,不能用于其它文档,创建了范围
//以后,接下来就可以使用它在后台选择文档的特定部分,而创建范围并设置了位置之后
//还可以针对范围的内容执行很多的操作,从而实现对底层DOM树更精确的控制!
console.log(alsoSupportRange);
通过上面的方式可以确定是否支持范围,我们看看支持那些属性和方法
startContainer:包含范围起点的节点,也就是选区中第一个节点的父节点
startOffset:范围在startContainer中起点的偏移量,如果startContainer是文本节点,注释节点,CDATA节点,那么就是范围起点之前跳过的字符数量,否则就是范围中第一个子节点的索引
endContainer:选区中最后一个节点的父节点
commonAncestorContainer:startContainer, endContainer共同的祖先节点在文档数中位置最深的那一个
选择节点:
selectNode:接受DOM节点,以该节点中的信息来填充范围,选择整个节点包含子节点
selectNodeContents:只选择节点的子节点
我们给选择节点的操作给出一个例子
<body>
<p id="p1"><b>Hello</b>world!</p>
</body>
给出下面例子
var dom=$('#p1')[0];
var range1=document.createRange();
var range2=document.createRange();
range1.selectNode(dom);
range2.selectNodeContents(dom);
//注意selectNode,selectNodeContents没有返回值
console.log(range1);
//selectNode时候包含p元素,这时候startContainer是body元素
//endContainer,commonAncestorContainer都是body!
//startOffset是1,因为p元素和body元素之间有空格,endoffset为2
//等于startOffset+1,因为只选中了一个节点!
console.log(range2);
//在调用selectNodeContents时候,startContainer,endContainer,commonAncestorContainer
//都是传入的节点也就是p,而startOffset始终为0,因为范围从给定节点第一个子节点开始
//最后endOffset等于子节点的数量node.childNodes.length
如果需要精确的控制那些节点在选区中可以使用setStartBefore/setStartAfter/setEndBefore/setEndAfter方法,在调用这些方法时候所有属性自动设置好了!
同时如要对DOM实现复杂的选择可以使用如下方法setStart,setEnd方法
var dom=$('#p1')[0];
var range1=document.createRange();
var range2=document.createRange();
var p1Index=-1,i,len;
for(i=0,len=dom.parentNode.childNodes.length;i<len;i++)
{
if(dom.parentNode.childNodes[i]==dom)
{
p1Index=i;
break;
}
}
//模拟selectNode
range1.setStart(dom.parentNode,p1Index);
range1.setEnd(dom.parentNode,p1Index+1);
//setStart,setEnd方法第一个参数是参照节点,第二个是偏移量值
//setStart会把参照节点变成startContainer,而偏离量变成startOffset
//setEnd会把参照节点设置为endContainer,而偏离量设置为endOffset!
range2.setStart(dom,0);
range2.setEnd(dom,dom.childNodes.length);
//模拟selectNodeContents!
模仿selectNode,selectNodeContents不是他们主要用途,主要用途在于可以选择节点的一部分
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.setStart(helloNode,2);
//startContainer就是helloNode,选择"llo"以后部分
range.setEnd(worldNode,3);
//endContainer是worldNode,endOffset是3!选择"wo"之前部分
注意:setStart,setEnd的时候是断尾的,也就是不包含指定的下标,如setStart(helloNode,2)就只是包含0-1的下标,同理适用于setEnd!在创建范围的时候,内部为我们创建一个文档碎片,但是前面都是开始和结束于文本内部,所以不算是格式良好的DOM结构,但是范围知道自己缺少那些开标签和闭标签,它能够重建有效的DOM结构。
第一个方法:deleteContents,该方法能够从文档中删除范围所包含的内容
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.setStart(helloNode,2);
//startContainer就是helloNode,选择"llo"以后部分
range.setEnd(worldNode,3);
//endContainer是worldNode,endOffset是3!选择"wo"之前部分
range.deleteContents();
//上面的HTML变成<p id="p1"><b>He</b>rld!</p>
//范围会保证修改底层DOM后,最终的DOM结构依然良好
第二个方法:extractContents方法,也是从文档中移除范围选取,但是该方法返回范围的文档片段,利用返回值可以把范围内容插入到文档其它地方
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.setStart(helloNode,2);
//startContainer就是helloNode,选择"llo"以后部分
range.setEnd(worldNode,3);
//endContainer是worldNode,endOffset是3!选择"wo"之前部分
var fragment=range.extractContents();
//上面的HTML变成<p id="p1"><b>Hello</b> world!</p>
dom.parentNode.appendChild(fragment);
//变成<p><b>He</b>rld!</p> <b>llo</b>wo
//记住:在将文档碎片传入到appendChild方法时候添加到文档中的只是
//片段的子节点,而非片段本身
第三个方法:cloneContents创建范围对象的一个副本,然后在文档的其它部分插入该副本
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.setStart(helloNode,2);
//startContainer就是helloNode,选择"llo"以后部分
range.setEnd(worldNode,3);
//endContainer是worldNode,endOffset是3!选择"wo"之前部分
var fragment=range.cloneContents();
//上面的HTML变成<p id="p1"><b>Hello</b> world!</p>
dom.parentNode.appendChild(fragment);
//和extractContents非常类似,主要区别在于该方法返回的文档片段包含的是
//范围中节点的副本,而不是实际的节点
//变成<p><b>Hello</b> world!</p> <b>llo</b>wo
注意:原始的HTML在DOM被修改之前会始终保持不变
插入DOM范围中的内容insertNode:
//插入代码<span style="color:red">Insert text</span>
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.setStart(helloNode,2);
range.setEnd(worldNode,3);
var span=document.createElement('span');
span.style.color="red";
span.appendChild(document.createTextNode('Insert text'));
range.insertNode(span);
//向范围选取的开始处插入一个节点,变成
//<p id="p1"><b>He<span style="color: red;">Insert text</span>llo</b> world!</p>
注意:由于没有上一节介绍的方法,结果原始的THML并没有添加或删除b元素,使用这些技术可以插入一些帮助信息例如在打开新窗口的链接方便插入一副图像
环绕选取插入内容,使用surroundContents就可以:
//插入代码<span style="color:red">Insert text</span>
var dom=$('#p1')[0];
var helloNode=dom.firstChild.firstChild;
//获取Hello节点
var worldNode=dom.lastChild;
//获取world节点
var range=document.createRange();
range.selectNode(helloNode);
var span=$('<span/>')[0];
span.style.backgroundColor="yellow";
range.surroundContents(span);
//把Hello节点用黄色的背景色包住
//<p id="p1"><b><span style="background-color: yellow;">Hello</span></b> world!</p>
为了插入span范围必须包含整个DOM选区,不能仅仅包含选中的DOM节点。使用这种技术可以突出显示网页中某些词句
利用collapse方法实现折叠DOM范围:
<p id="p1">paran1</p><p id="p2">param2</p>
用collapsed检测DOM是否已经折叠
var p1=$('#p1')[0];
var p2=$('#p2')[0];
var range=document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
console.log(range.collapsed);
//打印true,其中collapse()方法用于折叠选区,如果传入true那么表示折叠到
//范围的起点,否则折叠到终点
比较DOM范围compareBoundaryPoints方法
<p id="p1">paran1<span>I am span</span></p>
如果第一个范围点在前那么返回-1,相等返回0,否则返回1
var range1=document.createRange();
var range2=document.createRange();
var p1=$('#p1')[0];
range1.selectNodeContents(p1);
range2.selectNodeContents(p1);
range2.setEndBefore(p1.lastChild);
//开始比较两个选区START_TO_START比较起点,END_TO_END比较结束
//START_TO_END比较第一个范围的起点和第二个范围的终点
//END_TO_START比较第一个范围的终点和第一个范围的起点
console.log(range1.compareBoundaryPoints(Range.START_TO_START,range2));
console.log(range1.compareBoundaryPoints(Range.END_TO_END,range2));
//如果第一个范围中的点位于第二个范围中点之前那么返回-1,如果相等返回0,
//如果第一个范围中的点位于第二个范围中点之后返回1!(第一个范围的点-第二个范围点)
//打印0,1
复制DOM范围
var range1=document.createRange();
var range2=document.createRange();
var p1=$('#p1')[0];
range1.selectNodeContents(p1);
var newRange=range.cloneRange();
//复制范围
该方法可以获取范围的一个副本
清理DOM范围
var range1=document.createRange();
var range2=document.createRange();
var p1=$('#p1')[0];
range1.selectNodeContents(p1);
range1.detach();
//该方法从创建范围的文档中分离出该范围
range=null;
//Dereference,让垃圾回收
在使用范围最后我们再执行这两个步骤是我们推荐的方式!一旦分离范围就不能再恢复使用了!
IE8以及更早版本的范围:
IE8之前支持文本范围,其它浏览器不支持,通过body,button,input,textarea调用createTextRange就可以创建文本范围
var range=document.body.createTextRange();
var found=range.findText('Hello');
//该方法找到第一次出现的给定文本,并将范围移过来环绕该文本
//如果没有找到文本返回false
console.log(found);
console.log(range.text);
//通过text属性来获取选择的文本
//如果还需要查找第二个Hello那么我们继续调用findText,同时传入第二个参数表示搜索的方向
var foundAgain=range.findText('Hello',1);
//第二个参数如若是负数表示向后搜索,否则表示向前搜索
用moveToElement选择所有文本包括HTML标签,同时用htmlText获取选择内容
var range=document.body.createTextRange();
range.moveToElementText($('#p1')[0]);
//和selectNode很像,接受一个DOM元素选择该元素所有文本包括HTML标签,用htmlText属性获取文本
IE中没有任何属性可以随着范围选区的变化而动态更新,不过其parentElement方法与commonAncestorContainer类似
var range=document.body.createTextRange();
var ancestor=range.parentElement();
IE实现复杂的选择
move,moveStart,moveEnd,expand都接受两个参数表示移动的单位和移动单位的数量,其中单位"character,word,sentence,textedit"。moveStart表示移动起点,expand可以把范围可视化,表示把任何部分选择的文本全部选中,如expand('word')
range.move('character',5);
//首先折叠当前范围,然后把范围移动指定的单位数量
调用move后范围的起点和终点相同,因此必须在使用moveStart,moveEnd创建选区。
利用range的text属性和pasteHTML方法修改范围中的内容文本
var range=document.body.createTextRange();
range.findText('Hello');
range.text="qinliang";
//用text属性或者pasteHTML方法操作范围中的内容,text仅仅修改文本
range.pasteHTML="<em>Howdy</em>";
不过,在范围中包含HTML时候,不应该使用pasteHTML,因为这样很容易导致不可预期的效果
通过collapse进行折叠,同时使用boundingWidth判断是否折叠
var range=document.body.createTextRange();
range.collapse(true);
//把起点折叠到起点,如果是false表示折叠到终点
//但是IE没有提供collapsed,但是可以用boundingWidth
//返回范围的宽度,如果等于0表示折叠了
var isCollpased=(range.boundingWidth===0);
//还有一些不常用的boundingHeight/boundingLeft/boundingTop!
compareEndPoint用于比较范围,同时也支持isEqual,inRange,同时compareEndPoint和compareBoundaryPoints返回值相同
var range1=document.body.createTextRange();
var range2=document.body.createTextRange();
range1.findText('Hello world');
range2.findText('Hello');
console.log(range1.compareEndPoints('StartToStart',range2));
console.log(range1.compareEndPoints('EndToEnd',range2));
//打印0,1
console.log(range1.isEqual(range2));
console.log(range1.inRange(range2));
//isEqual确定两个范围是否相等,inRange判断一个范围是否包含另外一个范围
duplicate复制文本
var range1=document.body.createTextRange();
var newRange=range1.duplicate();
//复制文本范围,获取源范围的一个副本
各种类型:
DocumentType类型(nodeType为10)
console.log(document.doctype.name);
//打印'HTML'表示在<!doctype之后的文本
IE以及更早版本不支持DocumentType类型,因此document.doctype为null,可是这些浏览器会错误的把文档类型解释为注释,并且创建一个注释节点,IE9会给document.doctype赋值为正确的对象,但是仍然不支持访问!其nodeType为10,nodeValue为null,nodeName为doctype的名称,parentNode为Document!
Text类型:(nodeType为1)
用normalize方法合并多个文本节点
var elem=$('<div/>')[0];
var textNode=document.createTextNode('Hello World');
elem.appendChild(textNode);
var anotherText=document.createTextNode('qinliang');
elem.appendChild(anotherText);
document.body.appendChild(elem);
//其中elem元素有多个文本节点
console.log(elem.childNodes.length);
//打印2
elem.normalize();
//将所有的文本节点进行合并
console.log(elem.childNodes.length);
//打印1
浏览器在解析文档的时候永远不会创建相邻的文本,这种情况只在执行DOM时候才会出现
用splitText会将一个文本节点分割为两个文本节点,按照指定的位置分割nodeValue,原来的文本节点将包含从开始到指定位置之前的内容,新文本节点将包含剩下的文本,这个方法会返回一个新文本节点
var elem=$('<div/>')[0];
var textNode=document.createTextNode('Hello World');
elem.appendChild(textNode);
document.body.appendChild(elem);
var newNode=elem.firstChild.splitText(5);
//切割文本
console.log(elem.firstChild.nodeValue);
//原来的文本也改变了"Hello"
console.log(newNode.nodeValue);
//新文本就是剩下的文本" world"
console.log(elem.childNodes.length);
//已经被分割成为两个文本节点了
//HTML结构为:<div>"Hello" " World"</div>
可以通过nodeValue和data获取文本节点的值
var elem=$('<div/>')[0];
var textNode=document.createTextNode('Hello World');
elem.appendChild(textNode);
document.body.appendChild(elem);
//可以通过nodeValue或者data属性访问Text节点中包含的文本,这两个值包含的内容相同
//对nodeValue的修改会通过data反映出来,反之亦然。文本节点还包含一个length属性
//保存着节点中字符的数目,而且nodeValue.length和data.length保存同样的值
console.warn(textNode.data);
console.log(textNode.nodeValue);
//打印"Hello world"
还包括其它方法:appendData(text),deleteData(offset,count),insertData(offset,text),replaceData(offset,count,text),substringData(offset,count)等其它方法
Comment类型:(nodeType为8)
他拥有所有除了splitText之外的字符串方法,而且一定要保证他是在html中,浏览器会忽略</html>后的注释
var div=$('myDiv')[0];
var comment=div.firstChild;
console.log(comment.data);
//可以通过data也可以通过nodeValue访问
var com=document.createElement('A comment');
//创建comment对象
DocumentFragment对象(nodeType为11)
其中nodeValue为null,而且parentNode也是null
将文档片段作为appendChild或者insertBefore将文档片段内容插入到文档中,实际上只会将文档片段的所有子节点添加到相应的位置, 文档片段本身永远不会成为文档树的一部分。
var fgm=document.createDocumentFragment();
var ul=$('#myList')[0];
var li=null;
for(var i=0;i<3;i++)
{
li=$('<li/>')[0];
li.appendChild(document.createTextNode("Item"+(i+1)));
fgm.appendChild(li);
}
ul.appendChild(fgm);
//此时文档碎片的所有子节点都被删除并且转移到ul元素中!
Attr类型(nodeValue为2)
parentNode为null
var attr=document.createAttribute('align');
attr.value="left";
$('#myList')[0].setAttributeNode(attr);
//必须调用setAttributeNode把属性运用到元素
console.log($('#myList')[0].getAttributeNode('align').value);
console.log($('#myList')[0].getAttribute('align'));
console.log($('#myList')[0].attributes['align'].value);
可以通过getAttribute,getAttributeNode,attrbutes访问属性。记住:尽管他们是节点,但是他们
不被认为是DOM文档树的一部分!Element类型:
nodeValue为null,要访问标签名可以用nodeName也可以用tagName;id,title,lang,dir,className是property也是attribute,修改任何一个另外都会修改!
var dom=$('#myList')[0];
console.log(dom.getAttribute('lang'));
//打印null
dom.lang="English";
console.log(dom.getAttribute('lang'));
//打印"English"
console.log(dom.getAttribute('dir'));
//打印null
dom.setAttribute('dir','ltr');
//可以是"ltf"或者"rtl"
console.log(dom['dir']);
//打印ltr
特性的名称是不区分大小写的,同时HTML5规定,自定义属性要加上data-以供验证。对于style如果通过getAttribute访问返回文本,通过属性访问返回对象;onclick等事件处理程序通过getAttribute返回字符串,而通过属性访问返回javascript函数。由于存在差别,在通过js操作DOM时候,开发人员很少用getAttrubute而是只使用对象的属性,只在取得自定义属性时候用getAttrubute!IE7之前getAttribute('style')返回对象,getAttribute('onclick')返回函数,但是在IE8已经修改bug!
Element类型有一个attributes属性:
function outputAttributes(elem)
{
var pairs=new Array(),attrName,attrValue,i,len;
for(i=0,len=element.attributes.length;i<len;i++)
{
attrName=elem.attributes[i].nodeName;
attrValue=elem.attributes[i].nodeValue;
//每一个特征节点都有一个specified属性,如果是true,那么要么就是
//在HTML中指定了相应的特征,要么就是通过setAttribute设置了该属性
//IE中所有未设置的都是false,而在其它浏览器中根本不会为这类特性
//生成特性节点
if(elem.attributes[i].specified)
{
pairs.push(attrName+"="+attrValue);
}
}
return pairs.join(" ");
}
attributes属性包含的是NamedNodeMap,包含getNamedItem,setNamedItem(node),removeNamedItem,item(index)等方法,每一个节点的nodeName就是特性名称,nodeValue就是特性的值。当然也可以通过括号来访问
var oldAttr=element.attributes.removeNamedItem('id');
removedNamedItem和removeAttribute是一样的,唯一的区别是前者返回移除的Attr节点!同时setNamedItem必须为元素添加一个节点也就是参数是节点!
使用childNodes使用不同浏览器处理空格的方式是不一样的
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
我们使用childNodes来获取
var dom=$('#myList')[0];
for(var i=0,len=dom.childNodes.length;i<len;i++)
{
if(dom.childNodes[i].nodeType==1)
{
//在IE中包含三个节点,在其它浏览器中包含7个元素
}
}
如果是上面的例子我们可以在ul上调用getElementsByTagName('li'),同时在
IE9以后也引入了children属性,该属性只包含元素中同样还是元素的子节点,除此之外和childNodes没有任何区别,IE8之前版本children还包含了注释节点,但是在IE9之后修正了该bug!