转载

纯粹的 JavaScript 对象

最近在 EventEmitter3 源码 中看到了 Object.create(null) ,做一下考证。

传统的 JavaScript 对象

在 JavaScript 中,通常使用对象字面量语法来创建空对象。

var foo = {};             // create new object   foo.bar = 'bar';          // string -> string   foo.baz = function () {}; // string -> function

然而, {} 并非真正的「空」对象:它通过原型链继承了如 hasOwnProperty()toString()valueOf()constructor__proto__ 等 Object 对象 的属性和方法。

var foo = {};   foo.toString();  // "[object Object]"   foo.valueOf();   // Object {}

{} 创建的对象,其原型对象指向 Object.prototype (读者可以在 Chrome 控制台输入 {} 并回车,查看显示的结果)。

var foo = {};   Object.prototype.isPrototypeOf(foo);  // true   foo instanceof Object                 // true   foo.constructor === Object            // true

换言之, {} 对象字面量等价于 Object.create(Object.prototype)

var foo = Object.create(Object.prototype);

糟糕的是,这些继承的属性是隐藏的,即不可 enumerable

foo.propertyIsEnumerable('toString');  // false  for (var property in foo) {     console.log(property); } // nothing is printed

即便是 propertyIsEnumerable 也不可枚举自身。

foo.propertyIsEnumerable('propertyIsEnumerable');  // false

当然,这样设计的初衷是为了区分自有属性和继承自原型链的属性,但 JavaScript 对象有时通过原型链方法进行隐式类型转型,颇令人费解,进而招致骂名。

{}.length         // SyntaxError: Unexpected token . ({}).length       // undefined {} + 1            // 1 {} + {}           // Safari/ Firefox: NaN; Chrome/Node: "[object Object][object Object]" ({} + 1).length   // 16

({} + 1).length 为什么返回 16 呢?

  • 对象与算术表达式结合时,首先调用其 valueOf(){}.valueOf() 通过 Object.prototype.valueOf() 返回其自身;
  • 又回到了对象加数字 1,尝试第二种方式:使用 toString(){}.toString() 通过 Object.prototype.toString() 返回 "[object Object]" ,字符串与 1 相加,得到 "[object Object]1" ,其长度为 16

我们可以通过改写原型方法来追踪此过程( 仅作演示,实践中请勿改写内置对象的原型方法 ):

Object.prototype.valueOf = function() {     console.log('valueOf called'); return this; };  Object.prototype.toString = function() {     console.log('toString called'); return 'xyz'; };  ({} + 1).length; // 4 // valueOf called // toString called // 'xyz1'.length === 4

纯粹的 JavaScript 对象

有可能创建没有继承 Object 原型的、「纯粹」的空对象呢?答案是肯定的。

根据 《You Don't Know JS: this & Object Prototypes》 描述:

Object.create(null) is similar to {} , but without the delegation to Object.prototype , so it's "more empty" than just {} .

var foo = Object.create(null);   foo instanceof Object  // false   foo.constructor        // undefined   foo.toString();        // TypeError: Object [object Object] has no method 'toString'

Object.create 的内部实现如下:

Object.create = function(o) {     function F() {}   F.prototype = o;   return new F(); };

「纯粹」的对象适合用于存储键值对数据,而且没有隐式的类型转换,更加直观。

var foo = Object.create(null);  foo + foo          // TypeError: Cannot convert object to primitive value   (foo + 1).length;  // TypeError: Cannot convert object to primitive value

当然,纯粹对象仅仅是没有继承 Object 原型的属性和方法,其他和普通对象并无二致。

var foo = Object.create(null);  foo.bar = 'bar';   foo['bar']  // returns 'bar'   Object.freeze(foo);   foo.baz = 'baz';  // trying to add new property   // either does nothing silently // or throws TypeError in strict mode foo.getBar = function() { return this.bar; }   foo.getBar(); // returns value 'bar'

因为没有继承,使用 for-in 循环的时候也就不用再检查 hasOwnProperty 了。

var foo = Object.create(null);   foo.bar = 'baz';   for (var property in foo) {     console.log(property); } // prints bar

纯粹 JavaScript 对象在 《Speaking JavaScript》一书中被称为「字典模式」 。

另外,使用 JSON.parse 创建的对象并不「纯粹」:

var foo = JSON.parse('{}');   foo instanceof Object  // true

性能比较

{}Object.create(null) 性能比较如下:

  • 创建性能: {}Object.create(null) 快 20 倍 ,如果创建很多对象,就需要留意一下。猜测原因可能是: {} 空对象只是内存分配和拷贝预填充的对象元数据,而 Object.create(null) 实际上是执行自定义函数创建、填充对象。
  • 存、取性能:基本一致,原型指针并不影响属性存取性能。
  • JSON.stringify 速度:序列化纯粹对象快 大约 3% 左右 ,几乎可以忽略。

原型对象设置

ES2015 允许通过 __proto__ 属性设置原型对象 ,纯粹对象可以设置 __proto__ ,而且显示指向了设置的对象(不同 JavaScript 引擎可能会有差异),但是并没有像预期那样运行。因此,需要设置对象原型时,不能使用纯粹对象。

var foo = Object.create(null);   foo.__proto__ = {     bar: 'bar' }; foo;      // { [__proto__]: { bar: 'bar' } }   foo.bar;  // undefined

小结

Object.create(null) 虽然提供了一种创建「纯粹」对象的方式,但综合性能和兼容问题,似乎找不到太多用的理由。

参考链接

  • Sweet naked objects
  • Creating Objects With A Null Prototype In Node.js
  • Is it bad practice to use Object.create(null) versus {}?
原文  https://csspod.com/naked-javascript-object/
正文到此结束
Loading...