1. 程式人生 > >[高階程式設計]從高階程式設計中搬來的一些值得注意的地方(續)

[高階程式設計]從高階程式設計中搬來的一些值得注意的地方(續)

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";
  }
在CSDN中,用於儲存草稿的用法就是用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