目录

  1. 概述
    1. 构造函数
    2. 函数对象
  2. 原型链
  3. 原型
    1. 每个函数都有一个原型属性 prototype 对象
    2. 每个原型对象都有一个 constructor 属性指向关联的构造函数
    3. 原型链顶层:Object.prototype.__proto__== null
  • 第二版
    1. 原型
    2. 原型链

    概述

    摘自JavaScript高级程序设计:
      继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承实现继承 .接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的.
      在 JavaScript 中,是一种面向对象的程序设计语言,但是 JS 本身是没有 “类” 的概念,JS 是靠原型和原型链实现对象属性的继承。
      在理解原型前,需要先知道对象的构造函数是什么,构造函数都有什么特点?

    构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 构造函数 Person()
    function Person(name, gender) {
    this.name = name;
    this.gender = gender;
    }
    var person = new Person("周杰伦", "男");
    // 最后创建出来的对象实例 person
    person
    {
    name: "周杰伦",
    gender: "男"
    }

    以上代码,普通函数 Person(),加上 new 关键字后,就构造了一个对象 person
    所以构造函数的定义就是普通函数加上 new 关键字,并总会返回一个对象。

    函数对象

      同时,JS 中的对象分为一般对象和函数对象。那什么是一般对象,什么又是函数对象呢?
      JavaScript 的类型分为基本数据类型引用数据类型,基本数据类型目前有 6 种(null, undefined, string, number, boolean, Symbol)。 其余的数据类型都统称为 object 数据类型,其中,包括 Array, Date, Function等,所以函数可以称为函数对象。

    1
    2
    3
    4
    5
    let foo = function(){}
    foo.name = "bar";
    foo.age = 24;
    console.log(foo instanceof Function) //true
    console.log(foo.age) // 24

    以上代码就说明了函数其实是一个对象,也可以具有属性

    原型链

      JavaScript 中的对象,有一个特殊的 [[prototype]] 属性, 其实就是对于其他对象的引用(委托)。当我们在获取一个对象的属性时,如果这个对象上没有这个属性,那么 JS 会沿着对象的 [[prototype]]链 一层一层地去找,最后如果没找到就返回 undefined;
    这条一层一层的查找属性的方式,就叫做原型链。

    1
    2
    3
    var o1 = {
    age: 6
    }

      那么,为什么一个对象要引用,或者说要委托另外一个对象来寻找属性呢?
      本文开篇的第一句话,就指出来的,JavaScript 中,和一般的 OOP 语言不同,它没有 ‘类’的概念,也就没有 ‘模板’ 来创建对象,而是通过字面量或者构造函数的方式直接创建对象。那么也就不存在所谓的类的复制继承。

    原型

      那什么又是原型呢?
      既然我们没有类,就用其他的方式实现类的行为吧,看下面这句话↓↓。

    每个函数都有一个原型属性 prototype 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person() {}

    Person.prototype.name = 'JayChou';
    // person1 和 person2 都是空对象
    var person1 = new Person();
    var person2 = new Person();

    console.log(person1.name) // JayChou
    console.log(person2.name) // JayChou

      通过构造函数创造的对象,对象在寻找 name 属性时,找到了 构造函数的 prototype 对象上。
      这个构造函数的 prototype 对象,就是 原型
    用示意图来表示:
    原型
      查找对象实例属性时,会沿着原型链向上找,在现代浏览器中,标准让每个对象都有一个 __proto__ 属性,指向原型对象。那么,我们可以知道对象实例和函数原型对象之间的关系。
    原型

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

    为了验证这一说话,举个例子。

    1
    2
    function Person() {}
    Person === Person.prototype.constructor; // true

    那么对象实例是构造函数构造而来,那么对象实例是不是也应该有一个 constructor 呢?

    1
    2
    3
    4
    function Person() {}

    const person = new Person();
    person.constructor === Person // true

    但事实上,对象实例本身并没有 constructor 属性,对象实例的 constructor 属性来自于引用了原型对象的 constructor 属性
    person.constructor === Person.prototype.constructor // true
    原型

    原型链顶层:Object.prototype.__proto__== null

      既然 JS 通过原型链查找属性,那么链的顶层是什么呢,答案就是 Object 对象,Object 对象其实也有 __proto__属性,比较特殊的是 Object.prototype.__proto__ 指向 null, 也就是空。
    Object.prototype.__proto__ === null
    原型
    我们回过头来看函数对象:

    所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    Number.__proto__ === Function.prototype  // true
    Number.constructor == Function //true

    Boolean.__proto__ === Function.prototype // true
    Boolean.constructor == Function //true

    String.__proto__ === Function.prototype // true
    String.constructor == Function //true

    // 所有的构造器都来自于Function.prototype,
    // 甚至包括根构造器Object及Function自身
    Object.__proto__ === Function.prototype // true
    Object.constructor == Function // true

    // 所有的构造器都来自于Function.prototype,
    // 甚至包括根构造器Object及Function自身
    Function.__proto__ === Function.prototype // true
    Function.constructor == Function //true

    Array.__proto__ === Function.prototype // true
    Array.constructor == Function //true

    RegExp.__proto__ === Function.prototype // true
    RegExp.constructor == Function //true

    Error.__proto__ === Function.prototype // true
    Error.constructor == Function //true

    Date.__proto__ === Function.prototype // true
    Date.constructor == Function //true

    所有的构造器都来自于 Function.prototype,甚至包括根构造器ObjectFunction自身。所有构造器都继承了·Function.prototype·的属性及方法。如lengthcallapplybind

    以图会友,这就是网上经常看到的 JS 原型和原型链关系图:
    原型

    对于以上看似很复杂的关系图,只需要理解 5 点:
      每个函数都有一个原型属性 prototype 对象
      普通对象的构造函数是 Object(),所以 Person.prototype.__proto__ === Object.prototype
      函数对象都来自于 Function.prototype
      函数对象也是对象,所有 Function.prototype.__proto__ === Object.prototype
      记住,所有函数原型的都是 Object() 的实例
      Object.prototype.__proto__null

    第二版

    原型

      原型对象只存在于函数对象。也就是本质上只要是通过new Function创建的函数对象会有一个原型对象。
      而对于其他非Function的引用类型归根结底也是通过new Function创建的。
      如上面提到的Array类型、Object类型。
      实际上,在每个函数对象创建的时候,都会自带一个prototype的属性,这个属性相当于一个指针,指向他本身的原型对象,这个原型对象里包含着自定义的方法属性,
    现假设创建了函数对象a

    1
    2
    3
    4
    5
    6
    function a(){
    this.name='xiaoming';
    this.sayName=function () {
    console.log(this.name);
    }
    }

    则会有如下所示的结构

      在默认情况下,a.prototype下会带有一个constructor属性,这个属性指向创建当前函数对象的构造函数,比如这里
    constructor指向构造函数a本身也就是说

    1
    a.prototypr.constructor==a   //true

      另外默认还有一个_proto_属性,这个属性指向由创建这个函数对象的引用类型中继承而来的属性和方法。
      当通过构造函数实例化一个对象b时,即new a();
    则会产生如图所示结构

      首先这个new出来的对象属于普通对象,所以没有prototype属性。但他有_proto_这个属性,这个属性指向创建它的引用类型的原型对象,在这个例子中指向a.prototype,从而继承来自引用类型a的属性和方法。
      而原型的很大一部分作用是用来继承的,在上面的例子中,b就继承了a中的属性name,和方法sayName
    总结一下要点就是:

    1
    2
    1、原型对象只存在于函数对象中;
    2、prototype为函数对象的一个属性,这个属性指向原型对象;(a.prototype);

    原型链

      上面原型说的继承是指当前引用类型的实例继承来自引用类型的属性方法。而通过原型链我们可以进行两个类型之间的继承。假设当前有两个aa、AA:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      function aa(){
    a.prototype.name='xiaoming';
    a.prototype.sayName=function () {
    console.log(this.name);
    }
    }
    function AA() {
    A.prototype.age=15;
    A.prototype.sayAge=function(){
    console.log(this.age);
    }
    }
    //AA继承aa
    AA.prototype=new aa();
    var BB=new AA();
    console.log(BB);
    console.log(BB.sayName()) //"xiaoming"

    打印出的结果为
      大致流程就是通过AA.prototype=new aa();使得AA.prototype(原型对象)下的proto指向aa.prototype,当创建引用类型AA的实例化对象BB时,BB内部会产生一个proto属性指向AA的原型对象,再通过AA原型对象中的proto指向aa的原型对象,从而实现实例对象BB对aa的继承,整个链向:BB的proto —->AA.prototype—->aa.prototype,这就是一条原型链,如果在继续延伸的话,aa的原型对象下的proto属性会指向Function本身。
    整个结构图大致如图所示:

    转载:
      JavaScript原型与原型链
    参考:
      2019 面试准备 - JS 原型与原型链
      JS原型链与继承别再被问倒了
      三张图搞懂JavaScript的原型对象与原型链
      JavaScript原型及原型链
      温故js系列(15)-原型&原型链&原型继承
      继承与原型链