☚ GZOOL Quality Without A Name

Object.create(null) 的用法

Object.create(null),返回一个新创建的对象,它的原型是 null,意味着它没有继承任何对象。

本文要讨论的问题是:什么时候才需要用到这种对象?

Object.create(null) vs {}

当使用语句 var obj = {}; 创建对象时,它其实并不是一个真的“空对象”,它从 Object.prototype 上继承了一些方法:

那么本文要讨论的问题相当于:创建的对象需不需要用到上面的方法?

我们先来看下这些方法。

hasOwnProperty

在使用语句 for(var key in obj) 遍历对象的时候,只能使用 hasOwnProperty 判断 key 是否为 obj 对象自身的属性。

现在可以使用 Object.keys(obj).forEach(…) 来遍历对象。

不能通过 if(obj[property]) 来判断,因为 obj[property] 可能是”假值”,这样 if 条件就不成立。我相信绝大多数人都踩过这个坑。

这里有必要提下 with 语句,它的作用是扩展当前执行环境的作用域链,将 obj 置于作用域链的始端:

with (obj)
  statement

执行语句 statement 时,其中的非限定名(unqualified name)会按作用链查找,先检查它是不是 obj 自身的属性,如果不是,还会往上查找 obj 的原型。所以这里很可能需要使用 hasOwnProperty 方法,或者说需要一个“空对象”。

with 的设计违背了词法作用域,导致程序无法分析 js 代码[1],在 ES6 的 strict mode 中已被禁用。

valueOf

valueOf 很少直接使用。在隐式转换类型时,JavaScript 引擎会调用 valueOf 方法,强制把对象转换成原始值:

var obj = Object.create(null);
obj + 1;
// Uncaught TypeError: Cannot convert object to primitive value
toString、isPrototypeOf 和 propertyIsEnumerable

这几个方法直接使用的情况较少,但自己的代码中不用并不表示别人写的代码不会调用。比如,有些框架可能会调用 toString 方法来判断结果是否为 [object Object]。

结论

因此,我们可以得出结论:当创建的对象只在当前执行环境中使用并且不会用到任何从 Object.prototype 上继承来的方法,也不会将该对象作为其他对象的原型的时候,那么可以使用 Object.create(null)。比如,Map(Dictionary) 对象,遍历这种对象时也无需再使用 hasOwnProperty 方法。

延伸阅读

性能

Object.create(null) 的性能不如 {}[2]

标准

MDN 上 Object.create() 的 Polyfill[3] 没考虑参数为 null 的情况。

Object.prototype 的 __proto__ 属性是存取属性(通过 getter 和 setter 方法),由于绝大多数浏览器都支持这个属性,所以它被加到了 ES6 标准的附录 B[4] 之中。

ES6 附录 B 中的内容也是正式标准,但不是核心标准。这部分的标准主要针对浏览器环境,而其他环境(如 Node.js)是可以选择实现的。

可以通过将对象的 __proto__ 属性设置为 null,达到和 Object.create(null) 一样的效果。所以可以直接使用下面的语句来创建“空对象”。不过性能不如前者[5]

var obj = {__proto__: null}

直接更改对象的原型,在每个浏览器以及 JavaScript 引擎中的性能都很低,推荐使用 Object.create(prototype) 方法。

尽管 __proto__ 属性是标准的,但这只是为了浏览器的兼容性考虑。推荐使用 Object.getPrototypeOf() 和 Object.setPrototypeOf()。

如果想给对象添加 __proto__ 属性,而又不影响它的原型,则可以使用 ES6 中的“计算属性”:

var obj = {['__proto__']: null};
console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
console.log(Object.keys(obj)) // ['__proto__']

计算属性(computed property)是指该属性也需要求值。它使用 [] 语法,和对象的取值方式 obj[property] 类似。

兼容性

不支持 __proto__ 属性的浏览器,可以将 Object.prototype 上的方法和属性都删除,这样就能得到“空对象”。当然,我们不能直接在当前执行环境中做这样的操作,由于 iframe 有自己的执行环境,所以可以通过 iframe 来创建“空对象”,具体实现请参考 es-sham[6]


参考资料

[1]: Brendan Eich’s tweet about with statement
[2]: Object.create(null) vs {}
[3]: MDN Object.create()
[4]: ES6 标准的附录 B
[5]: Object.create(null) vs {__proto__: null}
[6]: es-sham __proto__

comments powered by Disqus