MZhou's blog

原型链上的DOM Attributes

本文翻译自html5rocks的DOM Attributes now on the prototype chain

Chrome开发小组最近发表声明他们正在將DOM properties移动到原型链中。这个更新将会在Chrome 43(2015年4月发布beta版本)中实现。这可以让Chrome与Web IDL标准以及其他浏览器(IE和Firfox)保持一致。注:旧的基于Webkit的浏览器与标准不兼容但是safari已经与标准兼容了。

注意:本篇文章中Attribute与Property没有区别,并可以互换。ECMA script标准定义了Properties包括Attributes。一个JS property等于一个WebIDL Attribute。本文里面的Attribute并不是HTML Attribute(例如image元素上class)。

这项更新有很多好处:

例如,假设有一个W3C规范规定的新属性叫做isSuperContentEditable,并且Chrome还没有实现它。但是我们可以实现一个polyfill去模拟这个特性。作为一个库的开发者,很自然地你会想到修改prototype来实现这个属性,这样更加高效。

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

在这次更新之前,为了在Chrome中与其他DOM属性保持一致,一定要为每个实例增加新的isSuperContentEditable属性。为页面上的每一个HTMLDivElement实例创建新属性是非常低效的。

这些更新对Web平台的一致性、性能和规范化都很重要。当然这也会带来一些不兼容的问题。如果你以前依赖过Chrome或Webkit的这个特性,强烈建议检查下自己的站点并阅读下面的更新总结。

更新总结

在DOM实例上调用hasOwnProperties现在会返回false

有时开发者会调用hasOwnProperties方法来检查属性是否某个对象上。以后这将不再起作用。因为DOM属性都移动到了原型链中,而hasOwnProperties方法不会检查原型链上是否有这个属性。

在Chrome 42及以前版本中,如下代码的执行结果为true

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

在Chrome 43及之后的版本中华,将会返回false

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

这意味着如果你想要检查DOM上的isContentEditable属性是否可用,那么需要检查HTMLObject的prototype。例如:HTMLDivElement继承自HTMLElement ,而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不会序列化prototype上的DOM属性。例如,如果你尝试序列化Push Notication的PushSubscription对象,那么你的代码会受到影响。

Chrome 42及以前的版本下,如下代码可以正常工作:

> JSON.stringify(subscription);
{
    "endpoint": "https://something",
    "subscriptionId": "SomeID"
}

Chrome 43及以后的版本中将不会序列化DOM属性,因为他们定义在prototype上。最后只会返回一个空对象。

> 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及以前的版本中修改只读DOM属性不会抛出异常但也不会生效。

// 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方法来做属性支持与否的检测,来兼容旧的浏览器。那么那个站点的开发者可以做如下事情:

我就是对这个改动感兴趣

MZhou's blog - Taste of life.

zmmbreeze / @zhoumm