• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

7.6 原型的方法

JS 教程 tiantian 364次浏览 0个评论 扫描二维码

在本章节中,我们将学习额外的方法,它们可以与原型进行配合使用。

JavaScript 存在其他的 get/set 原型的方法,除了我们已经学过的那些:

举例:

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 rabbit = Object.create(animal, {
  jumps: {
    value: true
  }
});

alert(rabbit.jumps); // true

那个描述符的格式与我们在属性标记和描述符章节中所学习的描述符相同。

我们可以使用Object.create来执行一个对象克隆,其功能远比使用for..in来复制属性来的强大:

// fully identical shallow clone of obj
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

这个调用完成了一个obj的非常准确的克隆操作,包括所有的属性:可枚举属性和不可枚举属性,数据属性和 setters/getters 等等一切内容,而且具有正确的[[Prototype]]

简短历史

如果我们数一下所有可以操作[[Prototype]]的方法,个数很多。很多方法的功能完全相同。

为什么?

这是由于历史原因造成的。

  • 在JavaScript 语言诞生的早期,构造器函数的"prototype"属性就开发发挥作用。
  • 在2012年:Object.create出现在最新的规范和标准中。它允许使用给定的原型来创建对象,但是不允许 get/set 原型。所以,浏览器使用了非标准的__proto__访问器来允许任意时间来 get/set 原型。
  • 在2015年:Object.setPrototypeOfObject.getPrototypeOf被添加到新的标准和规范中。那个__proto__已经在事实上被普遍实现,所以它出现了标准的附录B中,变成了非浏览器环境的可选实现。

截至目前,我们已经学习了上述的所有方法。

技术上,你可以在任何时候 get/set [[Prototype]]。但是,一般地,我们只在对象创建时设置该属性一次,然后就不再进行修改:rabbitanimal处继承了它,所以不应该去修改它。而且,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。此处的后果也许并不是很严重。但在某些情形下,那个原型可能会发生改变,所以该代码的执行可能会以完全无法预测的方式失败。

更糟糕的是,一般地开发者通常意识不到此类bug的存在和可能性。这使得该 bug 非常难以被发现和修正。当 JavaScript 用在服务器端时,这样的代码非常脆弱。

这样的事情最发生在__proto__属性上。所有其他的键都可以正常赋值和工作。

如何解决该问题?

首先,我们可以使用Map结构,那么就一切工作正常。

但是,Object可以适用于当前的场景,因为语言创造者在很久之前就思考过该问题。

那个__proto__不是对象的属性,而是Object.prototype的一个访问器属性:

7.6 原型的方法

所以,如果obj.__proto__被读取或者被赋值,那个原型中相应的 getter/setter 就被调用,并它 gets/sets “[[Prototype]]`。

正如我们开头所述:__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)在不使用原型的情况下创建一个空对象([[Prototype]]null):

7.6 原型的方法

所以,对于__proto__就没有继承的 getter/setter 方法。现在,它被当作一个普通的数据属性来对待,所有上述的示例代码可正常工作。

我们可以将上述对象称为“超简单对象”或者“纯字典对象,因为它们甚至比普通的简单对象{...}还有简单。

此类对象的一个缺点就是:它们缺少内建的对象方法,比如toStirng

let obj = Object.create(null);

alert(obj); // Error (no toString)

但是,此类对象作为关联数组确实极好地。

请注意,绝大部分的对象相关的方法都是Object.something(...),比如Object.keys(obj),—— 它们不是原型方法,所有它们可以与”超简单对象”协作:

let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";

alert(Object.keys(chineseDictionary)); // hello,bye

获取所有属性

JavaScript 存在许多方法可以获取对象的键值对。

我们已经知道其中的一些方法:

如果我们希望获得 symbolic 属性:

如果我们希望获得不可枚举的属性:

如果希望获得所有属性:

那些方法在返回的属性值上具有细微的差别,但是所有的这些方法都是仅返回本对象的属性。从原型中继承的属性不会被列出。

那个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具有它自己的,名为key的属性时返回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

7.6 原型的方法

注意,存在一个有趣的事情。方法rabbit.hasOwnProperty来自于哪里?从上面的原型链可以发现,那个方法是在Object.prototype.hasOwnProperty。换句话说,这是一个继承方法。

但是,为什么hasOwnProperty没有出现在for..in循环中,如果它会列出所有的继承属性的话?这个问题的答案很简单:它是不可枚举的。就像Object.prototype的其他属性一样。这就是它们都没有被列出的原因。

总结

下列是本章所讨论方法的一个简短总结——作为一个记录:

而且,我们明白了__proto__仅仅是[[Prototype]]的一个 getter/setter , 而且是位于Object.prototype里面,就像其他方法一样。

我们可以通过Object.create(null)来创建一个没有原型的对象。这样的对象可以用于“纯字典”,它们在使用__proto__作为键时不会出现任何问题。

所有返回对象属性的方法,比如Object.keys,都只返回对象本身的属性。如果我们希望继承属性,那么我们可以利用for..in循环。

任务


往字典中添加toString

假设存在一个对象dictionary,由Object.create(null)创建,用来存储任意的键值对。

往里面添加方法dictionary.toString(),该方法应该返回一个逗号分隔的键的列表,而且这个toString不应该出现在for..in循环中。

下面是它的工作原理:

let dictionary = Object.create(null);

// your code to add dictionary.toString method

// add some data
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // __proto__ is a regular property key here

// only apple and __proto__ are in the loop
for(let key in dictionary) {
  alert(key); // "apple", then "__proto__"
}

// your toString in action
alert(dictionary); // "apple,__proto__"

调用的区别

让我们来创建一个rabbit对象:

function Rabbit(name) {
  this.name = name;
}
Rabbit.prototype.sayHi = function() {
  alert(this.name);
};

let rabbit = new Rabbit("Rabbit");

下列调用是不是效果一样?

rabbit.sayHi();
Rabbit.prototype.sayHi();
Object.getPrototypeOf(rabbit).sayHi();
rabbit.__proto__.sayHi();


天天编码 , 版权所有丨本文标题:7.6 原型的方法
转载请保留页面地址:http://www.tiantianbianma.com/prototype-method.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址