JavaScript面向对象编程入门:从原型到继承

作者:代码拾荒时间:2026-10-28 09:18:04浏览:175

本文深入解析JavaScript面向对象编程核心概念,涵盖类定义、原型继承、构造函数及封装技术。通过具体代码示例,展示如何利用函数模拟类,掌握属性与方法定义,理解原型链工作原理,并实现单类继承与多态,帮助开发者构建模块化、可维护的JavaScript应用。

JavaScript面向对象编程概述

尽管JavaScript在面向对象特性上与其他编程语言存在差异,并因此引发了一些讨论,但毫无疑问,JavaScript具备强大的面向对象编程能力。本文将首先介绍面向对象编程的基本概念,回顾JavaScript的对象模型,最后通过实例演示JavaScript中的面向对象编程实践。

JavaScript基础回顾

如果你对变量、类型、函数以及作用域等JavaScript基础概念尚不熟悉,建议先阅读相关介绍JavaScript主题的文章,或查阅JavaScript 1.5核心指南以巩固基础。

面向对象编程核心概念

面向对象编程(OOP)是一种编程范式,它通过抽象来构建基于现实世界的模型。该技术综合运用了模块化、多态性和封装等已有范式。目前,包括Java、JavaScript、C#、C++、Python、PHP、Ruby和Objective-C在内的许多主流编程语言均支持面向对象编程。面向对象编程可被理解为通过协作的对象集合来进行软件设计,这与传统上将程序视为函数集合或指令列表的观点截然不同。在OOP中,每个对象都能接收消息、处理数据并向其他对象发送消息。每个对象可被视为具有特定角色或职责的独立小机器。面向对象编程旨在提升代码的灵活性和可维护性,因此在大型软件工程中得到了广泛应用。由于其高度强调模块化,OOP代码旨在简化开发过程,使后续理解更为容易,并且相较于少用模块化的编程方式,能更直接地处理复杂情况和步骤。

专用术语

类(Class):定义对象的特征。对象(Object):类的实例。属性(Property):对象的某一特征,例如颜色。方法(Method):对象的一种能力,例如行走。构造函数(Constructor):实例化时调用的方法。继承(Inheritance):一个类可以继承另一个类的特征。封装(Encapsulation):类仅定义对象的特征,方法仅定义其执行方式。抽象(Abstraction):将对象的复杂继承、方法和属性结合,以模拟现实模型。多态(Polymorphism):不同类可能定义相同的方法或属性。如需进一步了解面向对象编程,可参阅维基百科相关词条。

基于原型的编程机制

基于原型的编程

基于原型的编程(Prototype-based programming)是一种面向对象编程风格,其中不存在类(classes),行为重用(在基于类的语言中称为继承)是通过修饰充当原型的现有对象来完成的。这种模式也被称为无类(class-less)、面向原型(prototype-oriented)或基于实例(instance-based)的编程。关于基于原型语言的最初且规范的示例是由David Ungar和Randall Smith开发的Self编程语言。然而,这种无类编程风格近年来日益流行,已被JavaScript、Cecil、NewtonScript、Io、MOO、REBOL、Kevo、Squeak(当使用Viewer框架操纵Morphic组件时)及其他几种语言所采用。

JavaScript面向对象编程

JavaScript核心与自定义对象

核心对象(Core Objects)

JavaScript包含几个核心对象,例如Math、Object、Array和String等。以下示例演示了如何使用Math对象的random()方法获取随机数。

代码如下:

alert(Math.random());

提示:本例及所有其他示例均假设alert函数已在全局范围内定义(正如Web浏览器中内置的alert一样)。alert函数实际上并非JavaScript语言本身的一部分。

JavaScript核心对象列表,请参阅JavaScript 1.5核心参考中的全局对象(Global Objects)。在JavaScript中,每个对象都是Object对象的实例,因此继承其所有属性和方法。

自定义对象(Custom Objects)

类(The Class):JavaScript是基于原型的语言,不包含如C++或Java中那样的类声明(class statement)。这有时会让习惯使用类声明语言的程序员感到困惑。不过,JavaScript使用函数(functions)作为类。定义一个类等同于定义一个函数。在以下示例中,我们定义了名为Person(人)的新类。

代码如下:

function Person() { }


对象(类实例)(The Object (Class Instance))

要创建obj对象的一个新实例,我们使用语句new obj,并将结果(其类型为obj)赋值给某个变量(variable),以便后续访问。


在以下示例中,我们首先定义名为Person的类,然后创建两个实例(person1和person2)。


代码如下:

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

还可参阅新的实例化替代方法Object.create。

构造函数(The Constructor):实例化(创建对象实例的瞬间)时会调用构造函数。构造函数是类的一个方法。在JavaScript中,函数(function)即作为该对象的构造函数;因此,无需显式定义一个构造函数方法。类中声明的每个行为在实例化时都会执行。构造函数用于设置对象属性或调用方法,为使用该对象做准备。本文稍后将介绍,通过使用不同的语法来添加类方法及其定义。在以下示例中,当实例化Person时,Person类的构造函数会显示一个警告框。

代码如下:

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

属性(对象属性)(The Property (object attribute))

属性是包含在类中的变量;每个对象实例都拥有这些属性。属性应设置在类(函数)的原型(prototype)属性中,以便继承正常工作。在类中操作属性是通过this关键字实现的,this引用当前对象。在类外部访问(读或写)某个属性需通过以下语法:InstanceName.Property;这与C++、Java以及其他一些语言所用语法相同。(在类内部使用this.Property的语法来获取或设置属性值)。在以下示例中,我们为Person类定义gender(性别)属性,并在初始化时定义该属性。

代码如下:

function Person(gender) {
this.gender = gender;
alert('Person instantiated');
}
var person1 = new Person('Male'); // Male: 男
var person2 = new Person('Female'); // Female: 女
//显示person1的性别
alert('person1 is a ' + person1.gender); // person1 is a Male

方法(The methods):方法遵循与属性相同的逻辑;区别在于它们是函数且被定义为函数。调用方法与访问属性相似,不过你要在方法名末尾添加(),可能会有参数(arguments)。定义一个方法,就是为该类prototype属性上的某个命名属性指定一个函数;函数被分配到的那个名称就是在对象上调用该方法的名称。在以下示例中,我们为Person类定义并使用sayHello()方法。

代码如下:

function Person(gender) {
this.gender = gender;
alert('Person instantiated');
}
Person.prototype.sayHello = function() {
alert('hello');
};
var person1 = new Person('Male');
var person2 = new Person('Female'); // 调用Person的sayHello方法。
person1.sayHello(); // hello

在JavaScript中,方法是作为属性被绑定到某个类/对象的普通函数对象,这意味着,可以“脱离上下文(out of the context)”来调用它们。考虑如下示例代码:

代码如下:

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中没有“基于对象的方法(per-object methods)”,因为该方法的所有引用都指向完全相同的函数,即我们起初在原型上定义的那个函数。当某个函数被作为方法(或确切地说是属性)调用时,JavaScript会将当前的“对象上下文(object context)”“绑定”到特定的“this”变量。这与调用该函数对象的“call”方法等效,如下所示:


代码如下:

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

更多相关信息,请参阅Function.call和Function.apply。

继承、封装与多态实现

继承(Inheritance)

继承是一种方法,用于创建作为一个或多个类专用版本的类。(JavaScript仅支持单类继承)。这个专用类通常被称为子类(child),而其他类通常被称为父类(parent)。在JavaScript中,你要完成继承,需将父类的实例赋给子类,然后将子类特化(specializing)。

提示:由于JavaScript不检测的子类的prototype.constructor(原型的构造函数),参阅Core JavaScript 1.5核心参考:Global Objects:Object:prototype属性,因此我们必须手动指定该值。

在以下示例中,我们定义Student类作为Person的子类。然后我们重新定义sayHello()方法,并添加sayGoodBye()方法。

代码如下:

// 定义Person类
function Person() {}
Person.prototype.walk = function() {
alert('I am walking!');
};
Person.prototype.sayHello = function() {
alert('hello');
};
// 定义Student类
function Student() {
//调用父类构造函数
Person.call(this);
}
// 继承Person
Student.prototype = new Person(); // 修正构造函数指针,由于它指向Person
Student.prototype.constructor = Student; // 替换sayHello方法
Student.prototype.sayHello = function() {
alert('hi, I am a student');
}
// 添加sayGoodBye方法
Student.prototype.sayGoodBye = function() {
alert('goodBye');
}
var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye(); // 检验继承
alert(student1 instanceof Person); // true
alert(student1 instanceof Student); // true

封装

在上例中,Student无须知晓Person类的walk()方法是如何实现的,但仍可使用该方法;Student类无须显式定义该方法,除非我们想改变它。这称为封装(encapsulation),这样每个类继承其父类的方法,并且只需定义它所希望改变的东西。

抽象

抽象是一种机制(mechanism),允许对处理中的问题的当前部分进行建模。这可以通过继承(特化)或组合(composition)来实现。JavaScript通过继承实现特化(specialization),通过让类实例成为其他对象的属性值实现组合。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));

多态

就像所有的方法和属性被定义在原型属性内部一样,不同的类可以定义具有相同名称的方法;方法的作用域限于定义它们的类之内。这仅当两个类之间没有父子关系(当一个类没有从继承链中的其他类继承时)时才为真。

提示

本文中所提出的面向对象编程实现技术不仅适用于JavaScript,因为就如何进行面向对象编程而言,这是非常灵活的。同样,这里展示的技术既没有使用任何语言技巧(language hacks),也没有模仿其他语言的对象理论实现。在JavaScript中,还有其他更高级的面向对象编程的技术,但是那些内容已超出了这篇介绍性文章的范围。

作者最新文章