​ 帮助理解前端常考知识点原型与原型链,介绍他们的相关概念以及他们是如何指向的。

基本概念

显示原型:prototype

  • 每个class/函数都有显示原型prototype,它默认指向一个Object空对象(即称为原型对象)
  • 原型对象中一个属性constructor,它指向函数对象

隐式原型proto

  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应的class/函数的prototype。

相关概念

  1. Function是所有函数(function)的父亲,所有函数都是它的实例。
  2. Object也是一个函数,所以Object是Function的实例对象。
  3. 任何对象的原型链顶端最终都指向Object.prototype,Object.prototype再往上已经没有东西了,所以指向null。
  4. Function比较特殊它的原型指向自己,即Function.proto === Function.prototype。
  5. 同时,Function是对象,由(3)可知Function必然有一个地方(即Function.prototype.__proto__)指向Object.prototype。

先来看一个简化版的原型链图:

image-20210303222509043

再来看一个完整版的:

原型与原型链

总之,记住一句话:A是B的实例,则A.__ proto __ === B.prototype;只要A是对象,那么A.prototype.__ proto__ 就指向Object.prototype;prototype中的constructor指向的是自己;

原型链与函数的继承

  1. ES5中的继承

    构造函数继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function Parent() {
    this.name = 'parent';
    }
    Parent.prototype.say = function () {
    console.log('say');
    };
    function Child(age) {
    Parent.call(this);
    this.age = age;
    }
    var p = new Parent(); //new一个Parent对象用来对比
    p.say(); //输出say
    var c = new Child(12);
    c.age // 12
    c.name //'parent'
    c.say(); //undifined
    /*
    say是Parent原型链上的方法,Parent对象调用方法时,如果自身不存在就回去原型链上寻找,在原型链上找到了say方法,而Child对象没有继承Parent对象的原型链,所以它在向上寻找时就找不到,输出undifined。
    */

    原型链继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Parent() {
    this.name = 'parent';
    }
    Parent.prototype.say = function () {
    console.log('say');
    };
    function Child(age) {
    this.age = age;
    }
    Child.prototype = new Parent();
    var c = new Child(12);
    console.log(c.name); //输出parent
    c.say() //输出say
    /*
    原型链继承是直接让Child构造函数的prototype直接指向Parent对象,这样Parent的东西Child对象可以直接从它的原型链上找到。缺点就是:当创建多个实例时,如果不同实例可能互相存在影响。
    */

    构造函数与原型链组合继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Parent() {
    this.name = 'parent';
    this.arr = [1,2,3,4]
    }
    Parent.prototype.say = function () {
    console.log('say');
    };
    function Child(age) {
    Parent.call(this);
    this.age = age;
    }
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    var c1 = new Child(12);
    var c2 = new Child(12);
    console.log(c1.arr); //[1,2,3,4]
    console.log(c2.arr);//[1,2,3,4]
    c1.arr.push(5);
    console.log(c1.arr); //[1,2,3,4,5]
    console.log(c2.arr); //[1,2,3,4]
  2. ES6中的继承

    ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法,才可使用this关键字,否则报错。),然后再用子类的构造函数修改this实现继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Parent2 {
    constructor() {
    this.name = 'parent';
    }
    }
    Parent2.prototype.say = function () {
    console.log('say');
    };
    class Child2 extends Parent {
    constructor(age) {
    super();
    this.age = age;
    }
    }
    var c2 = new Child2(12);
    console.log(c2.name); //输出parent
    c2.say(); //输出say
    console.log(c.constructor);
    //输出function Child(age) {Parent.call(this);this.age = age;}
    console.log(new Parent().constructor);
    //输出Parent() {this.name = 'parent';this.arr = [1,2,3,4];}