原型鏈結已推出 DOM 屬性

Chrome 團隊最近宣布將 DOM 屬性移至原型鏈結。這項變更已在 Chrome 43 (2015 年 4 月中旬的 Beta 版) 中實作,讓 Chrome 更符合 Web IDL 規格和其他瀏覽器 (例如 IE 和 Firefox) 的實作方式。編輯:已澄清 舊版 WebKit 瀏覽器目前與規格不相容,但 Safari 已相容。

這項新行為在許多方面都具有正面影響。其中包括:

  • 遵循規格,改善網頁的相容性 (IE 和 Firefox 已實現此目標)。
  • 可讓您在每個 DOM 物件上一致且有效率地建立 getter/setter。
  • 提高 DOM 程式設計的駭客攻擊可能性。舉例來說,您可以實作 polyfill,藉此有效模擬某些瀏覽器和 JavaScript 程式庫中缺少的功能,這些瀏覽器和 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 將不再傳回屬性屬性描述項

如果您的網站需要取得 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 方法進行屬性存在偵測,而這類情況通常是網站擁有者只指定舊版 WebKit 瀏覽器時才會發生。開發人員可以採取下列幾種行動:

  • 在 Chrome 問題追蹤工具中,針對受影響的網站回報問題
  • 在 WebKit Radar 上提出問題,並參考 https://bugs.webkit.org/show_bug.cgi?id=49739

我對這項變更的一般動向感興趣