面向对象(OOP)的概念
对于面向对象,有以下几个概念:
- 一切事物皆对象
- 对象具有封装和继承特性
- 对象与对象之间使用消息通信,各自存在信息隐藏
对象有哪些特性?
- 封装性:通过封装可以为对象内部的数据提供不同级别的保护,避免了外界的干扰和不确定性。
- 继承:很好的支持了代码的重用性,通过一个类派生一个新的类,这个新类不仅可以具有原来的类的特性,还可以拥有新的特性。
- 多态:多态指的不同类的对象对同一消息作出不同的响应。
面向对象的优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活、易于维护。
JavaScript作为一门面向对象的语言,却并不是通过类来实现面向对象的,而是通过原型(prototype)。那么这两者实现面向对象究竟有什么区别呢?
基于类vs基于原型
基于类的面向对象语言,比如Java和C++,是构建在两个不同实体的概念之上的:即类和实例。
- 类(class):定义了所有具有某一组特征对象的属性(可以将Java中的方法和变量以及C++中的成员都视作属性)。类是抽象的事物,而不是其所描述的全部对象中的任何特定的个体。
- 实例(instance):类的实例化体系;或者说,是类的一个成员。实例具有和其父类完全一致的属性。
1 | //定义类 |
其中,创建类的实例必须调用类的构造方法。类的构造方法是一类和类同名的方法,用于创建类的实例并初始化对象。
而对于JavaScript,我们不需要定义一个类,而只需要定义一个构造函数,然后通过这个构造函数就可以创建实例。
1 | function Person(name,age) { |
new操作符
在Java中,new操作符做的是在内存中开辟一个空间,然后生成的实例会通过指针指向这个空间。
在JS中,new操作符不仅仅在内存开辟了一个空间,还进行了另外的两个步骤
- 将实例的
._proto_
指向构造函数的prototype
属性。 - 调用构造函数来初始化实例对象。(
call
、apply
)
原型对象
对象不仅仅有自己独特的属性,例如Person
,每个Person
的实例对象不仅有属于自己的姓名与年龄,还有许多共用的方法。如果在每个对象的实例中都存一份方法就会造成空间的浪费。
在Java中,共用的方法会采用静态的方式用static
修饰将这些方法定义成静态成员,这些静态成员会存放在另外的内存空间中,直到该类被卸载的时候才会被回收。
而在JS中,则引入了原型对象。原型对象就是构造函数的prototype
属性,这个属性存放着实例对象需要共享的属性和方法,而那些不需要共享的属性和方法就放在构造函数里面。而在这个原型对象中则存在一个constructor
属性来指向该原型实例的构造函数。
原型链
由于JS中,实例是对象,函数是对象,原型也是对象,他们都包含各自的_proto_
属性,constructor
属性和prototype
属性,它们相互指来指去,就形成了一条原型链。
以Person
这个构造函数为例,我们创建了它的实例xm
,于是以下的原型链中我们就包含了三种不同的对象,一个是实例,一个是构造函数,还有构造函数的原型对象。其中_proto_
其实是浏览器为我们提供的非标准的查看对象原型的访问器,并不是真实存在的属性,所以用虚线表示。
属性查找
当查找一个对象的属性时,JS会向上遍历原型链,直到找到给定名称的属性为止,如果查找到原型链的终点(null)时,仍然没有找到,则会返回undefined
。
创建对象
- 基于Object对象创建
1 | var person = new Object(); |
- 字面量的方式:清晰的查找对象包含的属性和方法
1 | var person = { |
- 工厂模式:用于批量创建多构造类似的对象,缺点是无法识别对象的类型(都是Object)。
1 | functioni createPerson(name,age) { |
- 构造函数模式
1 | function Person(name,age) { |
Object.create(prototype, descriptors)
:是E5中提出的一种新的对象创建方式。它会新建一个对象,并且让这个对象的原型指向参数prototype
。
参数说明:prototype
:必需。要用作原型的对象,可以为null
。descriptors
:可选。包含一个或多个属性描述符的JavaScript对象。
1 | var var obj = Object.create({x:1}); |