本节我们主要说明额外的方法使用原型。
除了我们已经知道的方式,也有其他的方式get/set原型。
Object.create(proto[, descriptors])
– 使用给定的原型和可选的属性描述符创建一个空对象。Object.getPrototypeOf(obj)
– 返回对象原型[[Prototype]].Object.setPrototypeOf(obj, proto)
– 设置对象的原型[[Prototype]] 引用 proto.举例:
let animal = {
eats: true
};
// create a new object with animal as a prototype
let rabbit = Object.create(animal);
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
方法Object.create
第二个参数是可选的:属性描述符。我们可以提供额外的属性给新对象,如下代码:
let animal = {
eats: true
};
let rabbit = Object.create(animal, {
jumps: {
value: true
}
});
alert(rabbit.jumps); // true
描述符与我们之前章节描述格式一样,我们能用Object.create方法执行一个完整对象克隆,像这样:
// fully identical shallow clone of obj
let clone = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
这个调用完全克隆一个对象拷贝,包括所有属性:枚举和非枚举,数据属性及setter/getter——所有内容,并带有正确的原型。但当然不是深复制。
如果我们数以下管理原型的方式,太多了,有太多的方式做相同的事情!
为什么如此?那是历史原因。
Object.create()
方法在标准中出现,允许创建对象是指定原型,但不允许get/set,所以浏览器实现了非标准的__proto__
访问器,允许在任何时候get/set原型。Object.setPrototypeOf
和Object.getPrototypeOf
被增加至标准中。__proto__
是事实实现无处不在,所以它是附录B的标准,对非浏览器环境是可选的。现在我们有了所有的方法处理原型。
技术上,我们能get/set原型在任何时候,但通常我们仅在对象创建时设置一次,然后不再修改:rabbit继承自animal,无需改变。
Javascript引擎高度优化了这里,使用Object.setPrototypeOf
速度飞快,同样obj.__proto__ =
实现同样功能,速度很慢,但可以。
我们知道,对象可以作为关联数组存储键值对。
但是,我们尝试存储“用户提供键”(举例,用户输入字典),我们能发现一个有趣的错误,即除了”proto“键,其他所有键都可以。
示例说明:
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object], not "some value"!
这里,如果用户输入__proto__
,那么赋值被忽略!
我们不应该对此惊讶,__proto__
属性是特殊的:他必须是一个对象或null,字符串不能称为原型。
但我们不打算实现这样行为,对吧?我们想去存储键值对,并且键名称为__proto__
,也不能被存储。
所以这是一个bug,这里的后果并不可怕。但在其他情况原型可能确实被改变,所以执行出错可能是完全意料之外的。
最糟糕的是,通常开发者根本不考虑这些,让这些错误很难注意到,导致存在漏洞,特别在服务器段Javascript。
这中情况仅发生在__proto__
属性,其他属性被正常复杂。如何规避这个问题?
首先,我们能切换使用Map,则一切都好了。
但Object也能实现,因为语言的创建者在很早以前就考虑了这个问题。
__proto__
不是对象的一个属性,但能通过Object.prototype属性访问器访问。
所以,如果读取或赋值obj.__proto__
,原型中相应的setter/getter方法被调用,并设置或获得[[Prototype]]属性。
如前所说,obj.__proto__
是访问[[prototype]]
的一种方式,不是[[prototype]]
自身。
现在如果我们想使用对象作为关联数组,我们可以用点小技巧实现:
let obj = Object.create(null);
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Object.create(null) 创建一个空的对象,没有指定原型(原型为null)。
所以,没有从obj.__proto__
继承getter/setter方法。现在处理属性作为普通数据,所以上述示例正常运行。
我们能称这样的对象为“非常普通的对象”或“纯字典对象”,因为它们比一般普通对象{...}
更简单。
缺点是这样对象缺省任何内置方法,如toString
:
let obj = Object.create(null);
alert(obj); // Error (no toString)
但对关联数组来说,这通常是好事。
请注意,大多数对象相关的方法是Object.something(...)
,象Object.keyes(obj)
——它们不在原型中,因此它们在普通对象上仍然可以使用:
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";
alert(Object.keys(chineseDictionary)); // hello,bye
有许多方式从对象上获得键/值。我们已经知道这些是:
Object.keys(obj)
/ Object.values(obj)
/ Object.entries(obj)
– 返回一个数组,包括枚举属性字符串名称/值/键-值对。这些方法仅列出可枚举属性,并且使用字符串做为键的属性。获取符号属性:
获取非枚举属性:
获取所有属性s:
这些方法返回的属性有一些差异,但所有这些方法作用在对象上,原型中的属性并没有被列出。
循环for..in
不一样,也会列出继承属性,示例:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// only own keys
alert(Object.keys(rabbit)); // jumps
// inherited keys too
for(let prop in rabbit) alert(prop); // jumps, then eats
如果我们想区分继承属性,有个内置的方法obj.hasOwnProperty(key)
,如果obj有自己属性(不是继承的)则返回true。
所以我们能过滤出继承属性:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false
}
这里我们看下面继承链:rabbit,animal,然后Object.prototype(因为animal是普通对象{…},所以这时缺省的),最后是null:
注意,有个有趣的事情,rabbit.hasOwnProperty方法来自哪里?观察原型链,能看出Object.prototype.hasOwnProperty,也就是说,是继承来的。
但为什么hasOwnProperty没有出现在for..in的结果中,如果其列出所有继承属性?
答案是简单的:它是非枚举的,就想所有Object.prototype中的其他属性。所以没有被列出来。
这里简要列出本文讨论的方法,作为重述内容。
Object.create(proto[, descriptors])
创建一个空对象,使用参数proto作为其原型(也可以为null),另外还有一个可选的描述符属性参数。
Object.getPrototypeOf(obj)
返回对象的[[Prototype]](与proto获取一样)。
Object.setPrototypeOf(obj, proto)
设置proto给对象的[[Prototype]](与proto 设置一样)
Object.keys(obj) / Object.values(obj) / Object.entries(obj)
返回一个数组,可枚举属性字符名称/值/键值对。
Object.getOwnPropertySymbols(obj)
返回所有符合属性名称的数组。
Object.getOwnPropertyNames(obj)
返回所有拥有的字符串属性名称数组。
Reflect.ownKeys(obj)
返回所有拥有的字符串属性名称数组。
obj.hasOwnProperty(key)
返回true,如果对象有该属性(非继承),反之为false。
我们澄清:
proto是[[Prototype]]的一个getter/setter,象其他方法一样,驻留在Object.prototype中。
我们可以创建一个对象通过Object.create(null),这样对象我们通常称为“纯字典”,他们使用”proto“作为键没有问题。
所有返回对象属性的方法(想Object.keys和其他),返回自己的属性。如果我们想获得继承的属性,那么需使用for...in
。