Skip to content

原型与原型链

作者:guo-zi-xin
更新于:9 个月前
字数统计:1.6k 字
阅读时长:5 分钟
原型与原型链

注:上图中 P1是构造函数 Parent()的一个实例

前置知识

  1. Javascript中对象分为 函数对象普通对象, 每个对象都有 __proto__ 属性, 但是只有函数才会有 prototype 属性
  2. Object、Function 都是JavaScript内置的函数, 类似的我们还有 Array、RegExp、Date、Boolean、Number、String
  3. 属性__proto__是一个对象, 它有两个属性, constructor__proto__
  4. 原型对象 prototype 有一个默认的 constructor 属性, 用于记录实例是由哪个构造函数创建
javascript
let Parent = function () {

}
// 定义一个函数,那么它只是一个普通的函数
let  p1 = new Parent()
// 这时 Parent就不是一个普通的函数了,它现在是一个构造函数,因为通过 new 关键字调用了它
// 创建了一个Parent构造函数的实例 p1
prototype属性

prototype是函数独有的属性,从图中可以看到它从一个对象指向另一个对象,代表这个对象是这个函数的原型对象,这个对象也是当前函数所创建的实例的原型对象

prototype设计之初就是为了实现继承,让由特定函数创建的所有实例共享属性和方法,也可以说是让某一个构造函数实例化的所有对象都可以找到公共的方法和属性。有了prototype属性, 我们不需要 为每个实例创建重复的属性方法,而是将属性方法创建在构造函数的原型对象上(prototype),那些不需要共享的方法才被创建在构造函数之中。

继续引用上面的代码, 当我们想要为 Parent 实例化的所有实例添加一个共享的属性时:

javascript
Parent.prototype.name = '我是原型属性, 所有实例都可以读取到我'

这就是原型属性,可以在上面添加原型方法,那么问题来了, p1 是如何知道它的原型对象上有这个方法呢, 这就提到了__proto__属性~

__proto__属性

__proto__属性是对象独有的(包括函数), 从图中我们可以看到__proto__属性是从一个对象指向另一个对象,即从一个对象指向该对象的原型对象(也可以理解为父对象)。显然它的含义就是告诉我们一个对象原型对象是谁

prototype中我们可以知道 Parent.prototype上添加的属性方法叫做原型属性和原型方法,该构造函数的实例都可以访问调用。那么这个构造函数的原型对象上的属性和方法, 怎么和构造函数的实例联系在一起的呢?就是通过__proto__属性, 每个对象都有__proto__属性,该属性指向的就是该对象的原型对象

javascript
p1.__proto__ === Parent.prototype; // true

__proto__通常被称为隐式类型, prototype被称为显式类型, 那么我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。 我们通过在显式原型上定义的属性方法, 通过隐式原型传递给了构造函数的实例。这样一来实例就很容易能够访问到构造函数原型上的方法和属性了。

我们之前也说过 __proto__属性是对象(包括函数)独有的,那么Parent.prototype也是对象,它有隐式原型么,又指向谁呢?

javascript
Parent.prototype.__proto__ === Object.prototype; // true

可以看到,构造函数的原型对象上的隐式原型指向了 Object 的原型对象,那么Parent的原型对象就继承了Object的原型对象。由此我们可以验证一个结论,万物继承自 Object.prototype 这也就是为什么 我们可以实例化一个对象,并且可以调用该对象上没有的属性和方法了:

javascript
// 我们并没有在Parent中定义任何方法和属性,但是我们可以调用
p1.toString(); // hasOwnProperty 等等一些方法

我们可以调用很多我们没有定义的方法,这些方法从哪里来的呢? 现在引出 原型链的概念,当我们调用p1.toString()的时候, 先在p1对象本身寻找,没有找到则通过p1.__proto__ 找到了原型对象Parent.prototype, 也没有找到, 然后又通过Parent.prototype.__proto__找到了上一层原型对象Object.prototype,在这一层找到了 toString 方法, 返回该方法供p1 使用

当然如果直到 Object.prototype上也没有找到,就在Object.prototype.__proto__中寻找, 但是 Obnject.prototype.__proto__null, 所以返回了 undefined。 这也就是为什么当访问对象中一个不存在的属性的时候, 返回 undefined

constructor属性

constructor是对象才有的属性,从图中看到它是从一个对象指向一个函数的。指向的函数就是该对象的构造函数,每个对象都有构造函数,就好比上面的p1就是一个对象, 那么p1的构造函数是谁呢?

javascript
console.log(p1.constructor) // ƒ Parent()()

通过输出可以看到,很显然p1的构造函数是 Praent 函数, 我们有说过函数也是对象, 那 Parent 函数是不是也有构造函数呢?显然是有的:

javascript
console.log(Parent.constructor) // ƒ Function() { [native code] }

通过输出看到 Parent 函数的构造函数是 Function() 这点也不奇怪,因为我们每次定义函数其实都是调用了new Function(), 下面两种效果是一样的:

javascript
let func1 = new Function('msg', alert('msg'));

function func1(msg) {
  alert(msg)
}

那么我们再回来看一下,再次打印 Function.constructor

javascript
console.log(Function.constructor) // ƒ Function() { [native code] }

可以看到Function函数的构造函数就是本身了,那我们也可以说 Function时所有函数的构造函数。

到这里我们已经对 constructor 属性有了一个初步认识,它的作用是从一个对象指向一个函数,这个函数就是该对象的构造函数,通过例子我们可以看到, p1constructor属性指向了 Parent, 那么 Parent就是p1的构造函数。

同样Parentconstructor属性指向了Function, 那么 Function就是Parent的构造函数, 然后又验证了Function就是根构造函数。

引用

JavaScript原型&原型链
https://segmentfault.com/a/1190000021232132

人生没有捷径,就像到二仙桥必须要走成华大道。