原型鏈結已推出 DOM 屬性

Chrome 團隊最近宣布我們將將 DOM 屬性移至原型鏈。本次異動在 Chrome 43 執行 - (自 2015 年 4 月中旬起實施的 Beta 版測試) 讓 Chrome 更貼近 Web IDL 規格和其他瀏覽器的實作方式,例如 IE 和 Firefox。編輯:說明:舊版 WebKit 型瀏覽器目前與規格不相容,但 Safari 現已與此規格相容。

新行為在許多方面都展現正面價值。簡要說明如下:

  • 透過符合規格要求提升網路相容性 (目前 IE 和 Firefox 已經這樣做)。
  • 能讓您以一致且有效率的方式,為每個 DOM 物件建立 getter/setter。
  • 提高 DOM 程式設計的可入侵性。舉例來說,您可以實作 polyfill,以快速模擬某些瀏覽器和 JavaScript 程式庫中缺少的功能,而這些程式庫會覆寫預設的 DOM 屬性行為。

舉例來說,假設的 W3C 規格包含一些名為 isSuperContentEditable 的新功能,但 Chrome 瀏覽器並未實作此功能,但您可以使用「polyfill」或使用程式庫模擬此功能。身為程式庫開發人員,您自然會想要使用 prototype 來建立有效的 polyfill:

Object.defineProperty(HTMLDivElement.prototype, "isSuperContentEditable", {
    get: function() { return true; },
    set: function() { /* some logic to set it up */ },
});

在這項變更之前,為了與 Chrome 中其他 DOM 屬性一致,你得在每個執行個體中建立新的屬性,因此網頁上每個 HTMLDivElement 的效率都會很低。

這些變更對於網路平台的一致性、效能和標準化非常重要,但也可能會造成開發人員了一些問題。如果您因為 Chrome 和 WebKit 的舊版相容性而仰賴這項行為,建議您檢查自己的網站,並查看下方的異動摘要。

異動內容摘要

現在對 DOM 物件執行個體使用 hasOwnProperty 會傳回 false

開發人員有時會使用 hasOwnProperty 來檢查物件上是否有屬性。這個做法無法根據規格運作,因為 DOM 屬性現在是原型鏈鏈的一部分,而 hasOwnProperty 只會檢查目前的物件,確認是否已在其中定義。

在 Chrome 42 (含) 以下版本中,以下函式會傳回 true

> div = document.createElement("div");
> div.hasOwnProperty("isContentEditable");

true

Chrome 43 以上版本會傳回 false

> div = document.createElement("div");
> div.hasOwnProperty("isContentEditable");

false

現在,如果您要檢查元素是否提供 isContentEditable,即可檢查 HTMLElement 物件的原型。舉例來說,HTMLDivElement 繼承自定義 isContentEditable 屬性的 HTMLElement

> HTMLElement.prototype.hasOwnProperty("isContentEditable");

true

您並未受鎖定,無法使用「hasOwnProperty」。建議您使用更簡單的 in 運算元,因為這麼做會在整個原型鏈結上檢查屬性。

if("isContentEditable" in div) {
    // We have support!!
}

DOM 物件執行個體上的 Object.getOwnPropertyDescriptor 不會再傳回 Attributes 屬性描述元

如果您的網站需要取得 DOM 物件上屬性的屬性描述元,現在就需要遵循原型鏈。

如果想取得 Chrome 42 以下版本的屬性說明,請按下列步驟操作:

> Object.getOwnPropertyDescriptor(div, "isContentEditable");

Object {value: "", writable: true, enumerable: true, configurable: true}

Chrome 43 以上版本將傳回 undefined

> Object.getOwnPropertyDescriptor(div, "isContentEditable");

undefined

也就是說,如要取得 isContentEditable 屬性的屬性描述元,您必須遵循原型鏈結,如下所示:

> Object.getOwnPropertyDescriptor(HTMLElement.prototype, "isContentEditable");

Object {get: function, set: function, enumerable: false, configurable: false}

JSON.stringify 不會再序列化 DOM 屬性

JSON.stringify 不會序列化原型上的 DOM 屬性。舉例來說,如果您嘗試將物件 (例如推播通知的 PushSubscription) 進行序列化,你的網站就會受到影響。

Chrome 42 以下版本適用的功能如下:

> JSON.stringify(subscription);

{
    "endpoint": "https://something",
    "subscriptionId": "SomeID"
}

Chrome 43 以上版本不會序列化在原型上定義的屬性,而且您將傳回空白物件。

> JSON.stringify(subscription);

{}

您必須提供自己的序列化方法,例如:

function stringifyDOMObject(object)
{
    function deepCopy(src) {
        if (typeof src != "object")
            return src;
        var dst = Array.isArray(src) ? [] : {};
        for (var property in src) {
            dst[property] = deepCopy(src[property]);
        }
        return dst;
    }
    return JSON.stringify(deepCopy(object));
}
var s = stringifyDOMObject(domObject);

在嚴格模式中寫入唯讀屬性將擲回錯誤

使用嚴格模式寫入唯讀屬性時,應擲回例外狀況。例如,請採取下列做法:

function foo() {
    "use strict";
    var d = document.createElement("div");
    console.log(d.isContentEditable);
    d.isContentEditable = 1;
    console.log(d.isContentEditable);
}

Chrome 42 以下版本執行函式時,系統會持續以無訊息的方式執行函式,不過 isContentEditable 並未變更。

// Chrome 42 and earlier behavior
> foo();

false // isContentEditable
false // isContentEditable (after writing to read-only property)

現在,Chrome 43 以上版本會擲回例外狀況。

// Chrome 43 and onwards behavior
> foo();

false
Uncaught TypeError: Cannot set property isContentEditable of #<HTMLElement> which has only a getter

我遇到問題,該怎麼辦?

請按照指南進行,或是在下方留言,與我們交流。

我發現某個網站有問題,該怎麼辦?

好問題大多數網站問題都是因為網站選擇使用 getOwnProperty 方法偵測屬性狀態開發人員可以採取下列幾項行動:

  • 透過 Chrome 的 Issue Tracker 回報受影響網站的問題
  • 提出 WebKit radar 問題,並附上 https://bugs.webkit.org/show_bug.cgi?id=49739

我有意因應這項異動