Js.2 原型和原型链

原型Prototype和原型链[[Prototype]]这两个概念起先让我晕头转向,不过看得多了也就慢慢梳理清楚了,突然想写一篇博客记一下,以后要忘了还可以回过头来翻一翻,复习复习…

Javascript当中可以说都是对象(但不都是object类型),没有类和实例这样的区分,它的继承主要是靠“原型链”来实现。其实会变成这样也是Javascript发展过程中的某种妥协,原先是没打算设计“继承”机制的,后来才加上去的……

构造函数和实例对象

首先我们有一个构造函数 Animal():

function Animal(name) {
this.name = name;
}

一般我们会这么构造:var human = new Animal("Human"); // 上帝造个人

这时候我们有了一个构造函数,它是 Animal(),还有一个实例对象 human。

谁有__proto__?谁有prototype?

首先,引入一下 __proto__ 这个属性。这个没记错的话之前是浏览器提供的,后来纳入了 ES6 的规范。本质上它是[[Prototype]]的getter和setter方法,我们把它们俩简单的理解为“二位一体”就可以了。

其次,__proto__ 本质上是一个指针,指向的是一个函数对象的 prototype 属性的值。

这里要特别说明的是 nullundefined 这两个基本类型是没有 __proto__ 的,也就是没有原型链[[Prototype]]。

既然说到 prototype,那就继续往下讲。因为JavaScript中通过函数来模拟类,所以这个属性只有函数才有。就是这么任性!

每个函数都有 prototype ,他也是一个指针,指向一个对象。而函数所指向的这个对象中包含 __proto__constructor。它和普通对象的区别在于有一个 constructor 表示构造函数,这玩意儿并没有啥用。

结合上面的例子,我们梳理一下前面的代码当中二者的情况:

  • 构造函数 Animal() 有 prototype 属性,它是一个对象,包含 __proto__constructor
  • 实例对象 human 有 __proto__name

__proto__ 和 prototype 的内容

前面说了,原型链 __proto__ 指向的是一个函数对象的 prototype 属性的值,我们把它看作一个指针就可以了。上面例子的情况就是:

  • 在构造函数 Animal() 当中,Animal.prototype.__proto__ 指向的是 Object.prototype
  • 在实例对象 human 当中,human.__proto__ 指向的是 Animal.prototype
  • 构造函数 Animal() 有 prototype,实例对象 human 则没有

JavaScript中有一个特殊的引用类型 object,我们可以把 Object(), String(), Function(), Array()等等,都看作是 object 类型。创建实例时,每个实例都有以下的属性和方法:

  1. constructor:保存创建当前对象的函数,即构造函数
  2. hasOwnProperty(propName):可检查给定属性在当前实例中存在与否
  3. isPrototypeOf(object):检查传入对象是否是当前对象的原形
  4. propertyIsEnumerable(propName):检查给定参数可否用 for-in 枚举
  5. toLocaleString():返回字符串表示,与执行环境时区对应
  6. toString():返回对象的字符串表示
  7. valueOf():返回其值,与上者通常相同

上述列表就是 Object.prototype 的基本内容,也就是 Animal.prototype.__proto__ 的内容。

下面说说 Animal.prototype 的内容,函数在构建的时候会自动生成一个内置属性表示构造函数,也就是 constructor。它的值就是函数本身,也就是function Animal(name) {…} 。
(注意:constructorprototype 属性当中)

什么是构造函数

构造函数是JavaScript中很奇葩的一个属性,只有函数当中才存在,但也不是都存在。

接着上面的例子,现在上帝不想单独造一个物种了,他想弄一大科目的物种,比如说猫科。

function Cat(name) {
this.nickname = name;
}

但是怎么把它和 Animal() 关联起来,实现继承关系呢?

于是乎,奇葩难懂的地方来了……我们先梳理一下这个函数当前的情况:

  • Cat.prototype 没有任何额外的属性
  • Cat.prototype.constructor 是它本身,也就是 function Cat(name) {…}
  • Cat.prototype.__proto__ 指向的是 Object.prototype

我们用 Cat.prototype = new Animal("Cat"); 把它俩关联起来……

关联起来了……

下面看一下 Cat() 函数现在的情况:

  • Cat.prototype 中存在一个属性 name
  • Cat.prototype.constructor 不存在(调用时会访问原型链的上一层)
  • Cat.prototype.__proto__ 指向的是 Animal.prototype

最后构造一个猫科的实例对象:var lion = new Cat("lion");,我们看看它都有啥:

  • lion 中存在属性 nickname__proto__
  • lion.__proto__ 指向的是 Cat.prototype

混乱的总结

其实写到一半自己也有点要被绕晕的感觉。但这就是JavaScript实现原型链的方法,必须得清楚。

  1. __proto__本质上是[[Prototype]]的getter和setter,可以看作一体
  2. 函数默认有一个 prototype 属性,该属性包含 constructor__proto__
  3. 对象默认有一个 __proto__ 属性
  4. __proto__ 指向一个对象的 prototype,默认指向 Object.prototype
  5. 函数的 prototype.constructor 默认指向自身
  6. 函数的 prototype 被替换为其他函数对象后,prototype 属性中丢失 constructor,只存在 __proto__

来点图片总结……图片来源

咸鱼的一天开始了……

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注