十、DOM 擴展

??盡管 DOM 作為 API 已經非常完善了,但為了實現更過的功能,仍然會有一些標準或專有的擴展。

??2008 年之前,瀏覽器中幾乎所有的的 DOM 擴展都是專有的。此后,W3C 著手將一些已經成為事實標準的專有擴展標準化并寫入規范當中。

??對 DOM 的兩個主要的擴展是 Selectors API(選擇符 API)和 HTML5 這兩個擴展都源自開發社區,而降某些常見做法及 API 標準化一直是眾望所歸。

??此外,還有一個不那么引人矚目的 Element Traversal(元素遍歷)規范,為 DOM 添加一一些屬性。

??雖然前述兩個主要規范(特別是 HTML5)已經涵蓋了大量的 DOM 擴展,但專有擴展依然存在。

1、選擇符 API

??眾多 JavaScript 庫中最常用的一項功能,就是根據 CSS 選擇符與某個模式匹配的 DOM 元素。實際上,jQuery(www.jquery.com)的核心就是通過 CSS 選擇符查詢 DOM 文檔取得元素的引用,從而拋開了 getElementById() 和 getElementByTagName()。
??Selectors API(www.w3.org/TR/selectors-api/)是由 W3C 發起制定的一個標準,致力于讓瀏覽器原生支持 CSS 查詢。所有實現這一功能的 JavaScript 庫都會寫一個基礎的 CSS 解析器,然后再使用已有的 DOM 方法查詢文檔并找到匹配的節點。
??盡管庫開發人員在不知疲倦地改進這一進程的性能,但到頭來都只能通過運行 JavaScript 代碼來完成查詢操作。
??而把這個功能變成原生 API 后,解析和樹查詢操作可以在瀏覽器內部通過編譯后的代碼來完成,極大地改善了性能。

??Selectors API Level 1 的核心是兩個方法: querySelector() 和 querySelectorAll().

??在兼容的瀏覽器中,可以通過 Document 及 Element 類型的實例調用它們。目前已完全支持 Selectors API Level 1 的瀏覽器有 IE 8+、Firefox 3.5+、Safari 3.1+、Chrome 和 Opera 10+。

1.1、querySelector() 方法

??querySelector() 方法接收一個 CSS 選擇符,返回與該模式匹配的第一個元素,如果沒有找到匹配的元素,返回 null。示例:

// 取得 body 元素
var body = document.querySelector("body");

// 取得 ID 為"myDiv"的元素
var myDiv = document.querySelector("#myDiv");

// 取得類為"selected"的第一個元素
var selected = document.querySelector(".selected");

// 取得類為"button"的第一個圖像元素
var img = document.body.querySelector("img.button"); 

??通過 Document 類型調用 querySelector() 方法時,會在文檔元素的范圍內查找匹配的元素。而通過 Element 類型調用 querySelector() 方法時,只會在該元素后代元素的范圍內查找匹配的元素。

??CSS 選擇符可以簡單也可以復雜,是情況而定。如果傳入了不被支持的選擇符,querySelector() 會拋出錯誤。

1.2、querySelectorAll() 方法

??querySelectorAll() 方法接收的參數與 querySelector() 方法一樣,都是一個 CSS 選擇符,但返回的是所有匹配的元素而不僅僅是一個元素。這個方法返回的是一個 NodeList 的實例。
??具體來說,返回的值實際上是帶有所有屬性和方法的 NodeList,而其底層實現則類似于一組元素的快照,而非不斷對文檔進行搜索的動態查詢。這樣實現可以避免使用 NodeList 對象通常會引起的大多數性能問題。
??只要傳給 querySelector() 方法的 CSS 選擇符有效,該方法都會返回一個 NodeList 對象,而不管找到多少匹配的元素。如果沒有找到匹配的元素,NodeList 就是空的。
??與 querySelector() 類似,能夠調用 querySelectorAll() 方法的類型包括 Document、DocumentFragment 和 Element。示例:

// 取得某<div>中的所有<em>元素(類似于 getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em");

// 取得類為"selected"的所有元素
var selecteds = document.querySelectorAll(".selected");

// 取得所有<p>元素中的所有<strong>元素
var strongs = document.querySelectorAll("p strong"); 

??要取得返回的 NodeList 中的每一個元素,可以使用 item() 方法,也可以使用方括號語法,示例;

var i, len, strong;
for (i=0, len=strongs.length; i < len; i++){
    strong = strongs[i]; // 或者 strongs.item(i)
    strong.className = "important";
} 

??同樣與 querySelector() 類似,如果傳入了瀏覽器不支持的選擇符或者選擇符中有語法錯誤,querySelectorAll() 會拋出錯誤。

1.3、matchesSelector() 方法

??Selectors API Level 2 規范為 Element 類型新增了一個方法 matchesSelector().
??這個方法接收一個參數,即 CSS 選擇符,如果調用元素與該選擇符匹配,返回 true;否則,返回 false。示例:

if (document.body.matchesSelector("body.page1")){
    // true
} 

??在取得某個元素引用的情況下,使用這個方法能夠方便地檢測它是否會被 querySelector() 或 querySelectorAll() 方法返回。
??截至 2011 年年中,還沒有瀏覽器支持 matchesSelector()方法;不過,也有一些實驗性的實現。IE 9+通過 msMatchesSelector() 支持該方法,Firefox 3.6+通過 mozMatchesSelector() 支持該方法,
Safari 5+和 Chrome 通過 webkitMatchesSelector() 支持該方法。因此,如果你想使用這個方法,最好是編寫一個包裝函數。

function matchesSelector(element, selector){
    if (element.matchesSelector){
        return element.matchesSelector(selector);
    } else if (element.msMatchesSelector){
        return element.msMatchesSelector(selector);
    } else if (element.mozMatchesSelector){
        return element.mozMatchesSelector(selector);
    } else if (element.webkitMatchesSelector){
        return element.webkitMatchesSelector(selector);
    } else {
        throw new Error("Not supported.");
    }
}
if (matchesSelector(document.body, "body.page1")){
    // 執行操作
} 

2、元素遍歷

??對于元素間的空格,IE9 及之前版本不會返回文本節點,而其他所有瀏覽器都會返回文本節點。這樣,就導致了在使用 childNodes 和 firstChild 等屬性時的行為不一致。
??為了彌補這一差異,而同時又報出 DOM 規范不變,Element Traversal 規范(www.w3.org/TR/ElementTraversal/),新定義了一組屬性。

??Element Traversal API 為 DOM 元素添加了以下 5 個屬性。

  • childElementCount:返回子元素(不包括文本節點和注釋)的個數。
  • firstElementChild:指向第一個子元素;firstChild 的元素版。
  • lastElementChild:指向最后一個子元素;lastChild 的元素版。
  • previousElementSibling:指向前一個同輩元素;previousSibling 的元素版。
  • nextElementSibling:指向后一個同輩元素;nextSibling 的元素版。

??支持的瀏覽器為 DOM 元素添加了這些屬性,利用這些元素不必擔心空白文本節點,從而可以更方便地查找 DOM 元素了。

??過去,要跨瀏覽器遍歷某元素的所有子元素,需要像下面這樣寫代碼。

var i,
    len,
    child = element.firstChild;
while(child != element.lastChild){
    if (child.nodeType == 1){ // 檢查是不是元素
        processChild(child);
    }
    child = child.nextSibling;
} 

??而使用 Element Traversal 新增的元素,代碼會更簡潔:

var i,
    len,
    child = element.firstElementChild;
while(child != element.lastElementChild){
    processChild(child); // 已知其是元素
    child = child.nextElementSibling;
} 

??支持 Element Traversal 規范的瀏覽器有 IE 9+、Firefox 3.5+、Safari 4+、Chrome 和 Opera 10+。

3、HTML5

??對于傳統 HTML 而言,HTML5 是一個叛逆。所有之前的版本對 JavaScript 接口的描述都不過三言兩語,主要篇幅都用于定義標記,與 JavaScript 相關的內容一概交由 DOM 規范去定義。
??而 HTML5 規范則圍繞如何使用新增標記定義了大量 JavaScript API。其中一些 API 與 DOM 重疊,定義了瀏覽器應該支持的 DOM 擴展。

3.1、與類相關的擴展

??HTML4 在 Web 開發領域得到廣泛采用后導致了一個很大的變化,即 class 屬性用得越來越多,一方面可以通過它為元素添加樣式,另一方面還可以用它表示元素的語義。
??于是,自然就有很多 JavaScript 代碼會來操作 CSS 類,比如動態修改類或者搜索文檔中具有給定類或給定的一組類的元素,等等。
??為了讓開發人員適應并增加對 class 屬性的新認識,HTML5 新增了很多 API,致力于簡化 CSS 類的用法。

1. getElementsByClassName() 方法

??HTML5 添加的 getElementsByClassName() 方法是最受人歡迎的一個方法,可以通過 document 對象及所有 HTML 元素調用該方法。這個方法最早出現在 JavaScript 庫中,是通過既有的 DOM 功能實現的,而原生的實現具有極大的性能優勢。

??getElementsByClassName() 方法接收一個參數,即一個包含一或多個類名的字符串,返回帶有指定類的所有元素的 NodeList。傳入多個類名時,類名的先后順序不重要。示例:

// 取得所有類中包含"username"和"current"的元素,類名的先后順序無所謂
var allCurrentUsernames = document.getElementsByClassName("username current");

// 取得 ID 為"myDiv"的元素中帶有類名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected"); 

??調用這個方法時,只有位于調用元素子樹中的元素才會返回。在 document 對象上調用 getElementsByClassName() 始終會返回與類名匹配的所有元素,在元素上調用該方法就只會返回后代元素中匹配的元素。
??使用這個方法可以更方便地為帶有某些類的元素添加事件處理程序,從而不必再局限于使用 ID 或標簽名。
??不過別忘了,因為返回的對象是 NodeList,所以使用這個方法與使用 getElementsByTagName() 以及其他返回 NodeList 的 DOM 方法都具有同樣的性能問題。
??支持 getElementsByClassName() 方法的瀏覽器有 IE 9+、Firefox 3+、Safari 3.1+、Chrome 和 Opera 9.5+。

2. classList 屬性

??在操作類名時,需要通過 className 屬性添加、刪除和替換類名。因為 className 中是一個字符串,所以即使只修改字符串一部分,也必須每次都設置整個字符串的值。示例:

<div class="bd user disabled">...</div>

??這個<div>元素一共有三個類名。要從中刪除一個類名,需要把這三個類名拆開,刪除不想要的那個,然后再把其他類名拼成一個新字符串。示例:

// 刪除"user"類

// 首先,取得類名字符串并拆分成數組
var classNames = div.className.split(/\s+/);

// 找到要刪的類名
var pos = -1,
    i,
    len;
for (i=0, len=classNames.length; i < len; i++){
    if (classNames[i] == "user"){
        pos = i;
        break;
    }
}

// 刪除類名
classNames.splice(i,1);

// 把剩下的類名拼成字符串并重新設置
div.className = classNames.join(" ");

??為了從<div>元素的 class 屬性中刪除"user",以上這些代碼都是必需的。必須得通過類似的算法替換類名并確認元素中是否包含該類名。
??添加類名可以通過拼接字符串完成,但必須要通過檢測確定不會多次添加相同的類名。很多 JavaScript 庫都實現了這個方法,以簡化這些操作。

??HTML5 新增了一種操作類名的方式,可以讓操作更簡單也更安全,那就是為所有元素添加 classList 屬性。這個 classList 屬性是新集合類型 DOMTokenList 的實例。
??與其他 DOM 集合類似,DOMTokenList 有一個表示自己包含多少元素的 length 屬性,而要取得每個元素可以使用 item() 方法,也可以使用方括號語法。此外,這個新類型還定義如下方法。

  • add(value):將給定的字符串值添加到列表中。如果值已經存在,就不添加了。
  • contains(value):表示列表中是否存在給定的值,如果存在則返回 true,否則返回 false。
  • remove(value):從列表中刪除給定的字符串。
  • toggle(value):如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,添加它。

??這樣,前面那么多行代碼用下面這一行代碼就可以代替了:

div.classList.remove("user");

??以上代碼能夠確保其他類名不受此次修改的影響。其他方法也能極大地減少類似基本操作的復雜性,示例:

// 刪除"disabled"類
div.classList.remove("disabled");

// 添加"current"類
div.classList.add("current");

// 切換"user"類
div.classList.toggle("user");

// 確定元素中是否包含既定的類名
if (div.classList.contains("bd") && !div.classList.contains("disabled")){
    // 執行操作
)
// 迭代類名
for (var i=0, len=div.classList.length; i < len; i++){
    doSomething(div.classList[i]);
}

??有了 classList 屬性,除非你需要全部刪除所有類名,或者完全重寫元素的 class 屬性,否則也就用不到 className 屬性了。
??支持 classList 屬性的瀏覽器有 Firefox 3.6+ 和 Chrome。

3.2、焦點管理

??HTML5 也添加了輔助管理 DOM 焦點的功能。首先就是 document.activeElement 屬性,這個屬性始終會引用 DOM 中當前獲得了焦點的元素。
??元素獲得焦點的方式有頁面加載、用戶輸入(通常是通過按 Tab 鍵)和在代碼中調用 focus() 方法。示例:

var button = document.getElementById("myButton");
button.focus();
console.log(document.activeElement === button); // true

??默認情況下,文檔剛剛加載完成時,document.activeElement 中保存的是 document.body 元素的引用。文檔加載期間,document.activeElement 的值為 null。

??另外就是新增了 document.hasFocus() 方法,這個方法用于確定文檔是否獲得了焦點。示例:

var button = document.getElementById("myButton");
button.focus();
console.log(document.hasFocus()); // true

??通過檢測文檔是否獲得了焦點,可以知道用戶是不是正在與頁面交互。
??查詢文檔獲知哪個元素獲得了焦點,以及確定文檔是否獲得了焦點,這兩個功能最重要的用途是提高 Web 應用的無障礙性。
??無障礙 Web 應用的一個主要標志就是恰當的焦點管理,而確切地知道哪個元素獲得了焦點是一個極大的進步,至少我們不用再像過去那樣靠猜測了。
??實現了這兩個屬性的瀏覽器的包括 IE 4+、Firefox 3+、Safari 4+、Chrome 和 Opera 8+。

3.3、HTMLDocument 的變化

??HTML5 擴展了 HTMLDocument,增加了新的功能。與 HTML5 中新增的其他 DOM 擴展類似,這些變化同樣基于那些已經得到很多瀏覽器完美支持的專有擴展。所以,盡管這些擴展被寫入標準的時間相對不長,但很多瀏覽器很早就已經支持這些功能了。

1. readyState 屬性

??IE4 最早為 document 對象引入了 readyState 屬性。然后,其他瀏覽器也都陸續添加這個屬性,最終 HTML5 把這個屬性納入了標準當中。
??Document 的 readyState 屬性有兩個可能的值:

  • loading,正在加載文檔;
  • complete,已經加載完文檔。

??使用 document.readyState 的最恰當方式,就是通過它來實現一個指示文檔已經加載完成的指示器。
??在這個屬性得到廣泛支持之前,要實現這樣一個指示器,必須借助 onload 事件處理程序設置一個標簽,表明文檔已經加載完畢。document.readyState 屬性的基本用法如下。

if (document.readyState == "complete"){
    // 執行操作
}

??支持 readyState 屬性的瀏覽器有 IE4+、Firefox 3.6+、Safari、Chrome 和 Opera 9+。

2. 兼容模式

??自從 IE6 開始區分渲染頁面的模式是標準的還是混雜的,檢測頁面的兼容模式就成為瀏覽器的必要功能。
??IE 為此給 document 添加了一個名為 compatMode 的屬性,這個屬性就是為了告訴開發人員瀏覽器采用了哪種渲染模式。
??就像下面例子中所展示的那樣,在標準模式下,document.compatMode 的值等于 "CSS1Compat",而在混雜模式下,document.compatMode 的值等于 "BackCompat"。

if (document.compatMode == "CSS1Compat"){
    console.log("Standards mode");
} else {
    console.log("Quirks mode");
}

??后來,陸續實現這個屬性的瀏覽器有 Firefox、Safari 3.1+、Opera 和 Chrome。最終,HTML5 也把這個屬性納入標準,對其實現做出了明確規定。

3. head 屬性

??作為對 document.body 引用文檔的<body>元素的補充,HTML5 新增了 document.head 屬性,引用文檔的<head>元素。
??要引用文檔的<head>元素,可以結合使用這個屬性和另一種后備方法。

var head = document.head || document.getElementsByTagName("head")[0];

??如果可用,就使用 document.head,否則仍然使用 getElementsByTagName()方法。
??實現 document.head 屬性的瀏覽器包括 Chrome 和 Safari 5。

3.4、字符集屬性

??HTML5 新增了幾個與文檔字符集有關的屬性。其中,charset 屬性表示文檔中實際使用的字符集,也可以用來指定新字符集。
??默認情況下,這個屬性的值為"UTF-16",但可以通過<meta>元素、響應頭部或直接設置 charset 屬性修改這個值。示例:

console.log(document.charset); // "UTF-16"
document.charset = "UTF-8";

??另一個屬性是 defaultCharset,表示根據默認瀏覽器及操作系統的設置,當前文檔默認的字符集應該是什么。如果文檔沒有使用默認的字符集,那 charset 和 defaultCharset 屬性的值可能會不一樣,例如:

if (document.charset != document.defaultCharset){
    console.log("Custom character set being used.");
}

??通過這兩個屬性可以得到文檔使用的字符編碼的具體信息,也能對字符編碼進行準確地控制。運行適當的情況下,可以保證用戶正常查看頁面或使用應用。
??支持 document.charset 屬性的瀏覽器有 IE、Firefox、Safari、Opera 和 Chrome。支持 document.defaultCharset 屬性的瀏覽器有 IE、Safari 和 Chrome。

3.5、自定義數據屬性

??HTML5 規定可以為元素添加非標準的屬性,但要添加前綴 data-,目的是為元素提供與渲染無關的信息,或者提供語義信息。這些屬性可以任意添加、隨便命名,只要以 data-開頭即可。示例:

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

??添加了自定義屬性之后,可以通過元素的 dataset 屬性來訪問自定義屬性的值。dataset 屬性的值是 DOMStringMap 的一個實例,也就是一個名值對兒的映射。
??在這個映射中,每個 data-name 形式的屬性都會有一個對應的屬性,只不過屬性名沒有 data-前綴(比如,自定義屬性是 data-myname,那映射中對應的屬性就是 myname)。示例:

// 本例中使用的方法僅用于演示
var div = document.getElementById("myDiv");

// 取得自定義屬性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;

// 設置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";

// 有沒有"myname"值呢?
if (div.dataset.myname){ 
    console.log("Hello, " + div.dataset.myname);
}

??如果需要給元素添加一些不可見的數據以便進行其他處理,那就要用到自定義數據屬性。
??在跟蹤鏈接或混搭應用中,通過自定義數據屬性能方便地知道點擊來自頁面中的哪個部分。

3.6、插入標記

??雖然 DOM 為操作節點提供了細致入微的控制手段,但在需要給文檔插入大量新 HTML 標記的情況下,通過 DOM 操作仍然非常麻煩,因為不僅要創建一系列 DOM 節點,而且還要小心地按照正確的順
序把它們連接起來。
??相對而言,使用插入標記的技術,直接插入 HTML 字符串不僅更簡單,速度也更快。以下與插入標記相關的 DOM 擴展已經納入了 HTML5 規范。

1. innerHTML 屬性

??在讀模式下,innerHTML 屬性返回與調用元素的所有子節點(包括元素、注釋和文本節點)對應的 HTML 標記。
??在寫模式下,innerHTML 會根據指定的值創建新的 DOM 樹,然后用這個 DOM 樹完全替換調用元素原先的所有子節點。示例:

<div id="content">
    <p>This is a <strong>paragraph</strong> with a list following it.</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
</div>

??對于上面的<div>元素來說,它的 innerHTML 屬性會返回如下字符串。

<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

??但是,不同瀏覽器返回的文本格式會有所不同。IE 和 Opera 會將所有標簽轉換為大寫形式,而 Safari、Chrome 和 Firefox 則會原原本本地按照原先文檔中(或指定這些標簽時)的格式返回 HTML,包括空格和縮進。不要指望所有瀏覽器返回的 innerHTML 值完全相同。
??在寫模式下,innerHTML 的值會被解析為 DOM 子樹,替換調用元素原來的所有子節點。因為它的值被認為是 HTML,所以其中的所有標簽都會按照瀏覽器處理 HTML 的標準方式轉換為元素(同樣,這里的轉換結果也因瀏覽器而異)。
??如果設置的值僅是文本而沒有 HTML 標簽,那么結果就是設置純文本,示例:

div.innerHTML = "Hello world!";

??為 innerHTML 設置的包含 HTML 的字符串值與解析后 innerHTML 的值大不相同。示例:

div.innerHTML = "Hello & welcome, <b>\"reader\"!</b>";

??以上操作得到的結果如下:

<div id="content">Hello &amp; welcome, <b>&quot;reader&quot;!</b></div>

??設置了 innerHTML 之后,可以像訪問文檔中的其他節點一樣訪問新創建的節點。

??為 innerHTML 設置 HTML 字符串后,瀏覽器會將這個字符串解析為相應的 DOM 樹。
??因此設置了 innerHTML 之后,再從中讀取 HTML 字符串,會得到與設置時不一樣的結果。原因在于返回的字符串是根據原始 HTML 字符串創建的 DOM 樹經過序列化之后的結果。

??使用 innerHTML 屬性也有一些限制。比如,在大多數瀏覽器中,通過 innerHTML 插入<script>元素并不會執行其中的腳本。
??IE8 及更早版本是唯一能在這種情況下執行腳本的瀏覽器,但必須滿足一些條件。
??一是必須為<script>元素指定 defer 屬性;
??二是<script>元素必須位于(微軟所謂的)“有作用域的元素”(scoped element)之后。<script>元素被認為是“無作用域的元素”(NoScope element),也就是在頁面中看不到的元素,與<style>元素或注釋類似。
??如果通過 innerHTML 插入的字符串開頭就是一個“無作用域的元素”,那么 IE 會在解析這個字符串前先刪除該元素。換句話說,以下代碼達不到目的:

div.innerHTML = "<script defer>alert('hi');<\/script>"; // 無效

??此時,innerHTML 字符串一開始(而且整個)就是一個“無作用域的元素”,所以這個字符串會變成空字符串。
??如果想插入這段腳本,必須在前面添加一個“有作用域的元素”,可以是一個文本節點,也可以是一個沒有結束標簽的元素如<input>。例如,下面這幾行代碼都可以正常執行:

div.innerHTML = "_<script defer>alert('hi');<\/script>";
div.innerHTML = "<div>&nbsp;</div><script defer>alert('hi');<\/script>";
div.innerHTML = "<input type=\"hidden\"><script defer>alert('hi');<\/script>";

??第一行代碼會在<script>元素前插入一個文本節點。事后,為了不影響頁面顯示,你可能需要移除這個文本節點。
??第二行代碼采用的方法類似,只不過使用的是一個包含非換行空格的<div>元素。如果僅僅插入一個空的<div>元素,還是不行;必須要包含一點兒內容,瀏覽器才會創建文本節點。同樣,為了不影響頁面布局,恐怕還得移除這個節點。
??第三行代碼使用的是一個隱藏的<input>域,也能達到相同的效果。不過,由于隱藏的<input>域不影響頁面布局,因此這種方式在大多數情況下都是首選。

??大多數瀏覽器都支持以直觀的方式通過 innerHTML 插入<style>元素,例如:

div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";

??但在 IE8 及更早版本中,<style>也是一個“沒有作用域的元素”,因此必須像下面這樣給它前置一個“有作用域的元素”:

div.innerHTML = "_<style type=\"text/css\">body {background-color: red; }</style>";
div.removeChild(div.firstChild);

??并不是所有元素都支持 innerHTML 屬性。
??不支持 innerHTML 的元素有:<col>、<colgroup>、
<frameset>、<head>、<html>、<style>、<table>、<tbody>、<thead>、<tfoot> 和 <tr>。
??此外,在 IE8 及更早版本中,<title>元素也沒有 innerHTML 屬性。

??Firefox對在內容類型為 application/xhtml+xml的 XHTML文檔中設置 innerHTML 有嚴格的限制。
??在 XHTML 文檔中使用 innerHTML 時,XHTML 代碼必須完全符合 要求。如果代碼格式不正確,設置 innerHTML 將會靜默地失敗。

??無論什么時候,只要使用 innerHTML 從外部插入 HTML,都應該首先以可靠的方式處理 HTML。
??IE8 為此提供了 window.toStaticHTML() 方法,這個方法接收一個參數,即一個 HTML 字符串;返回 一個經過無害處理后的版本——從源 HTML 中刪除所有腳本節點和事件處理程序屬性。示例:

var text = "<a href=\"#\" onclick=\"alert('hi')\">Click Me</a>";
var sanitized = window.toStaticHTML(text); // Internet Explorer 8 only 
console.log(sanitized); // "<a href=\"#\">Click Me</a>"

??上述例子將一個 HTML 鏈接字符串傳給了 toStaticHTML() 方法,得到的無害版本中去掉了 onclick 屬性。雖然目前只有 IE8 原生支持這個方法,但我們還是建議讀者在通過 innerHTML 插入代 碼之前,盡可能先手工檢查一下其中的文本內容。

2. outerHTML 屬性

??在讀模式下,outerHTML 返回調用它的元素及所有子節點的 HTML 標簽。
??在寫模式下,outerHTML 會根據指定的 HTML 字符串創建新的 DOM 子樹,然后用這個 DOM 子樹完全替換調用元素。示例:

<div id="content">
    <p>This is a <strong>paragraph</strong> with a list following it.</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
</div>

??如果在<div>元素上調用 outerHTML,會返回與上面相同的代碼,包括<div>本身。
??不過,由于瀏覽器解析和解釋 HTML 標記的不同,結果也可能會有所不同。(這里的不同與使用 innerHTML 屬性時存在的差異性質是一樣的。)
??使用 outerHTML 屬性以下面這種方式設置值:

div.outerHTML = "<p>This is a paragraph.</p>";

??這行代碼完成的操作與下面這些 DOM 腳本代碼一樣:

var p = document.createElement("p");
p.appendChild(document.createTextNode("This is a paragraph."));
div.parentNode.replaceChild(p, div);

??結果,就是新創建的<p>元素會取代 DOM 樹中的<div>元素。
??支持 outerHTML 屬性的瀏覽器有 IE4+、Safari 4+、Chrome 和 Opera 8+。Firefox 7 及之前版本都不支持 outerHTML 屬性。

3. insertAdjacentHTML() 方法

??插入標記的最后一個新增方式是 insertAdjacentHTML() 方法。這個方法最早也是在IE中出現的,
??它接收兩個參數:插入位置和要插入的 HTML 文本。第一個參數必須是下列值之一:

  • "beforebegin",在當前元素之前插入一個緊鄰的同輩元素;
  • "afterend",在當前元素之后插入一個緊鄰的同輩元素;
  • "afterbegin",在當前元素之下插入一個新的子元素或在第一個子元素之前再插入新的子元素;
  • "beforeend",在當前元素之下插入一個新的子元素或在最后一個子元素之后再插入新的子元素。

??注意,這些值都必須是小寫形式。第二個參數是一個 HTML 字符串(與 innerHTML 和 outerHTML 的值相同),如果瀏覽器無法解析該字符串,就會拋出錯誤。以下是這個方法的基本用法示例。

// 作為前一個同輩元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");

// 作為第一個子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");

// 作為最后一個子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");

// 作為后一個同輩元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");

??支持 insertAdjacentHTML() 方法的瀏覽器有 IE、Firefox 8+、Safari、Opera 和 Chrome。

4. 內存與性能問題

??使用本節介紹的方法替換子節點可能會導致瀏覽器的內存占用問題,尤其是在 IE 中,問題更加明顯。
??在刪除帶有事件處理程序或引用了其他 JavaScript 對象子樹時,就有可能導致內存占用問題。
??假設某個元素有一個事件處理程序(或者引用了一個 JavaScript 對象作為屬性),在使用前述某個屬性將該元素從文檔樹中刪除后,元素與事件處理程序(或 JavaScript 對象)之間的綁定關系在內存中并沒有一并刪除。如果這種情況頻繁出現,頁面占用的內存數量就會明顯增加。
??因此,在使用 innerHTML、outerHTML 屬性和 insertAdjacentHTML() 方法時,最好先手工刪除要被替換的元素的所有事件處理程序和 JavaScript 對象屬性。

??不過,使用這幾個屬性——特別是使用 innerHTML,仍然還是可以為我們提供很多便利的。
??一般來說,在插入大量新 HTML 標記時,使用 innerHTML 屬性與通過多次 DOM 操作先創建節點再指定它們之間的關系相比,效率要高得多。這是因為在設置 innerHTML 或 outerHTML 時,就會創建一個 HTML 解析器。這個解析器是在瀏覽器級別的代碼(通常是 C++編寫的)基礎上運行的,因此比執行 JavaScript 快得多。
??不可避免地,創建和銷毀 HTML 解析器也會帶來性能損失,所以最好能夠將設置 innerHTML 或 outerHTML 的次數控制在合理的范圍內。例如,下列代碼使用 innerHTML 創建了很多列表項:

for (var i=0, len=values.length; i < len; i++){
    ul.innerHTML += "<li>" + values[i] + "</li>"; // 要避免這種頻繁操作!!
}

??這種每次循環都設置一次 innerHTML 的做法效率很低。而且,每次循環還要從 innerHTML 中讀取一次信息,就意味著每次循環要訪問兩次 innerHTML。
??最好的做法是單獨構建字符串,然后再一次性地將結果字符串賦值給 innerHTML,像下面這樣:

var itemsHtml = "";
for (var i=0, len=values.length; i < len; i++){
    itemsHtml += "<li>" + values[i] + "</li>";
}
ul.innerHTML = itemsHtml;

??這個例子的效率要高得多,因為它只對 innerHTML 執行了一次賦值操作。

3.7、scrollIntoView() 方法

??如何滾動頁面也是 DOM 規范沒有解決的一個問題。為了解決這個問題,瀏覽器實現了一些方法,以方便開發人員更好地控制頁面滾動。
??在各種專有方法中,HTML5 最終選擇了 scrollIntoView() 作為標準方法。
??scrollIntoView() 可以在所有 HTML 元素上調用,通過滾動瀏覽器窗口或某個容器元素,調用元素就可以出現在視口中。
??如果給這個方法傳入 true 作為參數,或者不傳入任何參數,那么窗口滾動之后會讓調用元素的頂部與視口頂部盡可能平齊。如果傳入 false 作為參數,調用元素會盡可能全部出現在視口中,(可能的話,調用元素的底部會與視口底部平齊。)不過頂部不一定平齊,例如:

// 讓元素可見
document.forms[0].scrollIntoView();

??當頁面發生變化時,一般會用這個方法來吸引用戶的注意力。實際上,為某個元素設置焦點也會導致瀏覽器滾動并顯示出獲得焦點的元素。
??支持 scrollIntoView()方法的瀏覽器有 IE、Firefox、Safari 和 Opera。

4、專有擴展

??雖然所有瀏覽器開發商都知曉堅持標準的重要性,但在發現某項功能缺失時,這些開發商都會一如既往地向 DOM 中添加專有擴展,以彌補功能上的不足。
??表面上看,這種各行其事的做法似乎不太好,但實際上專有擴展為 Web 開發領域提供了很多重要的功能,這些功能最終都在 HTML5 規范中得到了標準化。
??即便如此,仍然還有大量專有的 DOM 擴展沒有成為標準。但這并不是說它們將來不會被寫進標準,而只是說在編寫本書的時候,它們還是專有功能,而且只得到了少數瀏覽器的支持。

4.1、文檔模式

??IE8 引入了一個新的概念叫“文檔模式”(document mode)。頁面的文檔模式決定了可以使用什么功能。
??換句話說,文檔模式決定了你可以使用哪個級別的 CSS,可以在 JavaScript 中使用哪些 API,以及如何對待文檔類型(doctype)。
??到了 IE9,總共有以下 4 種文檔模式。

  • IE5:以混雜模式渲染頁面(IE5 的默認模式就是混雜模式)。IE8 及更高版本中的新功能都無法使用。
  • IE7:以 IE7 標準模式渲染頁面。IE8 及更高版本中的新功能都無法使用。
  • IE8:以 IE8 標準模式渲染頁面。IE8 中的新功能都可以使用,因此可以使用 Selectors API、更多 CSS2 級選擇符和某些 CSS3 功能,還有一些 HTML5 的功能。不過 IE9 中的新功能無法使用。
  • IE9:以 IE9 標準模式渲染頁面。IE9 中的新功能都可以使用,比如 ECMAScript 5、完整的 CSS3 以及更多 HTML5 功能。這個文檔模式是最高級的模式。

??要理解 IE8 及更高版本的工作原理,必須理解文檔模式。
要強制瀏覽器以某種模式渲染頁面,可以使用 HTTP 頭部信息 X-UA-Compatible,或通過等價的 <meta>標簽來設置:

<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">

??注意,這里 IE 的版本(IEVersion)有以下一些不同的值,而且這些值并不一定與上述 4 種文檔模式對應。

  • Edge:始終以最新的文檔模式來渲染頁面。忽略文檔類型聲明。對于 IE8,始終保持以 IE8 標準模式渲染頁面。對于 IE9,則以 IE9 標準模式渲染頁面。
  • EmulateIE9:如果有文檔類型聲明,則以 IE9 標準模式渲染頁面,否則將文檔模式設置為 IE5。
  • EmulateIE8:如果有文檔類型聲明,則以 IE8 標準模式渲染頁面,否則將文檔模式設置為 IE5。
  • EmulateIE7:如果有文檔類型聲明,則以 IE7 標準模式渲染頁面,否則將文檔模式設置為 IE5。
  • 9:強制以 IE9 標準模式渲染頁面,忽略文檔類型聲明。
  • 8:強制以 IE8 標準模式渲染頁面,忽略文檔類型聲明。
  • 7:強制以 IE7 標準模式渲染頁面,忽略文檔類型聲明。
  • 5:強制將文檔模式設置為 IE5,忽略文檔類型聲明。

??比如,要想讓文檔模式像在 IE7 中一樣,可以使用下面這行代碼:

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">

??如果不打算考慮文檔類型聲明,而直接使用 IE7 標準模式,那么可以使用下面這行代碼:

<meta http-equiv="X-UA-Compatible" content="IE=7">

??沒有規定說必須在頁面中設置 X-UA-Compatible。默認情況下,瀏覽器會通過文檔類型聲明來確定是使用最佳的可用文檔模式,還是使用混雜模式。
??通過 document.documentMode 屬性可以知道給定頁面使用的是什么文檔模式。這個屬性是 IE8 中新增的,它會返回使用的文檔模式的版本號(在 IE9 中,可能返回的版本號為 5、7、8、9):

var mode = document.documentMode;

??知道頁面采用的是什么文檔模式,有助于理解頁面的行為方式。無論在什么文檔模式下,都可以訪問這個屬性。

4.2、children 屬性

??由于 IE9 之前的版本與其他瀏覽器在處理文本節點中的空白符時有差異,因此就出現了 children 屬性。
??這個屬性是 HTMLCollection 的實例,只包含元素中同樣還是元素的子節點。除此之外,children 屬性與 childNodes 沒有什么區別,即在元素只包含元素子節點時,這兩個屬性的值相同。
??下面是訪問 children 屬性的示例代碼:

var childCount = element.children.length;
var firstChild = element.children[0];

??支持 children 屬性的瀏覽器有 IE5、Firefox 3.5、Safari 2(但有 bug)、Safari 3(完全支持)、Opera8 和 Chrome(所有版本)。
??IE8 及更早版本的 children 屬性中也會包含注釋節點,但 IE9 之后的版本則只返回元素節點。

4.3、contains() 方法

??在實際開發中,經常需要知道某個節點是不是另一個節點的后代。
??IE 為此率先引入了 contains() 方法,以便不通過在 DOM 文檔樹中查找即可獲得這個信息。
??調用 contains()方法的應該是祖先節點,也就是搜索開始的節點,這個方法接收一個參數,即要檢測的后代節點。如果被檢測的節點是后代節點,該方法返回 true;否則,返回 false。示例:

console.log(document.documentElement.contains(document.body)); // true

??上述例子測試了<body>元素是不是<html>元素的后代,在格式正確的 HTML 頁面中,以上代碼返回 true。
??支持 contains() 方法的瀏覽器有 IE、Firefox 9+、Safari、Opera 和 Chrome。

??使用 DOM Level 3 compareDocumentPosition() 也能夠確定節點間的關系。
??支持這個方法的瀏覽器有 IE9+、Firefox、Safari、Opera 9.5+和 Chrome。
??如前所述,這個方法用于確定兩個節點間的關系,返回一個表示該關系的位掩碼( bitmask)。下表列出了這個位掩碼的值。

掩碼 節點關系
1 無關(給定的節點不在當前文檔中)
2 居前(給定的節點在DOM樹中位于參考節點之前)
4 居后(給定的節點在DOM樹中位于參考節點之后)
8 包含(給定的節點是參考節點的祖先)
16 被包含(給定的節點是參考節點的后代)

??為模仿 contains() 方法,應該關注的是掩碼 16。可以對 compareDocumentPosition() 的結果執行按位與,以確定參考節點(調用 compareDocumentPosition() 方法的當前節點)是否包含給定的節點(傳入的節點)。示例:

var result = document.documentElement.compareDocumentPosition(document.body);
console.log(!!(result & 16));

??執行上面的代碼后,結果會變成 20(表示“居后”的 4 加上表示“被包含”的 16)。對掩碼 16 執行按位操作會返回一個非零數值,而兩個邏輯非操作符會將該數值轉換成布爾值。
??使用一些瀏覽器及能力檢測,就可以寫出如下所示的一個通用的 contains 函數:

function contains(refNode, otherNode){
    if (typeof refNode.contains == "function" && (!client.engine.webkit || client.engine.webkit >= 522)){
        return refNode.contains(otherNode);
    } else if (typeof refNode.compareDocumentPosition == "function"){
        return !!(refNode.compareDocumentPosition(otherNode) & 16);
    } else {
        var node = otherNode.parentNode;
        do {
            if (node === refNode){
                return true;
            } else {
                node = node.parentNode;
            }
        } while (node !== null); 
            return false;
        }
}

??這個函數組合使用了三種方式來確定一個節點是不是另一個節點的后代。函數的第一個參數是參考節點,第二個參數是要檢查的節點。
??在函數體內,首先檢測 refNode 中是否存在 contains()方法(能力檢測)。這一部分代碼還檢查了當前瀏覽器所用的 WebKit 版本號。如果方法存在而且不是 WebKit(!client.engine.webkit),則繼續執行代碼。否則,如果瀏覽器是 WebKit 且至少是 Safari 3(WebKit版本號為 522 或更高),那么也可以繼續執行代碼。在 WebKit 版本號小于 522 的 Safari 瀏覽器中,contains() 方法不能正常使用。
??接下來檢查是否存在 compareDocumentPosition() 方法,而函數的最后一步則是自 otherNode 開始向上遍歷 DOM 結構,以遞歸方式取得 parentNode,并檢查其是否與 refNode 相等。
??在文檔樹的頂端,parentNode 的值等于 null,于是循環結束。這是針對舊版本 Safari 設計的一個后備策略。

4.4、插入文本

??IE 原來專有的插入標記的屬性 innerHTML 和 outerHTML 已經被 HTML5 納入規范。但另外兩個插入文本的專有屬性則沒有那么好的運氣。
??這兩個沒有被 HTML5 看中的屬性是 innerText 和 outerText。

1. innerText 屬性

??通過 innertText 屬性可以操作元素中包含的所有文本內容,包括子文檔樹中的文本。
??在通過 innerText 讀取值時,它會按照由淺入深的順序,將子文檔樹中的所有文本拼接起來。
??在通過 innerText 寫入值時,結果會刪除元素的所有子節點,插入包含相應文本值的文本節點。示例:

<div id="content">
    <p>This is a <strong>paragraph</strong> with a list following it.</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
</div>

??對于上述例子中的<div>元素而言,其 innerText 屬性會返回下列字符串:

This is a paragraph with a list following it.
Item 1
Item 2
Item 3

??由于不同瀏覽器處理空白符的方式不同,因此輸出的文本可能會也可能不會包含原始 HTML 代碼中的縮進。
??使用 innerText 屬性設置這個<div>元素的內容,則只需一行代碼:

div.innerText = "Hello world!"; 

??執行這行代碼后,頁面的 HTML 代碼就會變成如下所示。

<div id="content">Hello world!</div>

??設置 innerText 屬性移除了先前存在的所有子節點,完全改變了DOM 子樹。
??此外,設置 innerText 屬性的同時,也對文本中存在的 HTML 語法字符(小于號、大于號、引號及和號)進行了編碼。示例:

div.innerText = "Hello & welcome, <b>\"reader\"!</b>";

??運行以上代碼之后,會得到如下所示的結果。

<div id="content">Hello &amp; welcome, &lt;b&gt;&quot;reader&quot;!&lt;/b&gt;</div>

??設置 innerText 永遠只會生成當前節點的一個子文本節點,而為了確保只生成一個子文本節點,就必須要對文本進行 HTML 編碼。
??利用這一點,可以通過 innerText 屬性過濾掉 HTML 標簽。方法是將 innerText 設置為等于 innerText,這樣就可以去掉所有 HTML 標簽,示例:

div.innerText = div.innerText;

??執行這行代碼后,就用原來的文本內容替換了容器元素中的所有內容(包括子節點,因而也就去掉了 HTML 標簽)。
??支持 innerText 屬性的瀏覽器包括 IE4+、Safari 3+、Opera 8+和 Chrome。

??Firefox 雖然不支持 innerText,但支持作用類似的 textContent 屬性。

??textContent 是 DOM Level 3 規定的一個屬性,其他支持 textContent 屬性的瀏覽器還有 IE9+、Safari 3+、Opera 10+ 和 Chrome。為了確保跨瀏覽器兼容,有必要編寫一個類似于下面的函數來檢測可以使用哪個屬性。

function getInnerText(element){
    return (typeof element.textContent == "string") ? element.textContent : element.innerText;
}

function setInnerText(element, text){
    if (typeof element.textContent == "string"){
        element.textContent = text;
    } else {
        element.innerText = text;
    }
} 

??這兩個函數都接收一個元素作為參數,然后檢查這個元素是不是有 textContent 屬性。如果有,那么 typeof element.textContent 應該是"string";如果沒有,那么這兩個函數就會改為使用 innerText。可以像下面這樣調用這兩個函數。

setInnerText(div, "Hello world!");
console.log(getInnerText(div)); // "Hello world!"

??使用這兩個函數可以確保在不同的瀏覽器中使用正確的屬性。

??實際上,innerText 與 textContent 返回的內容并不完全一樣。比如,innerText 會忽略行內的樣式和腳本,而 textContent 則會像返回其他文本一樣返回行內的樣式和腳本代碼。避免跨瀏覽器兼容問題的最佳途徑,就是從不包含行內樣式或行內腳本的 DOM 子樹副本或 DOM 片段中讀取文本。

2. outerText 屬性

??除了作用范圍擴大到了包含調用它的節點之外,outerText 與 innerText 基本上沒有多大區別。
??在讀取文本值時,outerText 與 innerText 的結果完全一樣。
??但在寫模式下,outerText 就完全不同了:outerText 不只是替換調用它的元素的子節點,而是會替換整個元素(包括子節點)。示例:

div.outerText = "Hello world!";

??這行代碼實際上相當于如下兩行代碼:

var text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);

??本質上,新的文本節點會完全取代調用 outerText 的元素。此后,該元素就從文檔中被刪除,無法訪問。
??支持 outerText 屬性的瀏覽器有 IE4+、Safari 3+、Opera 8+和 Chrome。

??由于這個屬性會導致調用它的元素不存在,因此并不常用。我們也建議讀者盡可能不要使用這個屬性。

4.5、滾動

??如前所述,HTML5 之前的規范并沒有就與頁面滾動相關的 API 做出任何規定。但 HTML5 在將 scrollIntoView() 納入規范之后,仍然還有其他幾個專有方法可以在不同的瀏覽器中使用。
??下面列出的幾個方法都是對 HTMLElement 類型的擴展,因此在所有元素中都可以調用。

scrollIntoViewIfNeeded(alignCenter) 方法

??只在當前元素在視口中不可見的情況下,才滾動瀏覽器窗口或容器元素,最終讓它可見。如果當前元素在視口中可見,這個方法什么也不做。
??如果將可選的 alignCenter 參數設置為 true,則表示盡量將元素顯示在視口中部(垂直方向)。Safari 和 Chrome 實現了這個方法。

scrollByLines(lineCount) 方法

??將元素的內容滾動指定的行高,lineCount 值可以是正值,
也可以是負值。Safari 和 Chrome 實現了這個方法。

scrollByPages(pageCount) 方法

??將元素的內容滾動指定的頁面高度,具體高度由元素的高度決定。Safari 和 Chrome 實現了這個方法。

??希望大家要注意的是,scrollIntoView() 和 scrollIntoViewIfNeeded() 的作用對象是元素的容器,而 scrollByLines() 和 scrollByPages() 影響的則是元素自身。

??下面還是來看幾個示例吧。

// 將頁面主體滾動 5 行
document.body.scrollByLines(5); 

// 在當前元素不可見的時候,讓它進入瀏覽器的視口
document.images[0].scrollIntoViewIfNeeded();

// 將頁面主體往回滾動 1 頁
document.body.scrollByPages(-1);

??由于 scrollIntoView() 是唯一一個所有瀏覽器都支持的方法,因此還是這個方法最常用。

小結

??雖然 DOM 為與 XML 及 HTML 文檔交互制定了一系列核心 API,但仍然有幾個規范對標準的 DOM 進行了擴展。這些擴展中有很多原來是瀏覽器專有的,但后來成為了事實標準,于是其他瀏覽器也都提
供了相同的實現。

??本章介紹的三個這方面的規范如下。

  • Selectors API,定義了兩個方法,讓開發人員能夠基于 CSS 選擇符從 DOM 中取得元素,這兩個方法是 querySelector() 和 querySelectorAll()。

  • Element Traversal,為 DOM 元素定義了額外的屬性,讓開發人員能夠更方便地從一個元素跳到另一個元素。之所以會出現這個擴展,是因為瀏覽器處理 DOM 元素間空白符的方式不一樣。

  • HTML5,為標準的 DOM 定義了很多擴展功能。其中包括在 innerHTML 屬性這樣的事實標準基礎上提供的標準定義,以及為管理焦點、設置字符集、滾動頁面而規定的擴展 API。

??雖然目前 DOM 擴展的數量還不多,但隨著 Web 技術的發展,相信一定還會涌現出更多擴展來。很多瀏覽器都在試驗專有的擴展,而這些擴展一旦獲得認可,就能成為“偽”標準,甚至會被收錄到規范的更新版本中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容

  • ??DOM(文檔對象模型)是針對 HTML 和 XML 文檔的一個 API(應用程序編程接口)。 ??DOM 描繪...
    霜天曉閱讀 3,674評論 0 7
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,776評論 1 92
  • 如果說《龍貓》是在大自然中單純地成長的童年回憶,《千與千尋》是在迷茫中追尋自我的少年歷程,那么《起風了》則是...
    歲月的溫室閱讀 11,408評論 0 17
  • 小韓姐,你的女兒真好看,肉嘟嘟的,太美好了,什么時候回來上班,好想你。 你也趕緊的結婚生子,寶寶帶給你的一切超乎你...
    星星_8d4c閱讀 250評論 0 3
  • 之后我們到了黃河,走到黃河畔,俯身下望,河面極其寬闊,但湖水發黃范泡,遠遠望去,只覺是黃土飛揚,在這一望無際中奔騰...
    乄鎭鈊愛妳_d484閱讀 233評論 0 0