图解 Google V8 # 06:原型链:V8是如何实现对象继承的?
说明
图解 Google V8 学习笔记
继承是什么?
简单的说:继承就是一个对象可以访问另外一个对象中的属性和方法,在JavaScript 中,我们通过原型和原型链的方式来实现了继承特性。。
不同的语言实现继承的方式是不同的,其中最典型的两种方式:
- 基于类的设计:C++、Java、C#
- 基于原型继承的设计:JavaScript
原型继承是如何实现的?
JavaScript 的每个对象都包含了一个隐藏属性 __proto__,我们就把该隐藏属性 __proto__ 称之为该对象的原型 (prototype),__proto__ 指向了内存中的另外一个对象,我们就把 __proto__ 指向的对象称为该对象的原型对象。
例子:
我们让 C 对象的原型指向 B 对象,让 B 对象的原型指向 A 对象,那么 C 对象就可以直接访问 B 以及 A 的方法跟属性了。
当我们通过对象 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__ = animal
使用 dog 来调用 animal 中的 getInfo 方法
dog.getInfo()
输出结果如下:
注意:通常隐藏属性是不能使用 JavaScript 来直接与之交互的。虽然现代浏览器都开了一个口子,让 JavaScript 可以访问隐藏属性 _proto_,但是在实际项目中,我们不应该直接通过 _proto_ 来访问或者修改该属性,应该使用构造函数来创建对象。
其主要原因有两个:
_proto_ 是隐藏属性,并不是标准定义的 ;
原型的实现做了很多复杂的优化,比如:通过隐藏类优化了很多原有的对象结构,所以通过直接修改 __proto__ 会直接破坏现有已经优化的结构,造成严重的性能问题。
构造函数是怎么创建对象的?
例子:
- 先创建一个 DogFactory 的函数,属性通过参数进行传递,在函数体内,通过 this 设置属性值。
function DogFactory(type, color){ this.type = type this.color = color }
- 再结合关键字
new
就可以创建对象(DogFactory 函数称为构造函数)
var dog = new DogFactory('Dog', 'Black')
V8 执行上面这段代码时,做了什么?
大致分为三步:
创建了一个空白对象 dog
将 DogFactory 的 prototype 属性设置为 dog 的原型对象
再使用 dog 来调用 DogFactory,这时候 DogFactory 函数中的 this 就指向了对象 dog,然后在 DogFactory 函数中,利用 this 对对象 dog 执行属性填充操作
最终就创建了对象 dog。
模拟代码如下:
var dog = {} dog.__proto__ = DogFactory.prototype DogFactory.call(dog, 'Dog', 'Black')
执行流程图示意图:
构造函数怎么实现继承?
例子:添加 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 占用空间示意图:
可以看到 constant_temperature 属性都占用了一块空间,因为 dog 是恒温动物,每个对象 没必要为 constant_temperature 属性都分配一块空间,该属性既然是通用的,可以设置属性为公用的。
每个函数对象中都有一个公开的 prototype 属性,当这个函数作为构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的 prototype 属性。
三个 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')
构造函数的__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 的市场。
相关文章
- 探究Presto SQL引擎(2)-浅析Join
- Linux 下12个监视网络连接的 ss 命令示例
- Linux 中列出挂载驱动器的四个命令
- 在 Linux 中使用 netcat 命令扫描端口
- 苹果 Safari 浏览器技术预览版 161 发布:兼容 macOS 13/12,修复 Bug 并改进性能
- Go 语言使用 goroutine 运行闭包的“坑”
- 微软 Edge 浏览器 109 正式版发布:最后一个与 Windows 7/8.1 兼容的版本
- 如何快速调试RTC?你知道吗?
- 基于OpenHarmony的智能电磁(炮)
- 为何用户会强烈吐槽 Windows 11 的升级门槛?微软员工“感同身受”并发布虚拟机安装 Windows 11 教程
- 京东APP OpenHarmony 化的跨端开发探索
- 在 Bash shell 中使用花括号扩展 {..}
- OBS Studio 29 发布,但对 Linux 用户来说变化不大
- 大数据,大业务:网络面临的巨大挑战
- 2023 年五大数据科学发展趋势
- 你需要知道的 TCP 四次挥手
- 数据网格有望升级整个IT系统
- 2022年值得推荐的React库!
- 如何用物联网和大数据建设智慧城市?
- 聊聊 13 种锁的实现方式