JavaScript 原型链

前言

这是我参与新手入门的第三篇文章,强烈推荐【冴羽】大神的 JavaScript深入系列文章,我今天要写的原型链,也是参考了其中的第一篇。

一、步骤拆解

首先,只要明白了什么是原型,自然就知道了原型链。其次,原型链围绕的无非三点:构造函数、实例对象、原型对象。我准备分以下三个部分来讲解:

  1. 简单了解一下怎么找到构造函数、实例对象、原型对象。
  2. 搞明白构造函数、实例对象、原型对象三者之间的关系。
  3. 引用实例和原型的关系来找出原型链。

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. 构造函数指向原型对象
  2. 实例对象指向原型对象
  3. 原型对象指向构造函数

1. 构造函数 —> 原型对象

每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。

这句话有些绕,看下面这个列子:

// 构造函数 Person
function Person(){

}
// 实例对象 person
var person = new Person();
console.log(Person.prototype === 原型对象)
复制代码

就是构造函数 Person 的 prototype 属性,指向了原型对象的意思。这里的原型对象是实例对象person被创建的时候自动关联的那个原型对象!

注意:只有函数才有 prototype 属性!构造函数也是函数。

关系图如下:

原型-函数.png

这里有一个点,原型对象其实是一个概念词,并没有某个具体值能书写他,既然这里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)。

更新关系图:

实例-原型.png

3. 原型对象 —> 构造函数

前两个关系中,实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?

  • 原型指向构造函数 — 有,constructor。
  • 原型指向实例 — 没有,因为一个构造函数可以生成多个实例。

每个原型都有一个constructor属性指向关联的构造函数。

// 构造函数 Person
function Person() {

}
console.log(Person === Person.prototype.constructor); //true
复制代码

更新关系图:

原型-构造函数.png

三、实例和原型

目前为止,构造函数,实例,原型对象的指向关系都明白了,但是要找到原型链,还要讲一讲实例和原型的关系。

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

// 构造函数 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
复制代码

s.png

最初的原型对象是通过Object构造函数生成的实例对象,结合之前所讲,实例的__proto__指向构造函数的prototype,所以我们再更新下关系图:

对象原型.png

那Object.prototype的原型呢?

就是null,可以打印:

console.log(Object.prototype.__proto__ === null) // true
复制代码

引用阮一峰老师的 《undefined与null的区别》 就是:

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

查找属性的时候查到 Object.prototype 就可以停止查找了。

原型链.png

其实原型链已经出来了,图中由相互关联的原型组成的链状结构就是原型链,上图中蓝色的那条线。

怎么说呢,原型链就是一个链条而已这句话,也不算是错的。

举个栗子:

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深入之从原型到原型链,多啃几遍,就明白了。

最后一篇!~终于可以摸鱼了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享