ES6对象字面量进阶:__proto__、简写方法与Super关键字详解

作者:闭包旅人时间:2026-10-25 01:36:07浏览:480

本文详解ES6对对象字面量的改进,包括使用__proto__直接设置原型、简写方法定义语法、在简写方法中使用super访问继承属性,以及计算属性名和剩余/展开属性的用法,帮助开发者更高效地构建JavaScript对象。

前言

在 ES6 发布之前,JavaScript 中的对象字面量(即对象初始化器)功能相对基础,主要支持两种属性定义方式:

  • 键值对{name1: value1}
  • 计算属性值,包括获取器(getter){ get name() { ... } }和设置器(setter){ set name(val) { ... } }。

var myObject = {
  myString: 'value 1',
  get myNumber() {
    return this._myNumber;
  },
  set myNumber(value) {
    this._myNumber = Number(value);
  },
};
myObject.myString; // => 'value 1'
myObject.myNumber = '15';
myObject.myNumber; // => 15

JavaScript 是一种基于原型的语言,万物皆对象。为了便于对象的创建、配置及原型访问,语言需要提供一种简洁的构造机制。

在对象创建时设置其原型是一个常见需求,最理想的方式是在对象字面量中通过一条语句直接指定原型。

然而,早期的字面量语法存在局限,无法直接通过简单方式实现这一目标,必须结合 Object.create() 函数与对象字面量配合使用才能设置原型。


var myProto = {
  propertyExists: function(name) {
    return name in this;
  }
};

var myNumbers = Object.create(myProto);
myNumbers['arrat'] = [1, 6, 7];
myNumbers.propertyExists('array'); // => true
myNumbers.propertyExists('collection'); // => false

这种折中方案显得不够灵活。既然 JavaScript 基于原型,为何创建带原型的对象如此繁琐?

幸运的是,JavaScript 语言在不断演进,许多令人困扰的问题正逐步得到解决。

本文将介绍 ES6 如何解决上述问题,并通过新增功能优化对象字面量的使用体验。

  • 在对象构造中设置原型
  • 方法的简写声明
  • Super 关键字的调用
  • 计算属性名

1. 在对象构造中设置原型

众所周知,访问现有对象原型的一种方式是使用 __proto__ 属性:


var myObject = {
  name: 'Hello World!',
};
myObject.__proto__; // => {}
myObject.__proto__.isPrototypeOf(myObject); // => true

myObject.__proto__ 返回该对象的原型对象。

需要注意的是,官方并不建议将 object.__proto__ 用作 getter/setter。推荐的替代方案是使用 Object.getPrototypeOf() 和 Object.setPrototypeOf()。

ES6 允许在对象字面量中使用 __proto__ 作为属性名,并通过 { __proto__: protoObject } 的形式直接设置原型。

接下来,我们利用 __proto__ 属性进行对象初始化,对前述代码进行优化:


var myProto = {
  propertyExists: function(name) {
    return name in this;
  },
};
var myNumbers = {
  __proto__: myProto,
  array: [1, 6, 7],
};
myNumbers.propertyExists('array'); // => true
myNumbers.propertyExists('collection'); // => false

myNumbers 对象通过特殊属性名 __proto__ 关联到原型 myProto。此次仅需一条语句即可完成创建,无需再调用 Object.create() 等额外函数。

由此可见,使用 __proto__ 进行编码十分简洁,这种简单明了的解决方案备受推崇。

此处稍作延伸。令人费解的是,看似简单灵活的解决方案往往需要复杂的设计与实现。如果方案足够简单,设计过程理应轻松;反之亦然:

  • 实现简单明了的效果往往涉及复杂的内部逻辑。
  • 而将其设计得复杂晦涩则相对容易。

若某项功能看起来过于复杂或难以使用,通常意味着它仍需进一步完善。

1.1 __proto__ 用法的特殊情况

尽管 __proto__ 用法直观,但仍需注意几种特殊情况。

在对象字面量中,__proto__ 属性只能出现一次,否则 JavaScript 引擎会报错:


var object = {
  __proto__: {
    toString: function() {
      return '[object Numbers]'
    }
  },
  numbers: [1, 5, 89],
  __proto__: {
    toString: function() {
      return '[object ArrayOfNumbers]'
    }
  }
};

上述示例在对象字面量中重复使用了两次 __proto__ 属性,这是被禁止的。此时将抛出 SyntaxError: Duplicate __proto__ fields are not allowed in object literals 错误。

JavaScript 规定,__proto__ 属性的值只能是对象或 null。若使用原始类型(如字符串、数字、布尔值)或 undefined,该值将被忽略,且对象的原型不会发生更改。


var objUndefined = {
  __proto__: undefined,
};
Object.getPrototypeOf(objUndefined); // => {}
var objNumber = {
  __proto__: 15,
};
Object.getPrototypeOf(objNumber); // => {}

例如,对象字面量尝试使用 undefined 和数字 15 设置 __proto__ 值。由于仅允许对象或 null 作为原型,这些值被忽略,objUndefined 和 objNumber 仍保持其默认原型:纯 JavaScript 对象 {}。

当然,尝试使用基本类型来设置对象原型在逻辑上也是不合常理的。

当对象字面量中包含计算结果为 '__proto__' 的字符串键时,如 { ['__proto__']: protoObj },此时创建的属性不会修改对象的原型,而是作为一个拥有键 '__proto__' 的普通属性存在。

2. 简写方法定义

可以使用更短的语法在对象常量中声明方法,省略 function 关键字和冒号。这被称为简写方法定义。

接下来,我们使用简写语法定义一些方法:


var collection = {
  items: [],
  add(item) {
    this.items.push(item);
  },
  get(index) {
    return this.items[index];
  },
};
collection.add(15);
collection.add(3);
collection.get(0); // => 15

一个显著的好处是,以此方式声明的方法会被命名为函数,这对调试非常有用。从上述示例中执行 collection.add.name 将返回函数名称“add”。

3. Super 的使用

JavaScript 的一项有趣改进是支持使用 super 关键字来访问原型链中继承的属性。请看以下示例:


var calc = {
  numbers: null,
  sumElements() {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  },
};
var numbers = {
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements() {
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    }
    return super.sumElements();
  },
};
numbers.sumElements(); // => 17

calc 是 numbers 对象的原型。在 numbers 的 sumElements 方法中,可以使用 super 关键字从原型访问方法:super.sumElements()。

总之,super 是访问对象原型链中继承属性的快捷方式。

在前面的示例中,若尝试直接执行 calc.sumElements() 调用原型,将会报错。然而,super.sumElements() 可以正确执行,因为它访问的是对象的原型链,并确保原型中的 sumElements() 方法使用 this.numbers 正确访问数组。

super 的存在清晰地表明正在使用继承的属性。

3.1 Super 使用限制

super 关键字只能在对象字面量的简写方法定义内部使用。

如果试图从普通方法声明 { name: function() {} } 中访问它,JavaScript 将抛出错误:


var calc = {
  numbers: null,
  sumElements() {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  },
};
var numbers = {
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements: function() {
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    }
    return super.sumElements();
  },
};
// Throws SyntaxError: 'super' keyword unexpected here
numbers.sumElements();

方法 sumElements 被定义为属性:sumElements: function() { ... }。由于 super 仅能在简写方法中使用,此时调用它会抛出 SyntaxError: 'super' keyword unexpected here。

此限制在很大程度上不影响对象字面量的声明方式。鉴于简写语法更为简洁,通常建议优先使用简写方法定义。

4. 计算属性名

在 ES6 之前,对象初始化通常使用字面量形式,属性名多为静态字符串。若要创建具有计算名称的属性,必须使用属性访问器。


function prefix(prefStr, name) {
  return prefStr + '_' + name;
}
var object = {};
object[prefix('number', 'pi')] = 3.14;
object[prefix('bool', 'false')] = false;
object; // => { number_pi: 3.14, bool_false: false }

当然,这种定义属性的方式是可行的。

接下来使用简写方式改写上述示例:


function prefix(prefStr, name) {
  return prefStr + '_' + name;
}
var object = {
  [prefix('number', 'pi')]: 3.14,
  [prefix('bool', 'false')]: false,
};
object; // => { number_pi: 3.14, bool_false: false }

[prefix('number', 'pi')] 通过计算 prefix('number', 'pi') 表达式(即 'number_pi')来设置属性名称。

同理,[prefix('bool', 'false')] 将第二个属性名称设置为 'bool_false'。

4.1 Symbol 作为属性名称

Symbol 也可用作计算的属性名。只需确保将其包含在方括号中即可:{ [Symbol('name')]: 'Prop value' }。

例如,使用特殊属性 Symbol.iterator 来迭代对象自身的属性名称。如下示例所示:


var object = {
   number1: 14,
   number2: 15,
   string1: 'hello',
   string2: 'world',
   [Symbol.iterator]: function *() {
     var own = Object.getOwnPropertyNames(this),
       prop;
     while(prop = own.pop()) {
       yield prop;
     }
   }
}
[...object]; // => ['number1', 'number2', 'string1', 'string2']

[Symbol.iterator]: function *() { } 定义了一个用于迭代对象自有属性的属性。展开运算符 [... object] 利用迭代器并返回自有属性列表。

5. 剩余和展开属性

剩余属性(Rest properties)允许在解构赋值后收集剩余的属性。

以下示例展示了在解构对象后收集剩余属性的操作:


var object = {
  propA: 1,
  propB: 2,
  propC: 3,
};
let { propA, ...restObject } = object;
propA; // => 1
restObject; // => { propB: 2, propC: 3 }

展开属性(Spread properties)允许将源对象的自有属性复制到对象字面量中。在此示例中,对象字面量从源对象收集其他属性:


var source = {
  propB: 2,
  propC: 3,
};
var object = {
  propA: 1,
  ...source,
};
object; // => { propA: 1, propB: 2, propC: 3 }

6. 总结

在 ES6 中,即使是对象字面量这一相对较小的结构也得到了显著改进。

现在可以使用 __proto__ 属性名直接从初始化器设置对象原型,这比使用 Object.create() 更加便捷。

请注意,__proto__ 是 ES6 标准附录 B 的一部分,官方不鼓励使用。该附录实现对于浏览器是必需的,但对于其他环境则是可选的。NodeJS 4、5 和 6 支持此功能。

现在方法声明形式更短,无需输入 function 关键字。在简写方法中,可以使用 super 关键字,轻松访问对象原型链中的继承属性。

如果属性名称是在运行时计算的,现在可以使用计算属性名 [expression] 来初始化对象。

以上就是详解 JS 中的对象字面量的详细内容,更多关于 JS 对象字面量的资料请关注本站其它相关文章!

作者最新文章