今天看啥  ›  专栏  ›  一二家的攻城狮

深入JavaScript(一): 原型与原型链

一二家的攻城狮  · 掘金  ·  · 2019-10-29 10:31
阅读 31

深入JavaScript(一): 原型与原型链

__proto__.jpeg

注:本文首发于我的博客,移步 获取更好的阅读体验。

抛开封面图,我们先以 MDN 的一句话作为开端,

对于使用过基于类的语言 (如 Java 或 C++) 的开发人员来说,JavaScript 有点令人困惑,因为它是动态的,并且本身不提供一个 class 实现。虽然在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的

那么到底什么是原型?

原型

我们先用构造函数创建一个实例对象,

其实理解原型,就是理解构造函数,实例对象和原型对象之间的关系

function Engineer(name) {
  this.name = name
}

Engineer.prototype.coding = function() {
  console.log('write less, do more.')
}

const engineer = new Engineer('campcc')
复制代码

JavaScript 中,每一个构造函数都有一个 prototype 属性,它指向构造函数的原型对象:

Engineer.prototype // {coding: ƒ, constructor: ƒ}
复制代码

原型对象中有一个 constructor 属性指回构造函数:

Engineer.prototype.constructor === Engineer // true
复制代码

而每一个实例对象都有一个 __proto__ 属性,当我们使用构造函数创建实例时,实例的 __proto__ 属性就会指向构造函数的原型对象:

engineer.__proto__ === Engineer.prototype // true,__proto__ 其实是一个 JavaScript 的非标准但许多浏览器都实现的属性,从 ECMAScript 6 开始,支持通过符号 [[Prototype]] 或者方法 Object.getPrototypeOf() 访问
复制代码

构造函数,实例对象与原型对象的关系为:

构造函数实例对象和原型对象关系图.png

原型链

为了更好的理解什么是原型链,我们尝试调用实例对象的几个方法,

engineer.coding() // write less, do more.

engineer.toString() // "[object Object]"

engineer.map() // Uncaught TypeError: engineer.map is not a function
复制代码

结果看似很出乎意料,因为我们其实并没有在实例里定义 codingtoString 方法啊,但是它们却能够被成功调用,为什么?因为在 JavaScript 中,当我们试图访问一个对象的属性或方法时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索:

原型链.png

我们尝试访问 map 方法时报错了,因为原型链上找不到此方法。原型链查找会一直持续,直到找到一个名字匹配的属性或方法,或者达到原型链的末尾,而根据定义,null 就是原型链的末尾

Object.prototype.__proto__ === null // true,null 在这里可以理解为 “没有对象”
复制代码

如果查找过程中遇到同名属性或方法,位于原型链底端的属性或方法会被优先应用,这叫做 “属性遮蔽(property shadowing)”。比如我们在 Engineer 的原型对象上声明一个同名的 name 属性:

Engineer.prototype.name = 'engineer'

engineer.name // campcc,这里不会打印 'engineer',因为在原型链查找的过程中,实例对象中就已经存在 name 属性了
复制代码

总结一下,原型链其实就是对象或原型对象的 __proto__ 组成的一条原型查找链

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例,但有两个例外:

  1. Object.prototype:我们刚才说了,它的原型是 null
  2. Object.create(null):创建一个原型为 null 的空对象

补充

关于封面图,其实隐喻了 JavaScript 中一直存在争议的一个问题,

Function.__proto__ === Function.prototype // true
复制代码

先来看看在 Chorme V8 中的打印结果:

Function.__proto__ // ƒ () { [native code] }

Function.prototype // ƒ () { [native code] }

Object.__proto__ // ƒ () { [native code] }
复制代码

从打印结果来看,根本不存在 "Function 也是 Function 本身的一个实例" 的说法,因为不管是 Function.__proto__Function.prototype 还是 Object.__proto__ 都是引擎创建的,Function 也是由引擎创建的,至于为什么会相等,只能说每个语言都存在缺陷,这个问题就像为什么 typeof null === "object" 一样,不用过于纠结。

总结

JavaScript 中,

  • 每个函数都有 prototype 属性,代表构造函数的原型对象
  • 每个实例对象都有 __proto__ 属性,指向构造函数的原型对象
  • 每个原型对象中都有 constructor 属性,指向构造函数,标识原型对象的是由哪个函数构造的
  • 对象或原型对象的 __proto__ 组成了一条原型链,原型链其实也是一条查找链
  • 原型链查找会一直持续,直到找到同名的属性或方法,或者到达原型链的末尾 null
  • 原型链查找会遵循 属性遮蔽 原则,位于底层的属性或方法会被优先找到

勘误与提问

如果有疑问或者发现错误,可以在相应的 issues 进行提问或勘误

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励

(完)




原文地址:访问原文地址
快照地址: 访问文章快照