什么是面向对象的程序设计?
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种==计算机编程架构==。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:==重用性、灵活性和扩展性==。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。
常用名词概念:对象、类、封装、聚合、重用与继承、多态
- 对象(Object):所谓对象,实质就是指自然界事物(包括人和物)在程序设计语言中的表现形式。例如,对于猫这种常见对象来说,我们可以看到它们具有某些明确的特征(如颜色、名字、体型等),能执行某些动作(如喵喵叫、睡觉、跑、跳、抓老鼠等)。在OOP语义中,这些对象特征叫属性,而哪些动作则被称为方法。此外,我们还有一个口语方面的类比:
1. 对象往往是用名词来表示的(如cat、book)
2. 方法一般都是些动词(如call、see、run)
3. 属性值则往往是一些形容词(如color:black, gender:male)
- 类(class):在现实生活中,相似对象之间往往都有一些共同的组成特征。例如蜂鸟和老鹰都具有卵生、恒温、体均被羽、前肢成翼、飞翔生活等鸟类特征,因此它们可以被统称为鸟类。==在OOP中,类实际上就是对象的设计蓝图或者制作配方。==‘对象’这个词,有时我们也叫做‘实例’。所以我们可以说老鹰是鸟类的一个实例。
javascript是一门基于原型(prototype)而非基于类(class)的语言。所以JavaScript中根本没有类。在基于类的语言中,我们一般这样描述自己的做法:‘我基于Person类创建了一个Bob的新对象。’ ==在基于原型的OOP语言中,我们应该这样描述:‘我将现有的Person对象扩展成了一个叫做Bob的新对象。’==
- 封装: 其主要用于阐述对象中所包含的内容。封装概念通常由两部分组成。
1. 相关的数据(用于存储属性)
2. 基于这些数据所能做的事情(所能调用的方法)
第一层意思:将数据和操作捆绑在一起,创造出一个新的类型的过程。第二层意思:将接口与实现分离的过程。
- 聚合: 所谓聚合,有时候也叫组合,实际上是指我们将几个现有对象合并成一个新对象的过程。总之,这个概念所强调的就是这种将多个对象合而为一的能力。例如个人电脑,由主机、鼠标、键盘、显示器等组合起来的一个新对象。
- 重用与继承: 类之间的关系,在这种关系中,一个类共享了一个或多个其他类定义的结构和行为。继承描述了类之间的“是一种”关系。子类可以对基类的行为进行扩展、覆盖、重定义。==但由于JavaScript中不存在类,因此它继承只能发生在对象之间。==
- 多态:类型理论中的一个概念,一个名称可以表示很多不同类的对象,这些类和一个共同超类有关。因此,这个名称表示的任何对象可以以不同的方式响应一些共同的操作集合。
一个完美的回顾
特征描述 响应概念 Bob是一个男人(后者是一个对象) 对象 Bob出生于1980年6月1日,男性, 黑头发 属性 Bob能吃饭、睡觉、喝水、做梦,以及记录自己的年龄 方法 Bob是Programmer类的一个实例 传统OOP的类 Bob是一个由Programmer对象扩展而来的新对象 基于原型OOP中的原型对象 Bob对象中包含了数据(例如出生日期)和基于这些数据的方法(例如记录年龄) 封装 我们并不知道其记录年龄的方法是如何实现的,对象通常都可以拥有一些私有数据,例如对闰年二月的天数,我们就不知道,而且也不想知道 隐藏信息 Bob只是整个web开发团队对象的一部分,此外开发团队对象还包含了一个Designer对象Jill,以及一个ProjectManager对象jack 聚合、组合 Designer、ProjectManager、Programmer都是分别扩展自Person对象的新对象 继承 我们可以随时调用Bob\Jill\Jack这三个对象各自的talk方法,它们都可以正常工作,尽管这些方法会产生不同的结果(如Bob可能谈得更多的是代码性能,Jill更倾向于代码的优雅性,而Jack强调的是最后期限)。总之,每个对象都可以从新定义他们继承的方法talk 多态、方法覆写
JavaScript中模拟实现面向对象编程
JavaScript模拟实现类
==javascript是一门基于原型的语言==。
javascript中的所有类型(Object、Function、Date等)都拥有原型。ECMAScript标准将该属性指定为隐藏属性,可以使用【ProtoType】来引用。
目前有两种方法可以访问该属性:非标准的proto属性和protoType属性。
在ECMAScript6中proto成为一个类型的正式属性。
javascript中核心类型(Date,String,Array)都有一个公用属性protoType。其他通过函数构造器(function constructor)创建的的类型也包含这个属性。protoType属性不能用于实例,所以这些类型的实例并没有protoType属性。
==JavaScript模拟创建类(calss):根据版本==ES3 通过构造函数模拟创建类示例:
function Person(firstName, lastName, birthDate, gender) { this.firstName = firstName || 'John'; this.lastName = lastName || 'Connolly'; this.birthDate = new Date(birthDate || '1964-09-05'); this.gender = gender || 'male'; } Person.prototype.getAge = function() { var today = new Date(); var diff = today.getTime() - this.birthDate.getTime(); var year = 1000 * 60 * 60 * 24 * 365.25; return Math.floor(diff / year); } Person.prototype.toString = function() { return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender; }
ES5 通过字面量模拟创建类示例:
var Person = { firstName: 'John', lastName: 'Connolly', birthDate: new Date('1964-09-05'), gender: 'male', getAge: function() { var today = new Date(); var diff = today.getTime() - this.birthDate.getTime(); var year = 1000 * 60 * 60 * 24 * 365.25; return Math.floor(diff / year); }, toString: function() { return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender; } };
这没什么特别的地方:一个人有姓、名、性别、出生日期以及计算年龄的方法。==Person是一个对象字面量,不是什么类似于类的东西。为了在无类的JavaScript与拥有类的面向对象语言之间做出区分,我们只是将Person视为类型。==
- ES6 通过语法糖模拟创建类示例:
class Person { constructor(firstName, lastName, birthDate, gender) { this.firstName = firstName || 'John'; this.lastName = lastName || 'Connolly'; this.birthDate = new Date(birthDate || '1964-09-05'); this.gender = gender || 'male'; } getAge () { var today = new Date(); var diff = today.getTime() - this.birthDate.getTime(); var year = 1000 * 60 * 60 * 24 * 365.25; return Math.floor(diff / year); } toString () { return this.firstName + ' ' + this.lastName + ' ' + ' is a ' + this.getAge() + ' year-old ' + this.gender; } }
==利用以上的模拟类,创建JavaScript模拟实例==
- * ES5字面量模拟实现OOP实例*
var bob = Object.create(Person);
bob.firstName = 'Bob';
bob.lastName = 'Sabatelli';
bob.birthDate = new Date('1969-06-07');
console.log(bob.toString());
我们将从对象Person对象中创建的实例保存在变量bob。这里没有涉及类。不过在创建的Person对象与Person类型之间存在某种联系。这种联系就出自【prototype】属性。打开浏览器控制台,可以查看bob的proto属性。你会发现它指向的是Person对象。可以通过bob.proto === Person来验证。
Object.create()是在ECMAScript5中加入JavaScript中的。这在表面上是为了简化、澄清对象之间的关系,尤其是对象与其原型的关系。它实际提供了一种建立对象间联系的简单方法。这种联系很像OOP中类与实例理念。但是JavaScript中没有类,因此我们拥有的只是一些彼此间包含关联的对象。==这种联系通常称为原型链。==
利用polyfill实现跨浏览器Object.create
if (typeof Object.create !== 'function') {
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
}
}
- ES3构造函数和ES6语法糖模拟实现OOP实例
var bob = new Person('Bob', 'Sabatelli', '1969-06-07');
console.log(bob.toString());
关键理解new操作符,根据MDN相关描述当代码 new Person()时,JavaScript引擎做了如下操作:
1. 一个新对象被创建。它继承自Person.prototype.
2. 构造函数 Person 被执行。执行的时候,相应的传参会被传入,同时上下文(this)会被指定为这个新实例。new Person 等同于 new Person(), 只能用在不传递任何参数的情况。
3. 如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。(一般情况下构造函数不返回任何值,不过用户如果想覆盖这个返回值,可以自己选择返回一个普通对象来覆盖。当然,返回数组也会覆盖,因为数组也是对象。)
伪代码示例:
Function.prototype.method = function(name, func) {
// 有条件的增加一个方法
if (!this.prototype[name]) {
this.prototype[name] = func;
}
}
Function.method('new', function() {
// 创建一个新对象,它继承自构造函数的原型对象。
function F() {}
F.prototype = this.prototype;
var that = new F();
// 调用构造函数,绑定-this-到新对象上。
var other = this.apply(that, arguments);
// 如果它返回的值不是一个对象,就返回该新对象。
return (typeof other === 'object' && other) || that;
})