# 原型与原型链

在 JavaScript 中,你可能会经常看到原型、原型链、prototype__proto__ 等高深莫测的词汇,它们到底是什么,在 JavaScript 中起着什么作用,让我们一起来揭开这层神秘的面纱。


# 彻底终结原型链吧

文章极短,没有图!

# 1.概念

# proto

  • 对象特有
  • 指向上层(创建自己的那个构造函数)的pototype
  • 因此对象可以从 prototype 中继承属性和方法

# prototype

  • 函数特有
  • 用于存储要共享的属性和方法

# constructor

  • 函数特有,定义在prototype里面
  • 通过new创建实例时,该实例便继承了prototype的属性和方法

# 2. 既是函数也是对象

# Object:既是对象,也是构造函数

  • 作为对象:Object.__proto__ = Function.prototype

  • 作为函数:Object.prototype是原型链的顶端,Object.prototype.__proto__ = null

# Function:既是对象,也是构造函数

  • 作为对象:Function.__proto__ = Function.prototype

  • 作为函数:Function.prototype用于共享,而Function.prototype.__proto__继承自Object.prototype

# Array(Date...):既是对象,也是构造函数

  • 作为对象:Array.__proto__ = Function.prototype
  • 作为函数:Array.prototype用于共享,Array.prototype.__proto__继承自Object.prototype

# 普通对象Person:既是对象,也是构造函数

  • 作为对象:Person.__proto__ = Function.prototype
  • 作为函数:Person.prototype用于共享,Person.prototype.__proto__继承自Object.prototype

# 总结

  1. 原型链顶端是Object.prototype

  2. 构造函数创建的对象(Object、Function、Array、普通对象等)都是Function的实例,它们的__proto__均指向Function.prototype

  3. 除了Object,所有对象(或叫构造函数)的prototype,均继承自Object.prototype

# 希望这篇简短的文章可以帮助你彻底理解原型链,如果觉得文章有用,点赞鼓励一下吧~

JavaScript 也是一门面向对象的语言,很神奇的是,在 ES6 之前,JavaScript 中没有 class 语法。接触过大学课程的都知道,c++ 也是面向对象的,典型的面向对象语言都是通过类来创建实例对象。

// c++ 通过类创建对象class Cat {
public:
  string color
}

Cat \*cat = new Cat()
1
2
3
4
5
6

那 JavaScript 是如何解决这个问题的呢?这就需要引入构造函数了。

# 构造函数

在 c++ 中,我们可以知道,类是事物的抽象,通过类可以生成一个个实例化的具体对象,类提供着生成对象的“模板”。在 JavaScript 中构造函数(constructor)就起着“模板”的作用,通过构造函数,我们可以生成实例化的对象。

functionCat() {
this.color = 'orange'
}

var cat = new Cat()
console.log(cat.color)     // orange
1
2
3
4
5
6

在上面的代码中,Cat 就是构造函数,使用 new 关键字来调用构造函数生成实例对象,我们约定构造函数的函数名要大写,在构造函数的内部可以使用 this 关键字来添加属性。

# prototype

了解完了构造函数,我们来看一下与函数相关的 prototype 关键字。每个函数都有一个 prototype 属性,它其实是个对象,我们可以通过代码来看一下。

functionCat() {
  this.color = 'orange'
}

console.log(Cat.prototype)
1
2
3
4
5

打开 chrome 浏览器的开发者工具,在 console 栏输入上面的代码,你可以看到 Cat.prototype 的值:

Cat.prototype 在控制台的输出结果)

# __proto__

在 JavaScript 中,每个实例对象都有一个私有属性 [[Prototype]],该属性指向了这个实例对象的原型,你可以通过 ES6 的 Object.getPrototypeOf() 来访问该属性,许多浏览器也对 [[Prototype]] 进行了实现,也就是我们经常见到的 __proto__,没错,__proto__ 指向了实例对象的原型,它也是一个对象。

functionCat() {
`this.color = 'orange'
}

var cat = new Cat()
console.log(cat.__proto__)
console.log(Object.getPrototypeOf(cat) === cat.__proto__)  // true
1
2
3
4
5
6
7

cat.__proto__ 在控制台的输出结果)

细心的你可能会发现,实例对象的 __proto__ 与创建该实例对象的构造函数的 prototype 是相等的,是的没错,构造函数的 prototype 指向调用该构造函数而创建的实例对象的原型,我们可以通过代码来看一下。

functionCat() {
this.color = 'orange'
}

var cat = new Cat()

console.log(cat.__proto__ === Cat.prototype)   // true
1
2
3
4
5
6
7

有关构造函数的 prototype 和实例对象的 __proto__ 的关系,我们可以用张图来体现一下。

(构造函数的 prototype 和实例对象的 __proto__ 关系图)

# constructor

在上文的 Cat.prototype 打印截图中,相信你已经看到了 constructor 这个字段,字段的内容是一个函数,函数名和构造函数竟然一样。可以说,每个原型对象都有一个 constructor 属性,指向相关联的构造函数,所以构造函数和构造函数的 prototype 是可以相互指向的。

functionCat() {
  this.color = 'orange'
}

console.log(Cat.prototype.constructor === Cat)    // true
1
2
3
4
5

(实例原型的 constructor 属性指向相关构造函数)

# 原型链

在上文我们理解了原型,原型链肯定是与原型有关了,是一个个原型链接起来的么?我们先通过下面的代码来观察一下。

functionCat() {
this.color = 'orange'
}

Cat.prototype.age = 4

var cat = new Cat()

console.log(cat.color)    // orange
console.log(cat.age)      // 4
1
2
3
4
5
6
7
8
9
10

在构造函数中,我并没有设置有关 age 的属性,只是把 age 设置在了实例原型上,然后我们通过实例对象也能访问到 age 属性。在 JavaScript 中,如果想访问某个属性,首先会在实例对象(cat)的内部寻找,如果没找到,就会在该对象的原型(cat.proto,即 Cat.prototype)上找,我们知道,对象的原型也是对象,它也有原型,如果在对象的原型上也没有找到目标属性,则会在对象的原型的原型(Cat.prototype.proto)上寻找,以此内推,直到找到这个属性或者到达了最顶层。在原型上一层一层寻找,这便就是原型链了。

那么原型链的最顶层是什么呢?我们可以在控制台测试看看。

functionCat() {
this.color = 'orange'
}

var cat = new Cat()

console.log(Cat.prototype.__ptoto__)
1
2
3
4
5
6
7

我们在控制台输出了实例对象原型的原型:

(构造函数 Cat 实例原型的原型)

输出了一大堆东西,好像看不出个所以然,比较直观的是,它是个对象,第一行的 constructor 是一个 Object 函数。我们在上文提过,每个原型对象都有一个 constructor 属性,指向相关联的构造函数,比如 Cat.prototype.constructor === Cat,在上面的截图中,我们可以猜测,xx.prototype.constructor === Object,可以知道 xx 就是构造函数 Object。

上面的输出内容其实就是 Object.prototype,我们用代码验证一下。

functionCat() {
this.color = 'orange'
}

console.log(Cat.prototype.__ptoto__ === Object.prototype)    // true
1
2
3
4
5

如果再往上寻找呢?Object.prototype 的原型会是什么?

console.log(Object.prototype.__proto__)   // null
1

它就是 null,null 没有原型,所以 Object.prototype 就是原型链的最顶端。

可以说,JavaScript 中的所有对象都来自 Object,Object 位于原型链的最顶端,几乎所有 JavaScript 的实例对象都是基于 Object。

我们可以将图片更新一下:

(原型链)

# 关于继承

JavaScript 的继承是基于原型链的,在原型链的任何位置设置属性,都能被对象访问到,原型的作用也是在此,它可以包含所有实例共享的属性和方法,就像该属性本来就在实例对象上一样,与其说是继承,不如说原型链建立了一个链条,可以顺藤摸瓜,实例对象可以访问这根链条上的属性和方法。

functionCat() {
this.color= 'orange'
this.age = 4
}

Cat.prototype.getColor = function() {
console.log(this.color)
}

Object.prototype.getAge = function() {
console.log(this.age)
}

var cat = new Cat()

cat.getColor()       // orange
cat.getAge()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

基于原型链的继承其实随处可见,只是我们没有意识到。当你随手新建一个数组,是否想过它怎么会有 splice、indexOf 等方法,新建一个函数怎么可以直接使用 call 和 bind?其实数组都继承于 Array.prototype,函数都继承于 Function.prototype,它们分别包含了数组和函数的基本方法,尝试去控制台打印出 Array.prototype 和 Function.prototype,上面的疑问便可得到解答。

vara= ['hello', 'world']
function f() {}

console.log(a.__proto__ === Array.prototype)      // true
console.log(f.__proto__ === Function.prototype)   // true
1
2
3
4
5

# 小结

本节我们学习了原型和原型链及与它们相关的一些关键词的含义。JavaScript 对象(除了 null)在创建的时候就会关联一个对象,这个对象就是原型,每一个对象都会从原型上继承属性,原型也是对象,所以原型也有原型对象,层层往上,直到 Object.prototype,这就是原型链。对象都会有一个 __proto__ 属性来访问自己的原型,同时这个原型就是生成该对象的构造函数的 prototype 属性值。每个原型对象都有一个 constructor 属性,指向相关联的构造函数。在本节中,你需要掌握:

  • 与原型相关的 prototype__proto__ 分别是什么;

  • construct 指向了哪里;

  • 原型链和继承。

参考链接: