JS原型链和继承图文理解

一. JS原型链概况

JS的构造函数(即一个普通的Function),可以理解为面向对象的一个Class。文字表现太生涩,没有图片理解形象。

function Father() {
  this.info = ["Zero"]
}
Father.prototype.test = function(){
  console.log("test prototype")
}
let f = new Father()
debugger

在这里插入图片描述

图1-1

这个图中要理解4个东西:
1.函数
2.prototype指针
3.__proto__对象内部指针
4.constructor 指针
接下来对这四个东西、进行详细解释:

1、函数

  1. 首先如代码所示,申明了一个Father的函数,函数名Father其实是一个指 针,指向函数对象。 因为JS中,所有函数都是Function对象。
  2. 接下来图中红色框1中所示,对象的展示方式是1这个样式的,arguments、caller、length、name、都是函数对象的属性。
    2.1 arguments是函数传入的参数,可以用arguments[0]等类似于数组下标的方式取值,0代表第一个参数。
    2.2 length代表参数个数,
    2.3 caller是指针,储存调用函数的的函数引用

2、prototype

prototype其实也是一个指针,指向的是原型对象,如果你把红色框中2收起,你会发现它是由{ xx:xx }大括号表示的一个对象,
该对象包含一个:constructor和__proto__属性,应该说只要是原型对象都会包含这两个属性

3、__proto__对象内部指针

  • 因为函数本身就是一个Function对象,所有的对象内部都有一个__proto__的指针,指向原型对象,那么__proto__其实指向的是Function的原型对象,Function的原型对象是一个Object.
  • 因为Funtion的原型对象上有apply和bind方法,因此每一个函数都可以使用apply方法来改变构造函数this指针。

4、constructor

constructor指向的其实是构造函数的实现。

  • 因此红色框2中的constructor指向的是Father函数的实现。
  • 红色框3中的constructor指向的是Function的实现(不要忘了,函数是Function对象)。

原型链的搜索过程

怎样寻找方法或属性

在这里插入图片描述

图1-2
let f = new Father()
f.test();

如果调用 f.test(),寻找过程是这样的:
在这里插入图片描述

2. JavaScript继承

1、原型链继承

由于原型链向上搜索的关系,JS可以衍生出继承。首先展示一下原型链继承

/*-------父类Super------*/
function Super(){
}
//原型对象上的方法
Super.prototype.getSuperValue = function(){
console.log("调用父类方法")
}
//原型对象的属性
Super.prototype.superValue = "父类属性"

/*-----子类Child-----*/
function Child(){
}
//修改原型对象
Child.prototype = new Super();
//实例化子类
let child = new Child()
//调用父类方法
child.getSuperValue();

搜索过程 child 实例–>child.proto.

1.1原型链继承有一个缺点

所有Child的实例会共享属性和方法,因此可以看作是一个浅拷贝,一个修改,全部的都会受影响。

1.2 如何优化

共享方法这个没有什么问题,主要是共享属性,如何深拷贝一份子类的属性呢?
答案是使用调用父类的构造函数,使用子类的this指针。

/*-------父类Super------*/
function Super(name){
   this.name = name
}
/*-----子类Child-----*/
function Child(name){
   Super.call(this, name)
}
let child1 = new Child("张三")
let child2 = new Child("李四")

在这里插入图片描述

图2-1

1.3 组合两者的优点,组合继承

组合继承结合两者优点,使子类拥有单独的属性,同时又拥有prototype上的公共方法。

/*-------父类Super------*/
function Super(name){
   this.name = name
}
/*-----子类Child-----*/
function Child(name){
   // Super 构造函数调用一次
   Super.call(this, name)
}
//修改原型对象, super初始化一次,调用构造函数一次
Child.prototype = new Super();

1.4 组合继承的缺点

在这里插入图片描述

图2-1

*如上图所示,会出现两个name属性,为什么呢?*

看1.3的代码,在Super的构造函数调用了两次,因此在原型对象上有name属性,同时Child的实例上也有name属性。

2.寄生组合继承

由于组合继承的缺点,如图2-1所示,有没有一种策略,让修改Child原型对象时,只获得Super.prototype上的方法,而不需要在实例化Super?


答案是:寄生继承。

2.1 寄生继承

寄生继承的主要思路是,既然不能让Super实例化,那就让别的Class实例化。
基于这个思路: 实例化一个Temp的临时对象,让Temp.prototype = Super.prototype. 这其实是对Super.prototype进行了一次浅拷贝。
大概样子是这样:

function getTempInstance(Child ,Super){
        function Temp(){}
        //浅拷贝Super的原型对象
        Temp.prototype = Super.prototype
        let temp = new Temp()
        //浅拷贝Child的构造方法
        temp.constructor = Child
        return temp;
}

于是整体就变成了这样:

/*-------父类Super------*/
function Super(name){
   this.name = name
}
/*-----子类Child-----*/
function Child(name){
}
//修改原型对象
Child.prototype = getTempInstance(Child,Super);

2.2 寄生组合继承

要理解这个挺简单,加上之前的调用构造函数。也就是所谓的寄生组合继承了。是不是很简单呀。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值