原型链

.

显式原型和隐式原型基本概念

  • 所有的构造函数(对象)都有一个属性:

    • prototype : 显式原型(属性),默认指向它的原型对象。显示原型在定义函数时自动添加,一般可以进行人为操作。
    • 每个原型对象中有一个属性:constructor,默认指向这个函数对象
  • 由函数对象创建的实例对象都有一个属性:

    • [[Prototype]](实际中是__proto__) : 隐式原型(属性),默认指向它构造函数对象的原型对象。隐式原型在创建对象时自动添加,它是内部指针,一般禁止对其进行直接操作。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function Person(){
      }

      Person.prototype.name = 'Nicholas';
      Person.prototype.age = 29;
      Person.prototype.job = 'Software Engineer';
      Person.prototype.sayName = function() {
      console.log(this.name);
      };

      var person1 = new Person();
      var person2 = new Person();
      console.log(person1.sayName === person2.sayName); //true
      enter description here

显式原型和隐形原型的关系:构造函数的显式原型的值和它实例对象的隐式原型的值相同,都指向构造函数的原型对象。

显式原型和隐式原型的生成过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//定义构造函数
function Fn() { // 内部语句: this.prototype = {};
}

// 1. 每个函数function都有一个prototype,即显式原型, 默认指向一个空的Object对象
console.log(Fn.prototype);

// 2. 每个实例对象都有一个__proto__,即隐式原型
var fn = new Fn(); // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__);

// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值(构造函数的显式原型对象 === 其构造函数实例对象的隐式原型对象)
console.log(Fn.prototype === fn.__proto__); // true

//给原型添加方法
Fn.prototype.test = function () {
console.log('test()');
};
//通过实例调用原型的方法
fn.test();

以上过程的内存解析:
原型内存解析

显示原型和隐式原型

  1. 每创建一个函数,该函数就有一个prototype,即显式原型属性(简称显式原型),指向一个空的Object对象(原型对象)
  2. 每给函数创建一个实例对象,该对象就有一个__proto__(书写上写作[[prototype]]), 即隐式原型属性(简称隐式原型),其值为其对应构造函数的显式原型的值。简而言之,隐式原型和显式原型中保存的地址值是相同的,都指向原型对象。ES5之前的版本,一般不宜对__proto__做直接操作。
  3. 对象的隐式原型的值为其对应构造函数显式原型的值
  4. 对于函数Function(),它有一个prototype,该函数自身的实例对象Function有一个__proto__。它们都指向原型对象。简而言之,Function函数的显式原型和该函数自己创建的隐式原型的值相同(其他函数并不存在这一点特征)。
  5. 函数所有实例的隐式原型__proto__的值都等于该函数显式原型prototype的值,并等于原型对象的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Fn() {
//浏览器引擎内部自动添加语句:this.prototype = { };
}
console.log(Fn.prototype); //一个Object实例
var fn = new Fn();
console.log(fn.__proto__); //内部语句:this.__proto__ = Fn.prototype
console.log(Fn.prototype === fn.__proto__); //true

//给原型对象添加方法
Fn.prototype.test = function(){
console.log('test');
}
fn.test(); //test。这一步是通过实例对象调用原型对象的方法。当实例对象中没有外部属性时,浏览器自动根据__proto__的值,即原型对象的地址,来寻找原型对象中的相关属性。

console.log(fn.__proto__ === Fn.prototype); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Fn.__proto__ === Fn.prototype); //false
console.log(Object.prototype.__proto__); //null

原型链

原型链(隐式原型属性链):访问一个对象属性时,先在自身属性中查找,可以找到就返回该属性值;若没有找到,则沿着__proto__这条链依次向上查找;若果在Object原型对象(原型链的尽头)仍没有找到该属性,则返回undefined。

但是JavaScript设定:Object的原型对象指向null

许多面向对象语言都支持两种继承方式:接口继承实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在 ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链(又称隐式原型链)来实现的。

主要思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
依据:每一个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(constructor),而函数的实例对象都存在一个指向原型对象的指针(__proto__)。假如实例所指向的原型对象本身又是另一个构造函数的实例对象(称另一个类的实例),那么此时原型对象(记为原型对象1)中就包含一个指向另一个原型对象(记为原型对象2)的指针,相应的,另一个原型对象包含一个指向另一个构造函数的指针。进一步可知,原实例对象与原型对象2的关系是:原实例对象的隐式原型的隐式原型指向原型对象2。事实上,原型对象2是原型对象1的父类型,原型对象1是原实例对象的父类型。
原型链的作用:访问对象属性

  • 访问一个对象的属性时,
    ① 先在实例对象自身属性中查找,看是否有该属性,若找到则直接返回该属性
    ② 如果没有找到, 再沿着__proto__这条链(即原型链)向上查找, 直到找到则返回该属性
    ③ 若始终没有找到,这种沿着原型链向上游查找也不是无限制的。JS中,一切对象都是Object()的实例对象,所以,它的原型对象将作为查找的终点。如果最终在Object()的原型对象Object.prototype中仍没有找到该属性, 则返回undefined(这个对象再没有实际指向了,它的隐式原型Object.prototype.__proto__值为null,是个空指针)
    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
    //创建父类
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }

    //创建子类
    function Student(name, age, sex) {
    Person.call(this, name, age); // 提取父类的属性
    this.sex = sex; //子类自己添加sex属性
    }

    Student.prototype = new Person(); // 真正实现继承
    Student.prototype.constructor = Student; //修正constructor属性

    //为Student创建一个实例对象girl
    var girl = new Student("silybily", 26, "female");

    //检验:
    console.log(girl);
    girl.__proto__ === Student.prototype; //true
    girl.__proto__.__proto__ === Person.prototype; //true
    girl.__proto__.__proto__.__proto__ === Object.prototype; //true
    girl.__proto__.__proto__.__proto__.__proto__ === null; //true

    console.log(girl.name); //silybily
    console.log(girl.hobby); //undefined
    原型链
    有一点需要特别注意的是:构造函数本身,首先是一个对象,理解了这一点,上面的过成就不难理解了

探索instanceof

作用:判断函数的显式原型是否在另一个对象的原型链上
语法A instanceof B 判断B函数的显式原型是否在A对象的原型链上。如果存在则返回true,否则返回false

1
2
3
4
5
//案例1
function Foo() { }
var f1 = new Foo();
console.log(f1 instanceof Foo); // true
console.log(f1 instanceof Object); // true

function Foo() { },构造函数Foo()的显式原型指向一个空的Object原型对象{ }
var f1 = new Foo();,声明了一个变量f1指向函数Foo()的实例对象
由以上两步可知,Foo.prototype === f1.__proto__ === { }因此Foo()的显式原型在对象f1的原型链上
Object是任何实例的构造函数,显然在f1的原型链上

1
2
3
4
5
//案例二
console.log(Object instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true

这个是怎么一回事呢?仍要回头看instanceof得定义:A instanceof B判断A的原型链上是否存在B.prototype。而在JavaScript中,Object和Function之间的关系可以用一张图来说明:
enter description here
从上图中抽离Function和Object的关系,即:
enter description here
根据该图①,再来看一下代码就一目了然了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//①构造器Function的构造器是它自身
Function.constructor=== Function; //true

//②构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
Object.constructor === Function; //true

//③构造器Function的__proto__是一个特殊的匿名函数function() {}
console.log(Function.__proto__); //function() {}

//④这个特殊的匿名函数的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype; //true

//⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函数
Object.__proto__ === Function.prototype; //true
Function.prototype === Function.__proto__; //true

如果看完以上,你还觉得上面的关系看晕了的话,只需要记住下面两个最重要的关系,其他关系就可以推导出来了:

1、所有的构造器的constructor都指向Function

2、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__proto__指向Object.prototype

总结

  • 显式原型与隐式原型的关系
    • 函数的prototype: 定义函数时被自动赋值, 原型对象的值默认为{}
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链
    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构—->原型链
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找

版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!