javascript语言的设计有诸多缺陷,当初只是为了完成一些简单的任务。得益于web技术的飞速发展,javascript承担的工作也越来越多。由于javascript在浏览器端运行,有着诸多历史遗留的兼容性问题,也无法爽快的对javascript进行更新换代。
当使用javascript时,现有的语言特性无法满足需求时,第一种解决方式是,使用编译原理等技术,将支持新特性的ES版本编写的代码编译成低版本的ES代码;第二种解决方法是,看看能不能用已经有的语言特性较为直观的去模拟和实现该特性。
幸运的是,js支持函数式编程,函数是一等公民,这就给了它很大的模拟空间。本博文就来介绍如何使用js模拟面向对象编程。
1. js中的对象
javascript本身就有对象这一概念。如以下代码:
这样,point
就是javascript对象,使用 point.x
就能取得对象的属性。
但是同时,你也可以使用 point['x']
去取得point的x属性。从语法上来看,第一种更像是对象的语法,第二种更像是数组的写法。
而实际上, point
其实是编程中经常用到的数据结构,hash表。只不过,javascript提供了类似对象取属性写法的语法糖来操作hash表。
从这个角度去看,js中的对象虽然是基于hash表的,但是语言的设计没有屏蔽这一点而是直接暴露了出来。对象的属性就是hash表的数据。那对象的方法呢?
|
|
由于函数是一等公民,所以对象的方法其实是个值为函数的属性。
在概念上,对象可以看成是一组属性和一组函数集合。由于函数是一等公民可以像其它值一样放入数据结构里,因此javascript中的对象就是一组属性,用hash表中存放的属性。
又由于js是动态弱类型语言,因此不需要考虑类型系统的事情。
2. 创建对象
在支持OOP语言中,首先需要声明类,然后使用诸如new之类的操作实例化出一个对象。
如:
将初始的数据传入构造函数,new操作符即可创建一个对象。
那,在javascript中,如果我使用一个函数,接受初始数据,创建一定结构的hash表(即javascript对象)返回呢?
|
|
在这种写法中,创建类的实例都不需要new操作符,由于Point是一个类似构造函数的函数,因此直接函数调用即可。
这种写法,加上用self
这个名字在构造函数中命名对象,有种写python语法的错觉。。。
3. 私有属性
那对象的私有属性如何隐藏呢?粗暴的方法是类似python那样,大家约定加前下划线的属性_x
这种是私有属性,大家装作没看见就可以了。
在上面的写法中,还有更优雅的写法:
|
|
如上例所示,由于对象在构造函数中创建,那么对象的方法中可以引用构造函数中的局部变量,但是对象的使用者却无法拿到。
这样,利用闭包的特点,实现了数据的隐藏,实现了私有属性。
4. 继承
面向对象的三大基本特性:封装,继承,多态。封装上面介绍的已经实现了,而动态语言的鸭子类型天然多态,因此剩下继承如何实现。
子类的模拟方式依然用一个类似构造函数的函数:
首先,调用父类的构造函数得到父类对象。由于对象是hash表,之后可以动态的修改它的属性,以达到添加新属性或重写旧方法的目的。
那问题是,如果在重写某一个方法时候,需要调用父类方法呢?这可能有点麻烦:
可以看到,使用一个局部变量保存了旧有方法,也就是父类的方法。
这样,在重写该方法时就能调用父类方法了。
5. 最后
可以发现,js的两个特点,第一,对函数式的支持,第二,javascript对象实际上是hash表这种数据结构。利用这两个特性,就能简单而优雅的模拟面向对象基本特性。
在实际的项目中,如果需要将一些代码封装起来或者需要自己实现某一抽象概念时,就能使用这种方式优雅的将其抽象成类,将代码模块化,提高可复用性和可读性。