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,这种新属性会非常低效。

这些变更对于 Web 平台的一致性、性能和标准化非常重要,但可能给开发者带来一些问题。如果您之前依赖 Chrome 和 WebKit 之间的旧版兼容性,我们建议您查看一下自己的网站,并在下方查看更改摘要。

变更摘要

现在,对 DOM 对象实例使用 hasOwnProperty 将返回 false

有时,开发者会使用 hasOwnProperty 来检查对象中是否存在某个属性。根据规范,此函数不再有效,因为 DOM 属性现在是原型链的一部分,而 hasOwnProperty 仅检查当前对象以查看其上是否已定义。

在 Chrome 42 之前(且包含 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 继承自 HTMLElement,后者定义了 isContentEditable 属性。

> 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 雷达上提交问题,并参考 https://bugs.webkit.org/show_bug.cgi?id=49739

我通常有兴趣遵循此更改