面向对象
面向对象的概念
OOP(object-oriented programming)面向对象程序设计是一种程序开发的方式。常常与面向过程的程序设计方式比较。
面向对象程序设计和传统的思想不同的是:传统的程序主张将程序看作一系列函数的集合,或者一系列对电脑下达的指令。面向对象的程序设计中每一个对象都应该能够接收数据、处理数据并且能够将数据传达给其他对象,因此它们都可以被看作是一个小型的“机器”,即对象。
面向对象的程序开发方式的描述性更强,是人们可以更简单的理解、设计并维护程序,特别适合在大型项目中应用。当然,也有一部分反对者在某些领域对此予以否认。
面向对象的设计
面向对象的编程语言通常使用继承
其他类
达到代码重用和扩展的性的特性。这里提到了继承
和类
的概念。继承后面再说,
对象指的是类
的实例,它将对象做为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
类(Class)定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。对象则是类的实例。
面向对象的三大基本特征
封装性
隐藏对象的属性和实现细节。类中定义的属性和方法,通过类实例化而来的对象不必关系类中定义的属性和方法的细节。封装的目的是安全和简化编程继承
在某种情况下,一个类会有“子类”。子类比原本的类(称为父类)要更加具体化。例如,“狗”这个类可能会有它的子类“牧羊犬”和“吉娃娃犬”。子类会继承父类的属性和行为,并且也可包含它们自己的。多态
多态(Polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。例如,狗和鸡都有“叫()”这一方法,但是调用狗的“叫()”,狗会吠叫;调用鸡的“叫()”,鸡则会啼叫。
JS的面向对象
关于JS是否是面向对象的语言这一点倒是有点争议,JS本身是基本符合面向对象的三大基本特征的。与Java面向不同之处在于JS是基于原型的面向对象,而Java是基于类的面向对象。
关于JS是否是基于对象的语言的争议,可以参考贺师俊的回答,基于对象是指有对象的结构但是没有继承和多态的语言(比如VB),所以JS并不是基于对象的语言。
基于类和基于原型的区别
基于类的面向对象的语言,比如Java和C++,是构建在两个不同实体的感念之上的,即:类和实例。
所以上面有关面向对象的介绍其实都是在说基于类的面向对象。JS的面向对象则与此不同。
在JS中,所有的对象都是实例,ES6之前使用构造函数,ES新增Class语法糖,注意这里的Class并不和Java中的类对等,JS的底层依旧是基于原型。严格来说,在JS中,没有“类”。
差异总结
基于类的(Java) | 基于原型的(JavaScript) |
---|---|
类和实例是不同的事物。 | 所有对象均为实例。 |
通过类定义来定义类;通过构造器方法来实例化类。 | 通过构造器函数来定义和创建一组对象。 |
通过 new 操作符创建单个对象。 | 相同 |
通过类定义来定义现存类的子类,从而构建对象的层级结构。 | 指定一个对象作为原型并且与构造函数一起构建对象的层级结构 |
遵循类链继承属性 | 遵循原型链继承属性 |
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。 | 构造器函数或原型指定初始的属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。 |
JS基于原型的继承
基于类的面向对象中,子类在继承的时候会复制父类的定义,子类和父类通过类链关联;基于原型的面向对象中,通过构造器创建对象,构造器都有自己的原型对象(prototype),一个构造器可以继承另一个构造器,通过可以复制原来的构造器的原型,并进行扩展来达到继承的能力。
JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也是一个对象,有自己的私有属性( proto )指向它的构造函数的原型对象(protorype) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
注意:__proto__
是JS中每个对象都有的属性,这里的对象包括numble、boolean、string等,它们分别指向对象构造函数的原型;prototype
是构造函数才有的属性。通过同一个构造函数创造的实例对象的___proto__
属性执行同一个prototype
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
1 | const Foo = new Function() |
用下图展示应该更清晰
注意Function
本身也是函数,所以Function.__proto__ === Function.prototype
为true
,即Function的构造函数就是Function本身。null
是原型链的终点。
举一个具体的例子:
1 | // ES5的写法 |
这里直接使用了People的原型,这样写某些普通的业务可能没什么问题,但其实很不严谨,因为了People构造器共用一个原型,Student不能轻易修改People原型本来的属性和方法,People原型修改也可能影响到Student。
修改之后:
1 | function People(name) { |
这就是JS实现继承的一个基本方式,Student的构造器想要继承People构造器,通过加入原型链
使用ES6的class则没有这么麻烦,不过ES6的class本身只是个语法糖,最终底层的实现和ES5的没有本质的区别
1 | class People { |