面向对象的JavaScript

原文地址:https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript


JavaScript拥有很强的面向对象编程能力,即便是因为其与其它语言在面向对象上的不同而产生了一些争论也仍是如此。

这篇文章从对面向对象编程的简介开始,然后回顾了JavaScript的对象模型,最后演示了JavaScript基于面向对象编程的相关概念。

JavaScript回顾

如果你对JavaScript的概念——比如变量、类型、函数以及作用域——不是很确定,你可以在《JavaScript回顾》中阅读到相关的主题。你也可以参阅《JavaScript 1.5核心指导》

面向对象编程

面向对象编程是通过对现实世界中的事物进行抽象从来建立模型的一种编程思想。它借鉴了已有的编程思想中的一些技术,包括模块化、多态以及封装。今天,许多流行的编程语言(诸如Java、JavaScript、C#、C++、Python、PHP、Ruby和Objective-C)都支持面向对象编程(OOP)。

与传统的看法中将程序看作是一系列功能的集合,或者更简单地,作为计算机的一系列指令不同,面向对象编程可以被看做是使用一系列相互合作的对象所形成的集合的一种软件设计方式。在OOP中,每个对象都能够收到消息、处理数据并发送消息给其它对象。每个对象都能被看做是一个拥有特定角色或者职责的小型独立机器。

面向对象编程的设计者希望通过它来使编程变得更加灵活,更加易于维护,并且面向对象编程在大型的软件工程开发中广受欢迎。凭借面向对象编程对模块化的重点强调,编程人员通过给予其本身对复杂情况和流程更直接的分析、编码以及理解,试图使它比其它不太支持模块化的编程方法更加简单地进行开发,代码之后更容易被人理解。

术语

类:定义了对象的特征。

对象:类的实例。

属性:对象的一个特征,比如颜色。

方法:对象的一种能力,比如走路。

构造体:当类被实例化的时候调用的方法。

继承:一个类可以通过继承而得到另一个类的特征。

封装:一个类只定义了某个特定对象的特征,一个方法只定义了某个特定方法该如何执行。

抽象:一个类中复杂继承、方法和属性的结合必须能够模拟一个现实的模型。

多态:不同的类可能会定义相同的方法或者属性。

参阅维基百科的《面向对象编程》以获得对面向对象编程更加拓展的描述。

基于原型的编程

基于原型的编程是面向对象编程的一种类型,在这种类型中,类并不显式地表现重用(在基于类的语言中被称为继承),而是通过改进现有的对象来完成重用的过程,这些现有的对象被称为“原型”。这种模型也被称为是“低类(class-less)”、“面向原型(prototype-oriented)”或者“基于实例(instance-based)”编程。

基于原型的语言最初(并且最典范)的例子是由David Ungar和Randall Smith开发的编程语言Self。然而,“低类”这种风格最近逐渐流行起来,并且已经被众多编程语言所采纳,包括JavaScript、Cecil、NewtonScript、lo、MOO、REBOL、Kevo、Squeak(当使用Viewer架构来操纵Morphic组件的时候)等等。

JavaScript面向对象编程

核心对象

JavaScript的核心包含了几种对象,例如Math、Object、Array、String这种的对象。下面的例子展示了如何使用Math对象的random()方法来获得一个随机数:

alert(Math.random());

备注:以上以及之后的示例中都假定alert函数(正如浏览器中已经包含的那个一样)是被全局定义的。这个alert函数实际上并不是JavaScript本身的一部分。

参阅《JavaScript 1.5核心参考:全局对象》以获得JavaScript的核心对象列表。

在JavaScript中,每个对象都是Object类的一个实例,并因此继承Object的所有属性和方法。

自定义对象

JavaScript是一种基于原型的语言,它没有C++或者Java中的那种类的声明。这对那些习惯于有类声明的语言的程序员来说有时是很迷惑的。不同之处在于,JavaScript使用函数来代替类。定义一个类和定义一个函数一样简单。下面的例子中我们定义了一个叫做Person的新类:

function Person() { }
对象(类实例)

我们用new obj这样的语句来创建类obj的一个新的实例,之后把创建的结果(它的类型是obj)赋值给一个变量来引用它。

下面的例子中我们定义了一个类Person,并且创建了它的两个实例(person1和person2):

function Person() { }
var person1 = new Person();
var person2 = new Person();

还可以使用Object.create作为另外一种新的、可替代的实例化方法。

构造体

构造体在初始化(对象实例被创建)的时刻被调用。构造体是类的一个方法。在JavaScript中,函数本身作为对象的构造体,因此,没有必要显式地定义一个构造体方法。每个在类中声明的动作都会在实例化的时刻被执行。

构造体通常用来设置对象的属性或者调用其它方法来使对象准备就绪以便使用。通过另一种不同的语法来添加类的方法以及方法的定义将会在后文中描述。

在下面的例子中,Person类的构造体会在实例化的时候弹出一个警告框:

function Person() {
  alert('Person instantiated');
}

var person1 = new Person();
var person2 = new Person();

属性(类属性)

属性是类中包含的变量,类的每个实例中都拥有那些属性。设置属性应该在类(即函数)的原型中进行,以便使类能够被正确地继承。

在类的内部对属性进行操作需要使用关键字this,它引用了当前的对象。在类的外部操作(读写)属性需要使用语法:InstanceName.Property,这与C++、Java以及其它很多语言所用的语法是相同的。(在类的内部可以使用语法this.Property来设置或者获取属性的值。)

下面的例子中我们在Person类的初始化中定义了属性gender:

function Person(gender) {
  this.gender = gender;
  alert('Person instantiated');
}

Person.prototype.gender = 'Person Gender';
var person1 = new Person('Male');
var person2 = new Person('Female');

//display the person1 gender
alert('person1 is a ' + person1.gender); // person1 is a Male
方法

方法和属性遵循了相同的逻辑,不同之处在于它们是功能并且按照函数的形式定义。调用一个方法与存取一个属性类似,但是你需要再方法名的后面添加“()”,可能还需要参数。

下面的例子中我们在Person类中定义并使用了sayHello()方法:

function Person(gender) {
  this.gender = gender;
  alert('Person instantiated');
}

Person.prototype.gender = 'Person Gender';
Person.prototype.sayHello = function()
{
  alert ('hello');
};

var person1 = new Person('Male');
var person2 = new Person('Female');

// call the Person sayHello method.
person1.sayHello(); // hello

在JavaScript中,方法是和属性一样绑定到类或对象的普通函数实体,这意味着它能够在“上下文之外”被调用。试着考虑下面这段示例代码:

function Person(gender) {
  this.gender = gender;
}

Person.prototype.sayGender = function()
{
  alert(this.gender);
};

var person1 = new Person('Male');
var genderTeller = person1.sayGender;

person1.sayGender(); // alerts 'Male'
genderTeller(); // alerts undefined
alert(genderTeller === person1.sayGender); // alerts true
alert(genderTeller === Person.prototype.sayGender); // alerts true

这个例子一次性演示了许多概念。在JavaScript中并没有“每个对象中都有一个的方法”,这是因为某个特定方法的所有引用都指向一个确定的、相同的函数,而这个函数位于我们在类的原型中首次定义它的位置。当一个函数被作为对象的一个方法(确定地说是属性)调用的时候,JavaScript将当前的“对象的上下文环境”“绑定”给了特殊的“this”变量。这跟调用函数“call”是等效的,如下所示:

genderTeller.call(person1); //alerts 'Male' 

获取更多信息请参见Function.callFunction.apply

继承

继承是创建可以作为某一个或多个已有类的专门化版本的新类的方法(JavaScript中只支持单继承)。专门化的类通常叫做子类,其它已有的类通常叫做父类。在JavaScript中,通过把某个父类的实例赋值给子类来完成继承,并且达成专门化。

备注:JavaScript不会检测子类的prototype.constructor属性(参见《JavaScript1.5核心参考:全局对象:对象:原型》),因此我们必须手动声明它。

下面的例子中,我们定义了Person类的子类Student。然后我们重写了sayHello()方法,并添加了sayGoodBye()方法:

// define the Person Class
function Person() {}

Person.prototype.walk = function(){
  alert ('I am walking!');
};
Person.prototype.sayHello = function(){
  alert ('hello');
};

// define the Student class
function Student() {
  // Call the parent constructor
  Person.call(this);
}

// inherit Person
Student.prototype = new Person();

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
 
// replace the sayHello method
Student.prototype.sayHello = function(){
  alert('hi, I am a student');
}

// add sayGoodBye method
Student.prototype.sayGoodBye = function(){
  alert('goodBye');
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

封装

在前面的例子中,Student类并不需要知道Person类的walk()方法是如何实现的,但是它仍然能够调用这个方法;除非我们想要变动walk()这个方法,否则Student类没有必要显式地再去定义它。这就叫做封装。通过它,每个类都可以继承其父类的方法并且只需要定义它希望改变的东西。

抽象

抽象是一种允许对当前工作中的问题进行建模的机制。它可以通过继承(细分)或者聚合来实现。JavaScript通过继承来实现细分,同时通过让类的实例成为其它对象的属性值来实现聚合。

下面的JavaScript例子中,Function类继承自Object类(这演示了模型的细分。),同时Function.prototype属性是Object类的一个实例(这演示了聚合):

var foo = function(){};
alert( 'foo is a Function: ' + (foo instanceof Function) );
alert( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) );
多态

就像所有的方法和属性都被定义在prototype属性中一样,不同的类可以定义相同名称的方法;方法的作用域只限于定义它的类。这只在两个类之间没有父子关系(其中一个类与另一个类没有在同一条继承链上)时有效。

备注

在本文中展示的实现面向对象编程的技术并不是唯一能在JavaScript中使用的技术,JavaScript在如何实行面向对象编程方面非常灵活。

同样,这里展示的技术没有使用任何语言的黑客技术,也没有模仿其它语言的对象理论实现技术。

还有其它更为先进的JavaScript面向对象编程技术,但那些已经超出了这篇简介的范围。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值