前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

原型

作者头像
Karl Du
发布2020-10-23 17:15:12
6630
发布2020-10-23 17:15:12
举报

原型

[Prototype]

JavaScript中的对象有一个特殊的[[prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]属性都会被默认赋予一个空的值。

[[prototype]]有啥用呢?当我们试图引用对象的属性就会出发[[Get]]操作,比如myObject.a。对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它,如果没有就需要使用对象的原型链了。

var anOtherObject=  {
  a: 2
};

anOtherObject.a; // 2

我们知道,除了nullundefined以外都是对象。每一个对象都有自己的属性和方法。那么当我们访问一个对象的属性的时候,如果这个对象没有这个属性,引擎就会往原型链上向上查找,一个对象除了拥有自己的属性和方法,也会继承来自原型上层的父级对象的属性。所以,引擎会向上查找直至找到原型链上的对象或达到原型链的顶层。

//// 假定有一个对象 o, 其自身的属性(own properties)有 a 和 b:
// {a: 1, b: 2}
// o 的原型 o.[[Prototype]]有属性 b 和 c:
// {b: 3, c: 4}
// 最后, o.[[Prototype]].[[Prototype]] 是 null.
// 这就是原型链的末尾,即 null,
// 根据定义,null 没有[[Prototype]].
// 综上,整个原型链如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为1

console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为2
// o.[[Prototype]]上还有一个'b'属性,但是它不会被访问到.这种情况称为"属性遮蔽 (property shadowing)".

console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4

console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有.
// o.[[Prototype]].[[Prototype]]为null,停止搜索,
// 没有d属性,返回undefined

上面是一个原型链的模型,每个函数都有一个原型属性prototype指向自己的原型,而由这个函数创建的对象也有一个_proto_属性指向这个原型。

我们可以看到这里面有_proto_prototype这两个东西。如果你有使用过chromedevelop tools或者vscode进行单步调试的经验的话你会经常看到_proto_这个字段。那么,这个到底是什么意思呢?

先看_proto_开始说起

每一个JS对象一定对应一个原型对象,并且从原型对象那里继承属性和方法。(重点)

Every JavaScript object has a second JavaScript object (or null, but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.

看下面这个例子:

var one = {x: 1};
var two = new Object();
one.__proto__ === Object.protopyte //true
two.__proto__ === Object.prototype //true
one.toSting === one.__proto__.toString //true

我们看到onetwo都拥有一个__proto__属性,且原型对象都是Object.prototype,拥有对象上的属性和方法。

那么,Object.prototype到底是个啥?我们先把这个问题往后放,先搞清楚prototype_proto_有什么区别?

首先,prototype_proto_的第一个区别就在于:每一个对象都会有一个_proto_属性来标示自己所继承的原型。但是函数才会有prototype属性。当我们创建函数的时候,JS会为这个函数追加一个prototype属性。当我们尝试把这个函数当成一个构造函数来调用的时候,那么JS就会创建这个构造函数的实例,这个实例会继承构造函数prototype的所有属性和方法。同时实例会通过_proto_指向构造函数的prototype

于是JS就是这样通过_proto_prototype来实现原型链。

构造函数就是通过prototype来保存要共享给实例的属性和方法。

对象的_proto_总是指向自己的构造函数的prototype

Object.prototype

哪里是[[prototype]]的尽头呢?如上图,我们顺着箭头方向可以看到所有普通的[[prototype]]链最终都会指向内置的Object.prototype。由于所有的普通对象都源于这个对象,所以它包含了JavaScript中许多通用的功能。

有些功能我们已经很熟悉了,比如说.toString().valueOf()等等。

给一个对象设置属性并不仅仅是添加一个新属性或者修改已有的属性值。现在我们完整地讲解一下这个过程

myObject.foo = "bar";

如果myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。

如果foo不是直接存在于myObject中,[[Prototype]]链就会被遍历,类似[[Get]]操作。如果原型链上找不到foofoo就会被直接添加到myObject上。

然而,如果foo存在于原型链上层,赋值语句myObject.foo = "bar"的行为就会有些不同(而且可能很出人意料)。稍后我们会进行介绍。

如果属性名foo既出现在myObject中也出现在myObject[[Prototype]]链上层,那么就会发生屏蔽。myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性。

屏蔽比我们想象中更加复杂。下面我们分析一下如果foo不直接存在于myObject中而是存在于原型链上层时myObject.foo = "bar"会出现的三种情况。

  1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性(参见第 3 章)并且没有被标记为只(writable:false),那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性。
  2. 如果在 [Prototype] 链上层存在 foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
  3. 如果在 [Prototype] 链上层存在 foo 并且它是一个 setter(参见第 3 章),那就一定会调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这个 setter。

给对象添加属性大多数情况是第一种情况,但是当原型链已存在该同名属性时,我们就不能用=来赋值了。我们可以用Object.defineProperty来向对象添加属性。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020/5/27 下,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原型
    • [Prototype]
      • Object.prototype
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档