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 来说都非常低效。

这些更改对于 Web 平台的一致性、性能和标准化至关重要,但可能会给开发者带来一些问题。如果您之前之所以依赖此行为,是因为 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

我对此变更普遍感兴趣