0
点赞
收藏
分享

微信扫一扫

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?


说明

图解 Google V8 学习笔记

继承是什么?

简单的说:继承就是一个对象可以访问另外一个对象中的属性和方法,在JavaScript 中,我们通过原型和原型链的方式来实现了继承特性。

不同的语言实现继承的方式是不同的,其中最典型的两种方式:

  • 基于类的设计:C++、Java、C#
  • 基于原型继承的设计:JavaScript

原型继承是如何实现的?

JavaScript 的每个对象都包含了一个隐藏属性 ​​__proto__​​​,我们就把该隐藏属性 ​​__proto__​​ 称之为该对象的原型 (prototype),​​__proto__​​​ 指向了内存中的另外一个对象,我们就把 ​​__proto__​​ 指向的对象称为该对象的原型对象

例子:

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_构造函数


我们让 C 对象的原型指向 B 对象,让 B 对象的原型指向 A 对象,那么 C 对象就可以直接访问 B 以及 A 的方法跟属性了。

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_构造函数_02

当我们通过对象 C 来访问对象 A 中的 color 属性时,V8 会先从对象 C 中查找,没有查找到,接着继续在 C 对象的原型对象 B 中查找,依旧没有查找到,那么继续去对象 B 的原型对象 A 中查找,因为 color 在对象 A 中,那么 V8 就返回该属性值。我们把这个查找属性的路径称为原型链

原型链 vs 作用域链

  • 原型链:是沿着对象的原型一级一级来查找属性的
  • 作用域链:是沿着函数的作用域一级一级来查找变量的

实践:利用 ​​__proto__​​ 实现继承

下面先创建了两个对象 animal 和 dog,如果让 dog 对象继承于 animal 对象,应该怎么操作?

var animal = {
type: "Default",
color: "Default",
getInfo: function () {
return `Type is: ${this.type},color is ${this.color}.`
}
}
var dog = {
type: "Dog",
color: "Black",
}

最直接的方式就是通过设置 dog 对象中的 ​​__proto__​​ 属性,将其指向 animal。

dog.__proto__ =

使用 dog 来调用 animal 中的 getInfo 方法

dog.getInfo()

输出结果如下:

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_javascript_03

注意:通常隐藏属性是不能使用 JavaScript 来直接与之交互的。虽然现代浏览器都开了一个口子,让 JavaScript 可以访问隐藏属性 ​​_proto_​​​,但是在实际项目中,我们不应该直接通过 ​​_proto_​​ 来访问或者修改该属性,应该使用构造函数来创建对象。

其主要原因有两个:

  1. ​_proto_​​ 是隐藏属性,并不是标准定义的 ;
  2. 原型的实现做了很多复杂的优化,比如:通过隐藏类优化了很多原有的对象结构,所以通过直接修改​​__proto__​​ 会直接破坏现有已经优化的结构,造成严重的性能问题。

构造函数是怎么创建对象的?

例子:

  1. 先创建一个 DogFactory 的函数,属性通过参数进行传递,在函数体内,通过 this 设置属性值。

function DogFactory(type,){
this.type = type
this.color = color
}

  1. 再结合关键字​​new​​ 就可以创建对象(DogFactory 函数称为构造函数)

var dog = new DogFactory('Dog', 'Black')

V8 执行上面这段代码时,做了什么?

大致分为三步:

  1. 创建了一个空白对象 dog
  2. 将 DogFactory 的 prototype 属性设置为 dog 的原型对象
  3. 再使用 dog 来调用 DogFactory,这时候 DogFactory 函数中的 this 就指向了对象 dog,然后在 DogFactory 函数中,利用 this 对对象 dog 执行属性填充操作

最终就创建了对象 dog。

模拟代码如下:

var dog = {}  
dog.__proto__ = DogFactory.prototype
DogFactory.call(dog, 'Dog', 'Black')

执行流程图示意图:

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_构造函数_04

构造函数怎么实现继承?

例子:添加 constant_temperature 为 1 表示恒温动物

function DogFactory(type,color){
this.type = type
this.color = color
// 恒温动物
this.constant_temperature = 1
}
var dog1 = new DogFactory('Dog','Black')
var dog2 = new DogFactory('Dog','Black')
var dog3 = new DogFactory('Dog','Black')

dog1、dog2、dog3 占用空间示意图:

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_v8_05

可以看到 constant_temperature 属性都占用了一块空间,因为 dog 是恒温动物,每个对象 没必要为 constant_temperature 属性都分配一块空间,该属性既然是通用的,可以设置属性为公用的。

每个函数对象中都有一个公开的 prototype 属性,当这个函数作为构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的 prototype 属性。

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_javascript_06

三个 dog 对象的原型对象都指向了 prototype,我们只要让 prototype 包含 constant_temperature 属性,就能实现继承了。

function DogFactory(type,color){
this.type = type
this.color = color
}
DogFactory. prototype.constant_temperature = 1

var dog1 = new DogFactory('Dog','Black')
var dog2 = new DogFactory('Dog','Black')
var dog3 = new DogFactory('Dog','Black')

图解 Google V8 # 06:原型链:V8是如何实现对象继承的?_原型对象_07

构造函数的​​__proto__​​ 和 prototype

  • 函数作为对象他得拥有一个​​__proto__​​,该属性是隐藏属性,并不是标准定义的 ;
  • 函数作为一个构造函数,它得拥有一个​​prototype​​,该属性是标准定义的

上面的 ​​DogFactory​​​ 是 ​​Function​​​ 构造函数的一个实例,所以 ​​DogFactory.__proto__ === Function.prototype​​;

​DogFactory.prototype​​​ 是调用 Object 构造函数的一个实例,所以 ​​DogFactory.prototype.__proto__ === Object.prototype​​;

因此 ​​DogFactory._proto_​​​ 和 ​​DogFactory.prototype​​ 没有直接关系。

总结

在 JavaScript 中,是使用 new 加上构造函数的这种组合来创建对象和实现对象的继承。

JavaScript 完全没有必要使用关键字 new 来创建一个新对象的,但是为了进一步吸引 Java 程序员,依然需要在语法层面去蹭 Java 热点,所以 JavaScript 中就被硬生生地强制加入了非常不协调的关键字 new,虽然 new 关键字设计并不合理,但它的出现成功地推广 JavaScript 的市场。


举报

相关推荐

0 条评论