js面向对象编程之继承
前言
javascript本身不是静态语言,是一门动态语言,变量只有在运行的时候才知道是什么类型,严格上也没有像Java静态语言的类的概念((当然在ES6中提出了类的语法),那么它是如何实现类的继承的呢?
什么是继承?
继承就是在原类的基础上,略作修改,得到一个新的类,并且不影响原有类的功能。
我们都知道js中对象的实现,是基于它定位原型链系统。类也是一种对象,只不过类比一般的对象更完备,具有自身的属性和方法。所以在js中,类的继承必然少不了原型对象的概念。
而且类可以new出实例对象,通过构造函数的方式可以实现类的实例,也可以这样说,构造函数就是js的类。
继承的方式
构造函数伪装继承属性
我们以实现一个拖拽的demo来说明类的继承。现有一个拖拽类,这个拖拽类可以在页面中移动,并且能移出屏幕。
1 | function Drag(id) |
现在我们实现一个限制在窗口内的拖拽类,可以使用call来实现构造函数伪装。1
2
3
4function LimitDrag(id)
{
Drag.call(this, id); //(1)
}
要清楚,函数只不过是在某一环境下执行的在代码,这个环境就是js中的上下文,call()可以改变函数执行的上下文,也就是可以改变函数中this的指向。这就相当于将Drag类构造函数的代码,放在limitDrag类中执行了一遍。这个方式叫做构造函数伪装,可以实现类属性的继承,想要覆盖它父类的属性,只需要在(1)下面写属于自己的属性即可。
原型对象实现方法继承
我们先用这种方式实现方法的继承
1 | LimitDrag.prototype=Drag.prototype; |
然后我们发现,LimitDrag实例可以使用Drag类的方法,但是要是我们先重构一个属于limitDrag类的fnMove(),发现Drag类的fnMove()也被覆盖了,这就影响了前面所说的,“并且不影响原有类的功能”。这个其实跟对象的引用类型有关,因为LimitDrag.prototype和Drag.prototype在内存中指向的是同一块堆内存。
解决这个问题有两种思路:
- 把原型对象上的方法拷贝一份
- 将子类的原型对象的proto属性指向父类原型对象
1 | //方法一 |
1 | LimitDrag.prototype.__proto__ = Drag.prototype |
其实方法二才算是js的继承,LimitDrag.prototype instranceof Drag为true,方法一只是使用拷贝实现了类的克隆,并没有通过原型链连接起来。
在js中,一切对象都是通过克隆Object.prototype来克隆的,然后通过动态的设置对象的proto属性的指向到另一个构造器的原型对象上,来实现继承的效果。
所以现在就可以改写LimitDrag类的fnMove()了,并且不会影响到Drag类了
1 | LimitDrag.prototype.fnMove=function (ev) |
结语
js是静态语言,在实现类的继承可能没有动态语言严谨,但是增加了它的灵活性。通过构造函数伪装和原型对象继承两种方式可以实现js中类的继承。
继承是编程一个重要的概念,可以适用很多场景,比如以上拖拽类的栗子中,假如项目已经大范围的使用了不限制的拖拽类,茂茂然的改原有的代码一定会非常的痛苦,所以利用继承的特性实现一个限制的拖拽类是一种很好的解决方案。