一、每一个JavaScript对象(除了null)都具有一个属性,叫proto,这个属性会指向该对象的原型
function Person() {}var person = new Person();console.log(person.__proto__ === Person.prototype); // true
设置原型
一、
let animal = {eats: true};let rabbit = {jumps: true};rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal
2、现在,如果我们从rabbit中读取一个它没有的属性,JavaScript 会自动从animal中获取。
let animal = {eats: true};let rabbit = {jumps: true};rabbit.__proto__ = animal; // (*)// 现在这两个属性我们都能在 rabbit 中找到:alert( rabbit.eats ); // true // 将animal设置为rabbit的原型alert( rabbit.jumps ); // true
3、当alert试图读取rabbit.eats时,因为它不存在于rabbit中,所以 JavaScript 会顺着[[Prototype]]引用,在animal中查找(自下而上):
(1)在这儿我们可以说 “animal是rabbit的原型”,或者说 “rabbit的原型是从animal继承而来的”。
4、因此,如果animal有许多有用的属性和方法,那么它们将自动地变为在rabbit中可用。这种属性被称为“继承”。
5、如果我们在animal中有一个方法,它可以在rabbit中被调用:
let animal = {eats: true,walk() {alert("Animal walk");}};let rabbit = {jumps: true,__proto__: animal};// walk 方法是从原型中获得的rabbit.walk(); // Animal walk
(1)该方法是自动地从原型中获得的,像这样:
6、原型链可以很长:
let animal = {eats: true,walk() {alert("Animal walk");}};let rabbit = {jumps: true,__proto__: animal};let longEar = {earLength: 10,__proto__: rabbit};// walk 是通过原型链获得的longEar.walk(); // Animal walkalert(longEar.jumps); // true(从 rabbit)
(1)现在,如果我们从longEar中读取一些它不存在的内容,JavaScript 会先在rabbit中查找,然后在animal中查找。
(2)这里只有两个限制:
① 引用不能形成闭环。如果我们试图在一个闭环中分配proto,JavaScript 会抛出错误。
② proto的值可以是对象,也可以是null。而其他的类型都会被忽略。
五、一个对象只能有一个[[Prototype]]。一个对象不能从其他两个对象获得继承。
不推荐使用proto
一、proto被认为是过时且不推荐使用的(deprecated),这里的不推荐使用是指 JavaScript 规范中规定,proto必须仅在浏览器环境下才能得到支持。
1、但实际上,包括服务端在内的所有环境都支持它,因此我们使用它是非常安全的。
六、proto是[[Prototype]]的因历史原因而留下来的 getter/setter
1、proto与内部的[[Prototype]]不一样。proto是[[Prototype]]的 getter/setter。
2、proto属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数Object.getPrototypeOf/Object.setPrototypeOf来取代proto去 get/set 原型。
为什么proto不好
原型简史
一、如果我们数一下有多少种处理[[Prototype]]的方式,答案是有很多!很多种方法做的都是同一件事儿!
为什么会出现这种情况?
二、这是历史原因。
- 构造函数的”prototype”属性自古以来就起作用。
- 之后,在 2012 年,Object.create出现在标准中。它提供了使用给定原型创建对象的能力。但没有提供 get/set 它的能力。因此,许多浏览器厂商实现了非标准的proto访问器,该访问器允许用户随时 get/set 原型。
- 之后,在 2015 年,Object.setPrototypeOf和Object.getPrototypeOf被加入到标准中,执行与proto相同的功能。由于proto实际上已经在所有地方都得到了实现,但它已过时,所以被加入到该标准的附件 B 中,即:在非浏览器环境下,它的支持是可选的。
三、目前为止,我们拥有了所有这些方式。
四、如果速度很重要,就请不要修改已存在的对象的[[Prototype]]
1、从技术上来讲,我们可以在任何时候 get/set[[Prototype]]。但是通常我们只在创建对象的时候设置它一次,自那之后不再修改:rabbit继承自animal,之后不再更改。
2、并且,JavaScript 引擎对此进行了高度优化。用Object.setPrototypeOf或obj.proto=“即时”更改原型是一个非常缓慢的操作,因为它破坏了对象属性访问操作的内部优化。因此,除非你知道自己在做什么,或者 JavaScript 的执行速度对你来说完全不重要,否则请避免使用它。
proto会引起的bug
一、我们知道,对象可以用作关联数组(associative arrays)来存储键/值对。
二、但是如果我们尝试在其中存储用户提供的键(例如:一个用户输入的字典),我们可以发现一个有趣的小故障:所有的键都正常工作,除了”proto“。
【示例1】看一下这个例子:
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object],并不是 "some value"!
1、这里如果用户输入proto,那么赋值会被忽略!
2、proto属性很特别:它必须是对象或者null。字符串不能成为 prototype。
(1)我们想要存储键值对,然而键名为”proto“的键值对没有被正确存储。所以这是一个 bug。
3、在这里,后果并没有很严重。但是在其他情况下,我们可能会对对象进行赋值操作,然后原型可能就被更改了。结果,可能会导致完全意想不到的结果。
4、最可怕的是 —— 通常开发者完全不会考虑到这一点。这让此类 bug 很难被发现,甚至变成漏洞,尤其是在 JavaScript 被用在服务端的时候。
【示例2】为默认情况下为函数的toString以及其他内建方法执行赋值操作,也会出现意想不到的结果。
避免这样的问题的方式
Map
一、首先,我们可以改用Map来代替普通对象进行存储,这样一切都迎刃而解。
Object
一、但是Object在这里同样可以运行得很好,因为 JavaScript 语言的制造者很早就注意到了这个问题。
二、proto不是一个对象的属性,只是Object.prototype的访问器属性:
1、因此,如果obj.proto被读取或者赋值,那么对应的 getter/setter 会被从它的原型中调用,它会 set/get[[Prototype]]。
(1)当使用obj.proto时,可以理解成返回了Object.getPrototypeOf(obj)
三、proto是一种访问[[Prototype]]的方式,而不是[[prototype]]本身。
Object.create(null) -> “very plain” objects
一、现在,我们想要将一个对象用作关联数组,并且摆脱此类问题,我们可以通过Object.create(null)来创建没有原型的对象:
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)创建了一个空对象,这个对象没有原型([[Prototype]]是null):
三、因此,它没有继承proto的 getter/setter 方法。现在,它被作为正常的数据属性进行处理,因此上面的这个示例能够正常工作。
四、我们可以把这样的对象称为 “very plain” 或 “pure dictionary” 对象,因为它们甚至比通常的普通对象(plain object){…}还要简单。
1、对于”very plain”对象而言,使用”proto“作为键是没有问题的。
五、缺点是这样的对象没有任何内建的对象的方法,例如toString:
let obj = Object.create(null);
alert(obj); // Error (no toString)
六、但是它们通常对关联数组而言还是很友好。
七、请注意,大多数与对象相关的方法都是Object.something(…),例如Object.keys(obj)—— 它们不在 prototype 中,因此在 “very plain” 对象中它们还是可以继续使用:
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";
alert(Object.keys(chineseDictionary)); // hello,bye
