天天看点

JavaScript:继承详解

面向对象-继承

三个类

① 超类型

② 父类型

③ 子类型

④ 举例:

类: 动物 狗类 哈士奇

对于狗类来说:动物是它的父类型,而哈士奇类是它的子类型

对于哈士奇类来说:狗类是它的父类型,动物类是它的超类型.

继承:就是让子类拥有父类的资源

原型链继承

借用构造函数继承

组合继承 ????

原型式继承

寄生式继承

寄生式组合继承

拷贝属性继承

减少代码冗余

方便统一操作

弊端:耦合性比较强

继承-原型链继承-原型链实战

每个函数都能构建出一个对象, 这个对象内部有个属性指向着这个函数的原型对象

原型对象本质也是一个对象,也是由另外一个构造函数构造出来, 也指向那个构造函数的原型对象

以上,形成一个链式的结构,就称为是原型链

实战:画出数组的完整原型链

运行结果:

JavaScript:继承详解

图示:

JavaScript:继承详解

继承-原型链继承-原型链检索规则

对象.属性的方法去访问属性的时候,先查找有没有对应的实例属性,如果有那么就直接使用

如果没有,那么就去该对象的原型对象上面去找,如果有那么就直接使用

如果没有,那么就接着查找原型对象的原型对象,如果有,那么就直接使用,

如果没有,那么就继续上面的搜索过程

直到搜索到object.prototype为止,如果还是没有找到就返回undefined或者是报错

注意:原型链搜索的路径越长,查询属性所花费的时间就越多

原则:就近原型

继承-原型链继承-0

未继承效果:stu对象, 访问不到person里面的任何内容

JavaScript:继承详解

问题:子对象无法访问到父类的任何东西

继承-原型链继承-1

思路

① 查找规则:自己没有, 就到原型对象上面查找…

② 所以:

Ⅰ 要不考虑给添加到自己身上:只能自己有,以后的其他对象, 就没了

Ⅱ 要不考虑给添加到原型身上

解决方案:修改原型指向比较方便

图解

JavaScript:继承详解

问题:无法访问到父类的对象属性?

继承-原型链继承-2

思路:怎样才能拥有父类的实例属性和原型属性?

构造父类的实例

解决方案:构造父类的实例,并设置为子类的原型对象

图解:

JavaScript:继承详解

问题:类型问题

继承-原型链继承-3

解决方案:修复constructor指针即可

JavaScript:继承详解

问题:继承过来的实例属性, 如果是引用类型, 会被多个子类的实例共享

注意:到此为止,原型链继承已经结束????

继承-原型链继承-4

思路:只要在原型对象上,肯定都会被共享

解决方案

① 添加到对象自己身上

在创建过对象后添加:复用性差,冗余度高, 稳定性差, 不采用

在构造函数内部添加

② 优化

可以直接调用父类构造函数, 但是需要修改this指向

③ 再优化

注意覆盖关系,如果产生重名, 应该, 子类覆盖父类。即把继承的放在上面

JavaScript:继承详解

概念 ????

“借用构造函数继承”:在子构造函数内部, 调用父构造函数

问题:父类构造函数的参数无法修改

注意 ????

从此处开始,已经涵盖了原型链继承和借助构造函数继承

称之为**“组合继承”= 原型链 + 借助构造函数**

继承-原型链继承-5

父类构造函数, 需要设置接收可变参数

子类构造函数在调用父类构造函数的时候, 传递参数即可

问题:父类属性重复

实例上有一份,原型对象上有一份

继承-原型链继承-6

① 理解为什么会有两份?因为调用了两次父类构造函数。1次, 在子类构造函数内部,2次, 创建子类构造函数原型对象时

JavaScript:继承详解

② 分析:两次调用的意义?

1次, 在子类构造函数内部:为了获取父类的实例属性

2次, 创建子类构造函数原型对象时:为了获取父类的实例属性,为了获取实例的原型对象属性

③ 结论::想办法, 设置子类构造函数的原型对象时, 只要父类构造函数的原型对象属性

① 方案一:修改子类构造函数的原型对象指针, 为父类构造函数的原型对象

问题:共享原型对象、容易引发冲突、无法判定类型

不采用

② 方案二

能不能修改子类构造函数的原型对象指针, 为一个对象

这个对象, 只能访问到父类原型对象的属性/方法?

① 拷贝父类构造函数的原型对象

注意: 此处是指拷贝原型对象的内容, 不是整个地址过来

后面会讲解如何拷贝, 到时自行实现这块

② 借助父类构造函数的原型对象, 创建出来一个空对象实例

JavaScript:继承详解

此处用到了寄生式继承,所以最终, 到此为止, 变成了:寄生组合式继承!!

这是业界程序猿公认的引用类型最理想的范式

① 原型式继承

借助原型,然后基于已有的对象, 创建出新对象;同时不需要创建自定义类型

核心

系统实现:object.create

① 作用:创建对象,并且设置该对象的原型对象为传递过来的参数

兼容es5,ie8不支持

② 简单用法

创建了一个空对象obj2

把obj, 作为obj2的原型对象

③ 兼容处理

② 寄生式继承

在原型式基础上增强这个对象,所谓增加, 就是指, 再次给这个对象增加一些属性或者方法

面向对象-继承-原型链继承-总结

使用借助构造函数继承的方式

在子构造函数中, 调用父构造函数

注意修改this指针

注意调用顺序

寄生式继承方式

JavaScript:继承详解

面向对象-继承-原型链继承-练习-30m

继承两步走:

1. 借助构造函数继承

2. 原型链继承 和 寄生式继承

3. 注意!!!person的原型对象一定要放在继承的后面,不然会被继承的内容覆盖。

实例属性

name

age

原型方法

eat

run

父构造函数全部

job

jump

classname

study

面向对象-继承-应用练习(扩展内置对象)

直接给对象动态添加属性和方法

弊端: 如果操作很多个对象, 则无法共享;代码比较冗余

直接给array原型对象添加方法

弊端: 可能会产生覆盖的情况

提供一个新的构造函数,修改构造函数的原型指向为数组的原型对象,为了能够获取数组里面的属性和方法

问题: 依然会修改数组原型对象内容

优化: 原型对象就是一个对象,可以直接根据array创建一个对象, 给新创建的函数原型对象进行赋值

补充

函数的prototype属性

每个函数都有一个prototype属性, 它默认指向一个object空对象(即称为: 原型对象)

原型对象中有一个属性constructor, 它指向函数对象

给原型对象添加属性(一般都是方法)

函数的所有实例对象自动拥有原型中的属性(方法)

每个函数function都有一个prototype,称为显式原型(属性)

每个实例对象都有一个__proto__,称为隐式原型(属性)

对象的隐式原型的值为其对应构造函数的显式原型的值

我们能直接操作显式原型, 但不能直接操作隐式原型(es6之前)

内部语句: this.proto = fn.prototype

两个方法是function的原型对象上面的方法

两个函数的作用是一样的,都可以用来借用方法实现

参数:

第一个参数是调用该方法的对象(函数内部的this绑定的对象)

第二个参数:call:参数列表;apply:数组

实例

对象1.方法.call(调用方法的真正的对象,参数1,参数2,参数3);

对象1.方法.apply(调用方法的真正的对象,[参数1,参数2,参数3…])

JavaScript:继承详解
JavaScript:继承详解

this – >总是指向一个对象

函数的调用方式

① 作为对象的方法来调用 this—>当前的对象

② 作为普通的函数调用 this—>window

③ 作为构造函数和new使用 this—>构造函数内部新创建的对象

④ 被call或者是apply调用(函数上下文调用) this—>第一个参数