本文翻译自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的这个特性,强烈建议检查下自己的站点并阅读下面的更新总结。
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实例上的属性描述对象,那么你就需要在原型链中获取了。
在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不会序列化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
方法来做属性支持与否的检测,来兼容旧的浏览器。那么那个站点的开发者可以做如下事情: