🏜 JavaScript 原型方法
__proto__  已经被认为过时且不推荐使用的。在 JavaScript 规范中,proto 必须仅在浏览器环境下才能得到支持。
在 现代 JavaScript 中,使用以下的 原型方法 替代  __proto__  的使用:
Object.create(proto, [descriptors]):利用给定的proto作为[[Prototype]]和可选的 属性描述 创建一个空对象。Object.getPrototypeOf(obj):获取对象obj的原型属性[[Prototype]]。Object.setPrototypeOf(obj, proto):将对象obj的[[Prototype]]设置为proto。
🌰 例子:
let animal = {
  eats: true
}
// 创建以 animal 为原型的对象
let rabbit = Object.create(animal)
// 获取 rabbit 的原型
console.log(Object.getPrototypeOf(rabbit) === animal) // true
// 修改 rabbit 的原型
Object.setPrototypeOf(rabbit, {});
 2
3
4
5
6
7
8
9
10
11
12
🌰 例子 / 使用  Object.create()  实现对象克隆,配合 获取对象的属性描述符:
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
 该克隆对
obj对象进行完全真正准确的拷贝,包括所有的属性:枚举和不可枚举的,数据属性和 setters/getters —— 包括所有内容,并带有正确的[[Prototype]]。
# 普通对象 Plain Object
__proto__  的使用可能会带来问题:
🌰 例子:
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
console.log(obj[key]); // [object Object]
 2
3
4
5
6
这里可以看到,当对象存储
__proto__作为键时的属性时,这时赋值就会被忽略。(__proto__属性必须是对象或者null。字符串不能成为 prototype。)尽管并不是真的想要意图向__proto__赋值,但是键名为__proto__的键值对没有被正确存储。这是一个严重的漏洞。在其他情况下,可能会对对象进行赋值操作,然后原型可能就被更改了。结果,可能会导致完全意想不到的结果。当开发者不考虑这个情况,让这类 bug 很难被发现,甚至变成漏洞,尤其是在 JavaScript 被用在服务端的时候。
即使对在默认情况下为函数的
toString以及其他内建方法执行赋值操作,也会出现意想不到的结果。
要避免这样的问题,可以使用 Map 代替普通对象进行存储。
想要将一个对象用作关联数组,并且摆脱此类问题,使用  Object.create(null)  创建了一个空对象,这个对象没有原型,所以使用  __proto__  作为键没有问题 ( [[Prototype]]  是  null ):
let obj = Object.create(null);
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
console.log(obj[key]) // "some value"
 2
3
4
5
6
使用  Object.create(null)  创建的对象被称为 「very plain」 或 「pure dictionary」 对象,因为它们甚至比通常的普通对象(plain object) {...}  还要简单。
缺点是这样的对象没有任何内建的对象的方法,例如  toString :
let obj = Object.create(null)
console.log(obj)
 2
但是 对于与对象相关的方法( Object.xxx(...) )仍然可用,它们不在原型中:
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";
console.log(Object.keys(chineseDictionary))
 2
3
4
5
# 总结
现代 JavaScript 中直接访问原型的方法:
Object.create(proto, [descriptors]):利用给定的proto作为[[Prototype]]和可选的 属性描述 创建一个空对象。Object.getPrototypeOf(obj):获取对象obj的原型属性[[Prototype]]。Object.setPrototypeOf(obj, proto):将对象obj的[[Prototype]]设置为proto。
更多方法:
Object.keys(obj)/Object.values(obj)/Object.entries:返回一个可枚举的由 对象 自身的字符串属性名 / 值 / 键值对组成的数组。Object.getOwnPropertySymbols(obj):返回对象所有的 symbol 类型的键组成的数组。Object.getOwnPropertyNames(obk):返回一个由自身的所有字符串键组成的数组。Reflect.ownKeys:返回一个由自身所有键组成的数组。obj.hasOwnProperty(key):如果obj拥有名为key的自身的属性(非继承而来的),则返回true。- 所有返回对象属性的方法(如  
Object.keys及其他), 都返回「自身」的属性。如果想继承它们,我们可以使用for...in。 
# 实例
# 为对象添加不可枚举的方法
为该对象添加
dictionary.toString()方法,该方法应该返回以逗号分隔的所有键的列表。并且toString方法不应该在使用for...in循环遍历数组的时候显现出来。let dictionary = Object.create(null); dictionary.apple = "Apple"; dictionary.__proto__ = "test"; for(let key in dictionary) { alert(key); // "apple", then "__proto__" } console.log(dictionary)1
2
3
4
5
6
7
8
9
10
点击查看
let dictionary = Object.create(null, {
  toString: {
    value() {
      return Object.keys(this).join()
    }
  }
})
 2
3
4
5
6
7
为了使  toString  不可枚举,使用一个 属性描述器 来定义它。 Object.create  语法允许为一个对象提供属性描述器作为 第二参数。当使用描述器创建一个属性,它的标识默认是  false 。因此在上面这段代码中, dictonary.toString  是不可枚举的。
或者使用  Object.defineProperties :
Object.defineProperties(dictionary, "toString", {
  value: function() {
    return Object.keys(this).join()
  },
  enumerable: false
})
 2
3
4
5
6
# 调用方法的差异
创建一个新的
rabbit对象:function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert(this.name); }; let rabbit = new Rabbit("Rabbit");1
2
3
4
5
6
7
8分析:
rabbit.sayHi(); Rabbit.prototype.sayHi(); Object.getPrototypeOf(rabbit).sayHi(); rabbit.__proto__.sayHi();1
2
3
4
点击查看
rabbit.sayHi();                        // Rabbit
Rabbit.prototype.sayHi();              // undefined
Object.getPrototypeOf(rabbit).sayHi(); // undefined
rabbit.__proto__.sayHi();              // undefined
 2
3
4
this  指向问题。
所以,只有第一个调用显示  Rabbit ,其他的都显示的是  undefined 。
