ES6对象字面量进阶:__proto__、简写方法与Super关键字详解
本文详解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 对象字面量的资料请关注本站其它相关文章!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
