继承的串联及扩展
对象的三个特性:封装、继承、多态
- 封装:把客观事物抽象成类,并且类可以把自己的数据库和方法可让可信的类或者对象操作,对不可信的进行信息隐藏。这也是局部变量赋值 symbol 的应用场景
- 继承:继承指这样一种能力:它可以使用现有类的所有功能,并无需重新编写原来的类的情况下对这些功能进行扩展
- 多态:多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值它的子对象的特性以不同的方式运行。**允许将子类类型的指针赋值给父类类型的指针。
继承
很多面向对象语言都支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。接口继承在 ECMScript 中是不可能的,因为函数没有签名。实现继承是 ECMScript 唯一支持的集成方法,而这主要是通过原型链实现。
其基本思想就是通过原型继承多个引用类型的属性和方法
常用继承:组合继承,寄生组合继承
javascript 对象具有 “ 自有属性 ” 也有一些属性是从原型对象继承来的。
有两种继承方式:
- 寄生函数继承(构造函数继承 (call/apply)),利用 call 继承父类上的属性,用子类的原型等于父类实例去继承父类的方法。缺点:调用父类两次,造成性能浪费。
- 原型链继承 ( 挂载到 prototype 属性上面 )。
new 操作符
new 的源码实现
1 | function myNew(fun) { |
实例化的过程
1 | var obj = {} |
所以通过 Child.prototype = new Parent(),子类就可以得到父类共享方法
new 的过程如下:
- 创建一个空对象 obj
- 将创建的空对象的隐式原型指向其构造函数的显示原型
- 使用 call 改变 this 的指向
- 如果无返回值或者返回一个空对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话,那么直接返回该对象
所以,在 new 的过程中,我们使用 call 改变了 this 的指向
为什么不是 Child.prototype = Parent.prototype?
看到一道题是这些写的,想着想着掉坑里了,因为原型链是通过proto去寻找属性和方法了,若是通过Child.prototype = Parent.prototype
去绑定的话,那么之后所有继承都只能通过这种方式绑定,不能在通过 new,因为这里没有生成proto去构成原型链,这已经不是原型链了吧?顶多算是利用 prototype 的指针进行挂载
我们想要的效果,是将 Parent.prototype 这个对象挂在 Child.prototype.proto 的这个属性上;
关系判断
- instanceof 判断是否为另一个对象的实例
- isPrototypeOf() 判断对象是否存在另一个对象原型链上 Chtild.isPrototypeOf(Parent)
- Object.getPrototypeOf() ES6 中新增的方法,用于获取子类的父类 Object.getPrototypeOf(child)==Parent //true
寄生函数继承
1 | function Person(name, age, sex) { |
寄生函数继承:利用 call 继承父类上的属性,用一个干净的函数的原型去等于父类原型,再用子类的原型的等于干净函数的实例。
原型链继承
实例出来的是共用的
1 | function Parent() { |
构造函数继承
1 | function Parent(name) { |
- 优点:子类可以向父类的构造函数中传参,子类实例中的引用类型属性互不干扰
- 缺点:子类实例无法访问父类的原型(无法复用父类原型中的方法)
Object.create
1 | //polyfill |
constructor 的作用
实例的 constructor 指向创建实例的构造函数
经典原型链继承
1 | function Father() { |
上面的代码中Child.prototype.constructor = Child
的意义是什么呢?我们访问属性都是按照原型链查找的,为什么还需要这一句?看了有些解释是说要尽量让对象指向其构造函数,以维持这个惯例
还可以将实例的构造器的原型对象暴露出来,比如写了一个插件,别人得到的是实例化后的对象,若是别人想扩展对象就可以用 instance.constructor.prototype 去修改或扩展原型对象