前言
这是我参与新手入门的第三篇文章,强烈推荐【冴羽】大神的 JavaScript深入系列文章,我今天要写的原型链,也是参考了其中的第一篇。
一、步骤拆解
首先,只要明白了什么是原型,自然就知道了原型链。其次,原型链围绕的无非三点:构造函数、实例对象、原型对象。我准备分以下三个部分来讲解:
- 简单了解一下怎么找到构造函数、实例对象、原型对象。
- 搞明白构造函数、实例对象、原型对象三者之间的关系。
- 引用实例和原型的关系来找出原型链。
1. 找出构造函数、实例对象、原型对象。
// 构造函数 Person
function Person(){
}
//prototype是函数才会有的属性
Person.prototype.name = '小贝';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // 小贝
console.log(person2.name) // 小贝
复制代码
上例中,Person 就是一个构造函数,而person1,person2就是使用 new 关键字,基于 Person 构造函数,创建出来的实例对象。
构造函数一般有三个特点:
- 首字母一般大写
- 用 this 来构造它的属性和方法
- 用 new 关键字来调用的函数
这里贴出构造函数的特点是因为要区分他和普通函数,其实构造函数也就是一个普通函数。
那原型对象在哪呢?
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。
如上所说,每个JavaScript对象在创建的时候就自动关联了一个原型对象!
所以说person1,person2被创建的时候就已经有了原型对象,只是我们不知道原型对象长什么样子。那下一步,我们就要用三者之间的关系来推论出原型对象的模样。
二、关系
为了方便,以下所论述的构造函数、实例和原型的关系,要默认为他们是关联的,这样就不必写太绕的话。
下面是要论证的,三个关系指向的流程:
- 构造函数指向原型对象
- 实例对象指向原型对象
- 原型对象指向构造函数
1. 构造函数 —> 原型对象
每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。
这句话有些绕,看下面这个列子:
// 构造函数 Person
function Person(){
}
// 实例对象 person
var person = new Person();
console.log(Person.prototype === 原型对象)
复制代码
就是构造函数 Person 的 prototype 属性,指向了原型对象的意思。这里的原型对象是实例对象person被创建的时候自动关联的那个原型对象!
注意:只有函数才有 prototype 属性!构造函数也是函数。
关系图如下:
这里有一个点,原型对象其实是一个概念词,并没有某个具体值能书写他,既然这里Person.prototype等于了原型对象,那就可以把 Person.prototype 表达为原型对象。
2. 实例对象 —> 原型对象
每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
// 构造函数 Person
function Person(){
}
// 实例对象 person
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
复制代码
实例对象person有一个__proto__属性,也指向他的原型对象(Person.prototype)。
更新关系图:
3. 原型对象 —> 构造函数
前两个关系中,实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
- 原型指向构造函数 — 有,constructor。
- 原型指向实例 — 没有,因为一个构造函数可以生成多个实例。
每个原型都有一个constructor属性指向关联的构造函数。
// 构造函数 Person
function Person() {
}
console.log(Person === Person.prototype.constructor); //true
复制代码
更新关系图:
三、实例和原型
目前为止,构造函数,实例,原型对象的指向关系都明白了,但是要找到原型链,还要讲一讲实例和原型的关系。
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
// 构造函数 Person
function Person() {
}
Person.prototype.name = '阿花';
// 实例对象 person
var person = new Person();
person.name = '小贝';
console.log(person.name) // 小贝
delete person.name;
console.log(person.name) // 阿花
复制代码
在上面这个例子中,实例对象person设置了name名为小贝,所以第一个console打印出了小贝,然后我们删除了person.name,再去读取时,实例对象person中已经没有了name,那就要去实例对象的原型(Person.prototype)里去找,然后找到了阿花。如果原型对象里面也没有,就要去原型的原型对象中去找,一直找到Object.prototype。
为什么Object.prototype?因为对象最初的构建方法:
// 顶层 Object 构造函数
function Object(){
...
}
// 对象最初是根据 Object构造函数 创建出的实例对象。
var obj = new Object();
// 所以最初的原型对象就是 Object.prototype
复制代码
最初的原型对象是通过Object构造函数生成的实例对象,结合之前所讲,实例的__proto__指向构造函数的prototype,所以我们再更新下关系图:
那Object.prototype的原型呢?
就是null,可以打印:
console.log(Object.prototype.__proto__ === null) // true
复制代码
引用阮一峰老师的 《undefined与null的区别》 就是:
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
查找属性的时候查到 Object.prototype 就可以停止查找了。
其实原型链已经出来了,图中由相互关联的原型组成的链状结构就是原型链,上图中蓝色的那条线。
怎么说呢,原型链就是一个链条而已这句话,也不算是错的。
举个栗子:
var a = {a:1};
// 原型链:a --> Object.prototype --> null
var a = ['xxx','xxx'];
// 原型链:a --> Array.prototype --> Object.prototype --> null
function a(){ }
// 原型链:a --> Function.prototype --> Object.prototype --> null
复制代码
没了。
总结
虽然很努力的想讲明白,但是因为我已经明白了,有些细节的地方我觉得不用写,但是不写又觉得不够严谨,所以推荐去看【冴羽】大神原文 JavaScript深入之从原型到原型链,多啃几遍,就明白了。
最后一篇!~终于可以摸鱼了。