🏜 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
。