原型与原型链

13 阅读2分钟

前言

在 JavaScript 中,原型是一个非常重要的概念。
如果不理解原型,很多内容都会变得很模糊,比如:

  • 构造函数
  • 实例方法共享
  • 原型链
  • 类的本质

本文重点讲清楚:什么是原型、为什么需要原型、prototype 和 proto 有什么区别、原型链


一、什么是原型?

在 JavaScript 中,每个函数都有一个特殊属性:

prototype

这个属性指向一个对象,这个对象就叫做 原型对象

看一个例子:

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

Person.prototype.sayHello = function () {
  console.log(`你好,我是 ${this.name}`);
};

const p1 = new Person('Tom');
const p2 = new Person('Alice');

p1.sayHello(); // 你好,我是 Tom
p2.sayHello(); // 你好,我是 Alice

这里:

Person.prototype

就是构造函数

Person

的原型对象。


二、为什么需要原型?

如果我们把方法写在构造函数内部:

function Person(name) {
  this.name = name;
  this.sayHello = function () {
    console.log(`你好,我是 ${this.name}`);
  };
}

那么每创建一个实例,就会重新创建一次

sayHello

方法。

这样会导致:

  • 方法重复创建
  • 浪费内存

如果写到原型上:

Person.prototype.sayHello = function () {
  console.log(`你好,我是 ${this.name}`);
};

那么所有实例就可以共享同一个方法。

所以原型最核心的作用就是:

让实例共享属性和方法。


三、prototype 和 proto 的区别

这是非常高频、也非常容易混淆的知识点。

1)prototype

  • 只有函数才有
  • 指向构造函数的原型对象

2)__proto__

  • 对象才有
  • 指向该对象的原型

看例子:

function Person() {}

const p = new Person();

console.log(Person.prototype);
console.log(p.__proto__);
console.log(p.__proto__ === Person.prototype); // true

这个关系非常重要:

实例对象.__proto__ === 构造函数.prototype


四、原型上的属性和实例上的属性

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

Person.prototype.age = 18;

const p = new Person('Tom');

console.log(p.name); // Tom
console.log(p.age);  // 18

这里:

  • name

    是实例自己的属性

  • age

    是原型上的属性

当访问

p.age

时,JS 发现实例本身没有这个属性,就会去原型上找。


五、原型的实际意义

原型的最大意义就是“共享”。

例如数组为什么都有

push

pop

map

这些方法?

因为这些方法都定义在:

Array.prototype

上。

所以数组实例本身不需要重复拥有这些方法。


六、总结

原型可以用一句话总结:

原型是 JavaScript 中实现属性和方法共享的机制。

重点记住:

  • 函数有

    prototype

  • 对象有

    __proto__

  • 实例.__proto__ === 构造函数.prototype


学完原型之后,接下来最重要的就是 原型链
原型链本质上解决的是一个问题:

当我们访问对象属性时,JavaScript 到底是怎么查找的?

理解了原型链,你就能更清楚地看懂:

  • 为什么对象可以调用某些方法

  • 为什么数组能用

    push

  • 为什么实例可以访问原型方法


原型链

一、什么是原型链?

当访问一个对象的属性或方法时,JavaScript 会先在对象本身查找。
如果找不到,就会去对象的原型上查找。
如果原型上还找不到,就继续去原型的原型上查找。
这一层一层向上查找的结构,就叫做 原型链


二、属性查找过程

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

Person.prototype.sayHello = function () {
  console.log(`你好,我是 ${this.name}`);
};

const p = new Person('Tom');

console.log(p.name); // Tom
p.sayHello();        // 你好,我是 Tom

查找p.name

  1. 先看

    p

    自身有没有

    name

  2. 有,直接返回

查找p.sayHello

  1. 先看

    p

    自身有没有

    sayHello

  2. 没有

  3. p.__proto__

    ,也就是

    Person.prototype

    上找

  4. 找到了,执行它


三、原型链的尽头是什么?

function Person() {}

const p = new Person();

console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

所以原型链大致是:

p
→ Person.prototypeObject.prototypenull

null

就是原型链的终点。


四、数组的原型链

数组也是对象,所以也有原型链。

const arr = [1, 2, 3];

console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true

原型链结构大致是:

arr
→ Array.prototypeObject.prototypenull

所以数组既能用数组方法,也能用对象原型上的某些方法。


五、为什么原型链重要?

因为 JavaScript 中很多方法并不是对象自身直接拥有的,而是通过原型链继承来的。

例如:

const arr = [1, 2, 3];

arr.push(4);
arr.toString();

这里:

  • push

    来自

    Array.prototype

  • toString

    可能继续来自更上层原型


六、总结

原型链可以简单理解为:

对象查找属性时,沿着原型一层层向上查找的链式结构。

查找规则:

  1. 先找对象自身

  2. 再找原型

  3. 再找原型的原型

  4. 直到

    null