原型对象、原型、原型链以及原型链继承

一、什么是原型对象

  首先明确一下定义,每个函数都会有它的原型对象,也就是prototype,这是在函数创建的时候浏览器会根据一定规则自动生成的。看下图:

   

  可以看到,函数的prototype里面包含一个construction,它会指向函数本身。另外一个__proto__,也就是我们下面将会讲到的原型

二、什么是原型

  每一个JavaScript对象(null除外,谨记)都有它的原型,也就是__proto__这个属性。我们拿上面的构造函数创建一个实例来举例:

  

   看到没有,有没有觉得很熟悉!!对,其实它指向的就是Person的原型对象。换一句人话来说就是,JavaScript对象的原型是一个指针,它指向的是它的构造函数的原型对象(可能有点绕口,建议多念几遍理解一下)。千万不要把原型和原型对象弄混了,原型对象只有函数才有的,非函数是不存在原型对象的,原型是JavaScript对象都有的。

  console.log(person.prototype);  // undefined

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

三、什么是原型链

  在对JavaScript进行学习的时候,你一定听过“作用域链”这个名词,即在某一作用域里使用到的变量,若在当前作用域里找不到,便会继续往上层作用域里进行查找,直至到全局变量中。其实原型链与其是十分相似的。我们前面说到,实例对象的原型是会指向它的构造函数的原型对象的。那么就会出现一种情况,如果我需要通过Person这个构造函数来创建多个实例对象person1、person2、person3....它们除了名字不同,但是我想要它们拥有相同的操作方法,比如说它们都会说话speak、都会吃东西eat,这种情况需要怎么实现呢。先自己思考一下再继续往下看!

  聪明的你一定能想到,那在Person的原型对象上添加这些操作方法不就可以了!因为每一个实例的原型都会指向Person的原型对象。

  

   是不是很好理解。但这还不是完整的原型链,我们说作用域链是往上层作用域去查找变量,原型也是一样的,如果在对象本身中找不到变量,那么它就会往它的原型中查找,还找不到,就再往原型的原型中进行查找,直至null(这个具体可以自己动手去试一下,这里稍微提一下Person.prototype.__proto__ === Object.prototype,Object.prototype.__proto__ === null)。那么这个闭环就形成了一条原型链,是不是觉得跟作用域链十分相似。原型的应用十分丰富,下面提一下一个比较重要的应用 --- 利用原型链来实现继承(在ES5之前基本都是靠原型链来实现继承的,在ES6之后才引入了class这个语法糖)。

四、原型链继承

  不多逼逼直接看代码,再来慢慢分析。

 1 // 初始化父类
 2 function Person(name) {
 3     this.name = name;
 4 }
 5 
 6 // 在父类的原型对象上添加操作方法,方便后面测试子类是否有继承到
 7 Person.prototype.speak = function() {
 8     console.log(`My name is ${this.name}.`);
 9 }
10 
11 // 子类初始化
12 function Student(name, age) {
13     // 将父类构造函数内部的属性指向改为指向子类
14     Person.call(this, name);
15     this.age = age;
16     // 子类自己构造函数内部的操作方法
17     this.speak1 = function() {
18         console.log(`I am ${this.age} years old.`);
19     }
20 }
21 
22 // 初始化一个父类实例,并赋值给子类的原型对象
23 Student.prototype = new Person();
24 Student.prototype.speak2 = function() {
25     console.log('I am a student.');
26 }
27 
28 // 用子类实例化一个对象student
29 var student = new Student('Peter', '16');

  大部分代码应该都看得懂,比较有问题的可能就是23行,为什么要将父类实例化并且赋值给子类的原型对象。我们前面说到,一个实例对象,是可以访问到其构造函数的原型对象上的方法的,是不是恍然大悟了!然后在子类初始化的时候,使用call/apply的目的是为了让子类能够访问到父类构造函数内部的属性/方法,这样就能够完全继承到父类的所有属性和方法了。具体大家可以自己动手尝试一下。

   

posted @ 2020-04-11 13:16  卑微小陈的随笔  阅读(577)  评论(0编辑  收藏  举报