JS中 原型链继承 图示 个人理解

编程入门 行业动态 更新时间:2024-10-24 10:19:41

JS中 原型链继承 <a href=https://www.elefans.com/category/jswz/34/1767706.html style=图示 个人理解"/>

JS中 原型链继承 图示 个人理解

原型链继承

在学习的过程中,根据浏览器的输出,逆向分析得出的结论,但是JS中间的实现过程,我还不知道,但是逆向分析可以很好的理解如何继承.

个人理解,估计有错,慎看!

1, 首先写一个构造函数,添加两个属性和两个方法:

function Father(){this.baba = 'BABA'this.xx = 'xx'}Father.prototype.money = function(){this.baba = 'babababa'console.log('爸爸有钱')}Father.prototype.money_2 = function(){console.log('爸爸有钱2222')}console.log(Father) //说明点1console.log(Father.prototype) //说明点2

**说明点1:**这个时候我们直接输出看一下Father是什么,是不包含地下两个方法的源代码,这个直接输出的Father,对我们来说意思不大,就不看了.
说明点2: 看console.log(Father.prototype)的输出,如下图:

**从上面图片可以看出,构造函数只是 开辟了一块内存,然后把方法放到里面,根本就没有任何关于属性的操作, 注意这里我们仅仅创建了2个函数money() 和 money_2(),但是Father.prototype 里面竟然有3个函数和一个__proto__(姑且称之为属性把)

  1. 第一个是money()
  2. 第二个是: money_2()
  3. 第三个是: constructor()
  4. 第四个是__proto__ (注意这个)
    **
1.1 关于构造函数的总结:
  1. 不实现 属性, 压根就没有属性的位置
  2. 只把方法 放到一块内存里面,所有的方法放到这个,并且这个里面还有一个__proto__
  3. 竟然也有__proto__

2, 根据上面的构造函数,来实例化一个实例

var fa = new Father();
console.log(fa) //注意点1

整个输出如下图:

根据图片:

  1. 可以看到我们定义的两个属性出来了
  2. 同级别的还有一个__proto__的属性
    总结1:也就是说我们创建了一个实例,开辟了一块内存,里面放上所有的属性,还有一个__proto__的东西,就完事了,本质上就这么多工作。
  3. 但是!你注意看这个__proto__,展开看,他底下所有的内容的和上面的构造函数的整体输出是不是一样的?
  4. 也就是说这个 实例的__proto__ 它就相当于包含了整个构造函数中的东西了(我知道这么描述不对,但是这样描述整个和部分的关系,对于整体理解是有意义的,而且这里不是包含的关系,而是指向的关系。)
2.2 **关于实例的总结:
  1. 也就是说,实例单独开辟了一个内存空间,仅仅放上自己的属性和值,然后添加一个__proto__的属性,就完事了。注意这个部分的内存空间是这个实例独有的,独有的,独有的,自己的,自己的,不是公共的。**
  2. 实例已经有了,我们实例是可以调用方法的。。。但是实例的内存空间仅仅包含属性,并没有方法,所以方法去哪里了???在我们展开的图片上面可以看出其实就在__proto__属性的下面,包含了我们定义的方法。这个部分就是公共空间的,跟Father.prototype是一样的,我们可以验证:如下
console.log(Father.prototype === fa.__proto__) //true
  1. 所以说明了什么?说明了实例的创建是很省事的,仅仅创建了属性(在自己的空间),方法就用构造函数里面的共有的(公共空间的)。拟人的语气说就是:属性才是自己的,方法是公共的。如何找到公共方法,就靠__proto__(这个也是自己的).

3,总结构造函数和实例

  1. 构造函数里面虽然写了属性,但是 构造函数的内存空间里面,根本没有属性,,只有方法。
  2. 实例 只有自己的属性和自己的__proto__属性,没有方法。
  3. 构造函数搞一块内存,把所有的方法都扔进去,让所有的实例使用。
  4. 实例对象,有自己的一亩三分地,只有自己的属性。需要用到方法就到构造函数那个公共区域里面找。至于怎么找,就靠自己的__proto__属性。
  5. 可以看到所有的对象(包括构造函数和实例)都有__proto__属性,指向的都是创造自己的构造函数的公共区域(里面存的是公共方法) 【这句有点绕】

4, 原型链继承

开头先问一个比较实际的问题:你继承的话,是不是继承你父亲的所有东西?先不回答,朝下看。
定义一个儿子类:

function Son(){this.name = 'erzi'}Son.prototype.cost = function(){console.log('儿子花钱')}var s = new Son()

可以看到:

  • 可以看到儿子就一个属性name,和一个方法cost()

**现在考虑:**父亲Father()有属性,有方法,如果儿子Son要继承的话,是不是要一起继承过来?
如果要一起继承,自然想象的话 就会出现一个问题,(按照其他语言的类class继承)一般来想Son如果想要继承父亲Father的话,直接在两个构造函数上面操作(extends关键字),就可以了。。。
但是!!!!
上面我们看到的是不是Father的构造函数并没有属性,也就是说如果你继承的是Father()这个构造函数的话,只继承了方法,没有属性,因为Father()的公共内存里面找不到属性,只有方法。。。这咋办???

如果你还要继承的话,,就只能继承Father的实例对象了,因为实例对象里面有属性,也有一个__proto__属性指向公共方法。啥都有了,可以完全继承。。。但是又会带来一个问题,我上哪里去找Father的实例?

假如说:我可以先创建一个Father的实例,(如上面的fa实例),然后我在创建一个儿子的实例对象s
然后我把s对象的__proto__.__proto__指向fa对象,是不是就可以了?因为s.__proto__是指向的Son()构造函数里面的公共区域,这个公共区域里面还有自己的cost()方法。虽然继承别的的,但是也不能把自己的搞丢了,所以就是有2个__proto__了。

			s.__proto__.__proto__ = fa //把s实例指向 fa的整体, 这样不仅获得了fa的属性还有fa的方法console.log(s.baba) //BABAs.money() //爸爸有钱s.cost() //儿子自己的方法,也是可以执行的。

根据上面代码可以看到儿子实例s 可以访问父亲实例fa 里面的属性baba和方法money了,同时自己的方法cost()也是可以访问的。

我们再来看一下,经过上面的代码之后,fa还可以不可以访问自己的属性、方法,同时验证fa能不能方法儿子s的方法cost()

			console.log(fa.baba) //BABAfa.money() //爸爸有钱fa.cost() //Uncaught TypeError: fa.cost is not a function

fa对象仍然正常,而且不可以访问s。。说明我们上面的语句是正常的。继承也是对的。s实例对象确实继承了fa实例对象的方法和属性。

4.1 完善继承

经过上面一系列的操作,实现了继承。
首先需要创建两个实例,然后在儿子的.proto.__proto__指向父亲这个实例,这样才可以获得父亲的属性和方法(因为父亲的属性是放在自己的私有空间的,所以需要指向父亲这个整体)。从而实现继承!

这个继承链条是没有问题的,思路也米有问题,流程也米有问题。

但是存在问题!!!
首先需要两个实例(因为属性和方法存放的位置不一样导致的),想要完全继承父类的属性和方法。我先要创建一个父类的实例,才可以获得父类的属性,但是我实例化的时候,如果父类需要传递参数,假设父类有一个name参数(就是父亲的名字),我假设先初始化父亲叫 “张三”。
然后儿子初始化,然后继承。。但是这个儿子的父亲叫“李四”,,,请问我还可以继承吗?(假设不存在修改name的方法,写死的)
这就不可以继承了。。。因为你不能乱认爸爸啊。。。
(当然也可以在你儿子需要继承父类的时候,先实例化一个父类)

所以这样就存在问题,不够智能,需要继承,就创建一个父类实例,需要继承,就创建一个父类实例。很显然这样是可以的,但是这样不好,我能不能在创建子类实例的时候,自动的创建一个父类,然后实例化父类的属性,方法等。。然后继承父类的属性和方法。自动的,不要每次都手动。

所以问题就转化成:
要求:我自己创建了一个实例对象,这个实例对象可以自动的继承我想要继承的父类?

思考步骤:(这里还以上面的Father() 和Son()两个构造函数举例子)

  1. 首先考虑,是谁创建实例对象?
  2. 答案是 构造函数 创建。
  3. 所以问题就转向到构造函数身上。我想要创建Son的实例s,然后自动继承Father的一切属性和方法,同时还要确保这个Father是我自己的,不能乱认爸爸,所以咋办?
  4. 既然分析到构造函数上面了,肯定从这个构造函数突破了。
  5. 想要一个干净的Father实例,可以直接用new Father();
  6. 根据__proto__指向的东西可以有两种继承实现方式:
    6.1 Son.prototype.__proto__ = new Father();
    6.2 Son.prototype = new Father();
  7. 根据上面两个语句,然后结合输出,如下图:
4.2 总结

可以看到两种方式的实现过程,和优缺点:
注意看,实际就是把原本 原本的Father实例 fa的个人空间地址 赋值给Son.prototype。就是说把fa实例的个人空间的内容属性。放到Son的公共区域,两者结合。
实际应该是把fa实例的个人空间的属性复制到Son的prototype区域,在把fa的__proto__的值赋值给Son空间里面的__proto__,从而让这个指向改变,由原来的指向Object ,变为指向Father的公共区域prototype。

1,首先两种方式都可以实现继承,实现子类,套用父类里面属性和方法。
2, 第一种,其实是把整个Father的实例 放到了Son的公共方法空间里面了。(可以这么理解,虽然不对)
3, 第二种,其实是把整个Father实例的个人空间搬到Son的的公共部分里面了。(可以这么理解,虽然不对)不过也可以说是吧Son的公共区域prototype的东西搬到Father的实例的个人空间了。
总结:其实就是把儿子的公共区域prototype和父亲的个人空间,两个空间重叠了,变成一个空间了,空间内有儿子的公共方法和父亲的个人属性。两者混合
4, 第一种实际上是创建了两个实例。
5, 第二种是创建了一个实例。
6, 从节约内存的角度上面看,第二种要好一点。因为只创建了一个实例
7, 其实我也不知道该如何解释,让大家选择第二种。。。因为我这个时候也搞不懂。。
8,我看的视频教程,直接说的是用第二种。。。
9, 所以大家就用第二种吧。。。。

5, 继承带来的问题

当我们选择第二种方式:Son.prototype = new Father();
会出现一个问题,就是这个时候我们创建Son的实例,是谁创建的的问题,是Son()自身创建的,还是Father()创建的的问题?
因为是继承,所以某种程度上讲,两个构造函数都执行了,但是问题来了,都执行了,那么Son的实例到底是谁创建的???
var s = new Son();
从这个可以看出s应该是Son创建的, 但是:看输出:
console.log(s.constructor); 看一下这个s是谁创建的,输出:
ƒ Father(){ this.baba = 'BABA' this.xx = 'xx' }
很显然,结果竟然是父类。。。。

却是 Father构造的,constructor是构造函数,输出的是构造函数的原始语句。。
这就带来一个问题,s是Son() new出来的,但是构造函数确实Father的。。。。这有点不妥
需要让他改回来。变成自己搞的。

就让他自己的constructor 指向自身就可以了,问题是怎么指向?
首先明确constructor这个函数在哪里?
从最上面的那个图可以看到constructor()和money()函数在一起,也就是在Father.prototype里面,所以可以看出构造函数的constructor() 的调用路径是 Father.prototype.constructor()

既然找到了路径,现在就要让Son的constructor重新指向自己,从而达到让自己生自己的目的。
就是如下语句:
Son.prototype.constructor = Son
在来输出看一下。

console.log(s.constructor)

或者:

console.log(s)


可以看到上面已经改正, 正常了。

6, 更进一步:

经过前面几步,已经可以实现如何继承了,子类Son我们在初始化的时候,也实现了调用自己的constructor()函数,但是有一个问题,我们实例化Son的时候,有没有实例化父类Father里面的属性????
这里重新把上面的代码写一下如下:

function Father(money){this.money = money;}Father.prototype.baba = function(){console.log('叫爸爸就给你钱 '+ this.money)}Son.prototype = new Father();Son.prototype.constructor = Son;function Son(money, name){Father.call(this, money); //语句1this.name = name;}Son.prototype.cost = function(){console.log('花钱的功能!'+this.money)}var s = new Son(100, 'xlbai');console.log(s)s.cost()s.baba()

输出:

可以看到仅仅是儿子初始化了属性,父亲并没有初始化,即使你写了: Father.call(this, money); 这句话也没用,父类的money属性还是undefined的(假如你没有用函数去操控这个属性,如setMoney())
但是输出的话,确实你调用父类的函数,竟然也输出了??怎么回事???
其实是因为this的原因,this是谁调用,this是谁的原因,我们是s.baba() 是s调用的,子类调用的,这个输出的money是子类的。
这里我有点迷糊,烂尾了这个地方,不用看了。
实际上在子类的构造函数中调用父类的call()方法,里面传入两者相同的参数,就可以初始化了。

7,总结:

1, 构造函数的prototype 是公共的。
2,实例有自己的空间,里面放的是自己的属性,方法还是到构造函数中的公共区。
3,Son.prototype = new Father();
4, Son.prototype.constructor = Son;
5, 子类构造函数中的call方法,还需要在探索,不是很懂,第6段可以不用看

更多推荐

JS中 原型链继承 图示 个人理解

本文发布于:2024-02-25 15:36:54,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1699460.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:图示   原型   JS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!