原型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 属性的值。
这里要特别说明的是 null 和 undefined 这两个基本类型是没有 __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 类型。创建实例时,每个实例都有以下的属性和方法:
- constructor:保存创建当前对象的函数,即构造函数
- hasOwnProperty(propName):可检查给定属性在当前实例中存在与否
- isPrototypeOf(object):检查传入对象是否是当前对象的原形
- propertyIsEnumerable(propName):检查给定参数可否用 for-in 枚举
- toLocaleString():返回字符串表示,与执行环境时区对应
- toString():返回对象的字符串表示
- valueOf():返回其值,与上者通常相同
上述列表就是 Object.prototype 的基本内容,也就是 Animal.prototype.__proto__ 的内容。
下面说说 Animal.prototype 的内容,函数在构建的时候会自动生成一个内置属性表示构造函数,也就是 constructor。它的值就是函数本身,也就是function Animal(name) {…} 。
(注意:constructor 在 prototype 属性当中)
什么是构造函数
构造函数是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中存在一个属性nameCat.prototype.constructor不存在(调用时会访问原型链的上一层)Cat.prototype.__proto__指向的是Animal.prototype
最后构造一个猫科的实例对象:var lion = new Cat("lion");,我们看看它都有啥:
lion中存在属性nickname和__proto__lion.__proto__指向的是Cat.prototype
混乱的总结
其实写到一半自己也有点要被绕晕的感觉。但这就是JavaScript实现原型链的方法,必须得清楚。
- __proto__本质上是[[Prototype]]的getter和setter,可以看作一体
- 函数默认有一个
prototype属性,该属性包含constructor和__proto__ - 对象默认有一个
__proto__属性 __proto__指向一个对象的prototype,默认指向Object.prototype- 函数的
prototype.constructor默认指向自身 - 函数的
prototype被替换为其他函数对象后,prototype属性中丢失constructor,只存在__proto__
来点图片总结……图片来源

咸鱼的一天开始了……

发表回复