目录

🏜 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, {});
1
2
3
4
5
6
7
8
9
10
11
12

🌰 例子 / 使用 Object.create() 实现对象克隆,配合 获取对象的属性描述符:

let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
1

该克隆对 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]
1
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"
1
2
3
4
5
6

使用 Object.create(null) 创建的对象被称为 「very plain」 或 「pure dictionary」 对象,因为它们甚至比通常的普通对象(plain object) {...} 还要简单。

缺点是这样的对象没有任何内建的对象的方法,例如 toString

let obj = Object.create(null)
console.log(obj)
1
2

但是 对于与对象相关的方法( Object.xxx(...) )仍然可用,它们不在原型中:

let chineseDictionary = Object.create(null);
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";

console.log(Object.keys(chineseDictionary))
1
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()
    }
  }
})
1
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
})
1
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
1
2
3
4

this 指向问题。

所以,只有第一个调用显示 Rabbit ,其他的都显示的是 undefined

📢 上次更新: 2022/09/02, 10:18:16