【JS继承】JS继承之原型链继承

138 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

自我介绍:大家好,我是吉帅振的网络日志;微信公众号:吉帅振的网络日志;前端开发工程师,工作4年,去过上海、北京,经历创业公司,进过大厂,现在郑州敲代码。

JS继承专栏

1【JS继承】什么是JS继承?

2【JS继承】常见的7种继承方式

3【JS继承】JS继承之原型链继承

4【JS继承】JS继承之构造函数继承

5【JS继承】JS继承之组合继承

6【JS继承】JS继承之原型式继承

7【JS继承】JS继承之寄生式继承

8【JS继承】JS继承之寄生组合式继承

9【JS继承】JS继承之ES6 Class继承

image.png

如图所示:函数 Personal 包含3个部分,转换为代码如下:
 
// 函数对象(构造函数)
function Personal () {
}
// 函数原型对象
Personal.prototype = {
    constructor:Personal ,
    name:'hwk',
    sayName:function () {}
}
// p : 函数实例
var p = new Personal();
p.name      // 访问函数原型对象上的属性
p.sayName() // 调用函数原型对象上的方法

一、函数

所谓函数也就是 函数 Personal 其本身,也叫作构造函数 ,当一个函数被创建的同时,也会为其创建一个 prototype 属性,而这个属性,就是用来指向 函数原型,的我们可以把 prototype 理解为 Personal的一个属性,保存着函数原型的引用。

二、函数实例

函数实例很好理解,就是上面代码中通过 new Personal() 得到的实例p,于此同时函数实例 p 内部会包含一个指向 函数原型的指针[[Prototype]],因此我们通过 p 可以去调用 函数原型 上的属性和方法,但是由于[[Prototype]] 是内部属性,无法直接访问,但是可以通过以下方式进行获取:proto : 部分浏览器提供了此属性去访问[[Prototype]]属性的值,通过Object.getPrototypeOf 去获取。

三、函数原型

函数原型其实也是一个对象,它通过其constructor 属性与函数 Personal 进行关联,上面的代码中,通过重新赋值的方式定义了 Personal 的原型的属性和方法。

Personal.prototype = {
    constructor:Personal ,
    name:'hwk',
    sayName:function () {}
}

这里大家注意一下,因为这种方式定义属性和方法,会打断函数(Personal)和函数原型(Personal.prototype)之间的关系,因此需要重新将constructor 属性赋值为 Personal 函数本身,与其建立联系。

其实我们把 函数原型 看做一个独立的对象即可,它与其 函数 通过constructor 属性关联,上面说的函数(Personal) 有个prototype 属性是指向函数原型(Personal.prototype)。

Personal.prototype  === Personal.prototype

这里的代码我们把等号后面的 Personal.prototype 理解成一个单独的对象即可,而等号前面我们理解成 Personal 调用 prototype 属性,因为函数原型对象我们一般用这样 Personal.prototype 来表示。

image.png

如上图所示: Personal 对象想要继承 Main 对象,则通过将 Main 的实例赋值给 Personal 的原型对象 :

Personal.prototype = new Main () ; 如此 Personal原型对象 就能通过 Main 对象的实例中的 [[Prototype]] 来访问到 Main原型对象 中的属性和方法了,而此时大家注意,Personal原型对象 则与 Personal函数 断开了联系,因为Personal原型对象被重新赋值了,所以还需要重新将Personal函数和Personal原型对象建立联系:

Personal.prototype.constructor = Personal ;
 
完整代码如下:
 
function Main () {
}
Main.prototype.sex = '男' ;
Main.prototype.eat = function () {
    console.log('Main eat ...')
}
 
 
function Personal () {}
Personal.prototype.name = 'hwk';
Personal.prototype.sayName = function () {
    console.log('Personal name')
}
 
// 继承
Personal.prototype = new Main();
Personal.prototype.constructor = Personal;
 
var p = new Personal();
console.log(p.sex ) ; // 男
console.log(p.name) ; // undefined
p.eat();              // Main eat ...
p.sayName ();          // Uncaught TypeError:p.sayName is not a function 
运行如上代码你会发现 p.name 为 undefined , p.sayName 这个方法没找到,原因在于我们后面重新赋值了 Personal.prototype = new Main(); 因此找不到一开始定义在 Personal.prototype 上的name属性和sayName方法,因此在使用原型链继承的时候,要在继承之后再去在原型对象上定义自己所需的属性和方法:

// 先继承
Personal.prototype = new Main();
Personal.prototype.constructor = Personal;
 
// 后定义属性和方法
Personal.prototype.name = 'hwk';
Personal.prototype.sayName = function () {
    console.log('Personal name')
}
 
// 正确输出
console.log(p.sex ) ; // 男
console.log(p.name) ; // hwk
p.eat();              // Main eat ...
p.sayName ();          // Personal name

此时 Personal的实例 已经可以访问到父类Main原型对象中的方法和属性了,这也就是原型链继承的方式。在原型对象上定义属性和方法,其所有的构造函数实例都能共享原型上的属性和方法,因此如果某一个构造函数实例对象修改了原型对象上的属性值和方法,则也会影响其他实例对象。