面向对象 JavaScript 入门。
编写面向对象结构在 JavaScript 中的入门。
目录
引言
许多 JavaScript 程序员忽视或不知道编写面向对象 JavaScript 的能力。虽然 JavaScript 通常不是面向对象的语言,但它是一种基于原型的语言,这意味着继承类不是直接从基类派生,而是通过克隆作为原型的基类来创建的。这可以用于在 JavaScript 中实现封装、继承和多态性,从而创建面向对象的感觉。
面向对象的 JavaScript 也有几个优点。作为一种解释型语言,这意味着可以随时向类添加方法和属性,而不必像 C++ 等其他面向对象语言那样在类构造函数中声明。由于 JavaScript 支持可变数据类型,类属性不必采用固定数据类型(例如 boolean
或 string
),而是可以随时更改。此外,面向对象的 JavaScript 比过程式 JavaScript 更灵活、更高效,因为对象完全支持封装,并且可以使用 prototype
属性实现继承和多态性。
必备组件
尽管这是一篇关于面向对象 JavaScript 的入门文章,但了解面向对象编程会很有帮助,因为本文不会深入探讨这方面。但是,下面列出了关键的面向对象编程术语及其定义,以提供一些指导。
关键术语
本文将使用几个关键术语,总结如下
- 类:对象的定义,包括其方法和属性。
- 封装:指对象实例内部传递的数据被包含在该对象实例中。当创建对象的新实例时,会为该实例创建一组新的数据。
- 继承:指一个对象成为另一个类的“子”对象或子类,并且“父”类的属性和方法被应用于子类。
- 多态性:指一个类的子类可以在自己的上下文中调用相同的通用继承函数。
- 属性:与类关联的变量。
- 方法:与类关联的函数。
JavaScript 中的简单类
定义类
在 JavaScript 中可以非常容易地实现一个基本类。定义类只需声明一个 function
<script language="Javascript">
..
function MyClass()
{
}
..
</script>
这三行代码创建了一个名为 MyClass
的新对象,可以使用 new
运算符创建它的实例,例如
var c = new MyClass();
函数 MyClass
也充当类构造函数,当使用 new
运算符调用该类的新实例时,将调用此函数。
创建类属性
到目前为止,这段代码只是一个简单的类,只声明了它的构造函数。要向类添加属性,我们使用 this
运算符,后跟属性名称。如前所述,方法和属性可以在 JavaScript 中的任何位置创建,而不仅仅是在类构造函数中。这是一个向 MyClass
添加属性的示例。
..
function MyClass()
{
this.MyData = "Some Text";
this.MoreData = "Some More Text";
}
..
这些属性可以通过以下方式访问
var c = new MyClass();
alert(c.MyData);
这段代码将属性 MyData
和 MoreData
添加到类中。这可以在类构造函数和类方法中的任何位置使用 this
运算符访问,因此 MyData
可以通过使用 this.MyData
访问。另请注意,与某些面向对象语言不同,类属性使用 .
访问,而不是 ->
。这是因为 JavaScript 不区分指针和变量。如果在创建时将类引用存储在变量中,则可以通过变量名后跟 .
然后是类属性的名称来访问类属性,在此示例中为 myData
,它可以通过 c.MyData
访问。
创建类方法
正如本文前面所说,类方法是使用 prototype
属性创建的。当在 JavaScript 中创建一个类的方法时,该方法将使用 prototype
属性添加到类对象中,如以下代码片段所示
..
function MyClass()
{
//Any properties created here
}
MyClass.prototype.MyFunction = function()
{
//Function code here
}
..
为了清晰起见,这里的方法是使用 = function()
创建的。同样,该方法也可以通过声明 function MyClass.prototype.MyFunction()
来创建。这段代码的作用是使用 prototype
属性使 MyFunction
成为 MyClass
的方法。这使得 MyFunction
可以使用 this
运算符访问在 MyClass
中创建的任何其他方法或属性。例如
..
function MyClass()
{
this.MyData = "Some Text";
}
MyClass.prototype.MyFunction = function(newtext)
{
this.MyData = newtext;
alert("New text:\n"+this.MyData);
}
..
这段代码创建了 MyClass
类,然后在类构造函数中创建了一个名为 MyData
的属性。然后使用 prototype
运算符将一个名为 MyFunction
的方法添加到 MyClass
对象中,以便它可以访问 MyClass
的属性和方法。在该方法中,MyData
被更改为 newtext
,这是该方法的唯一参数。然后使用警告框显示此新值。
封装
封装是面向对象编程的一个有用部分,它“封装”或包含一个类实例中的数据,使其与同一类的另一个实例中的数据隔离开来。这就是为什么在类中使用 this
运算符,以便它检索该类实例中该变量的数据。
公共、受保护和私有成员
封装在 JavaScript 中通过分隔类中的实例数据来实现。但是,通过 public
、protected
和 private
运算符没有不同程度的封装。这意味着无法限制对数据的访问,如其他面向对象编程语言中所示。这样做的原因是,在 JavaScript 中这样做根本没有必要,即使对于相当大的项目也是如此。因此,类属性和方法可以从任何地方访问,无论是在类内部还是外部。
实践中的封装
下面展示了一个封装的例子
..
function MyClass()
{
this.MyData = "Some Text";
}
MyClass.prototype.MyFunction = function(newtext)
{
this.MyData = newtext;
alert("New text:\n"+this.MyData);
}
..
var c = new MyClass();
c.MyFunction("Some More Text");
var c2 = new MyClass();
c2.MyFunction("Some Different Text");
如果调用,c.MyData
将返回 "Some More Text"
,而 c2.MyData
将返回 "Some Different Text"
,表明数据封装在类中。
封装结论
封装是面向对象编程的重要组成部分,以便类不同实例中的数据彼此分离;这在 JavaScript 中通过使用 this
运算符实现。然而,与其他面向对象编程语言不同,JavaScript 不限制对类实例中数据的访问。
继承
继承属性
正如本文前面所说,JavaScript 中没有直接继承,因为它是一种原型语言。因此,一个类要从另一个类继承,需要使用 prototype
运算符来克隆父类构造函数,从而继承其方法和属性。父类构造函数也会在子类的构造函数中调用,以将其所有方法和属性应用于子类,如下面的代码所示
..
//Parent class constructor
function Animal(name)
{
this.name = name;
}
//Inherited class constructor
function Dog(name)
{
Animal.call(this, name);
}
Dog.prototype = new Animal();
Dog.prototype.ChangeName(newname)
{
this.name = newname;
}
..
在上面的代码示例中,创建了两个类——一个名为 Animal
的基类和一个名为 Dog
的子类,它继承自 Animal
。在基类构造函数中,创建了一个名为 name
的属性,并为其设置了一个传递给它的值。
当构造继承类时,需要两行代码才能从基类继承,如 Dog
所示
Animal.call(this, name);
这行代码在子类的构造函数中调用。call()
是一个 JavaScript 函数,它在指定的上下文(第一个参数)中调用一个函数。被调用函数所需的参数也从 call()
的第二个参数开始传递,如 name
所示。这意味着基类构造函数在子类构造函数中被调用,从而将 Animal
中创建的方法和属性应用于子类。
从基类继承所需的第二行代码是
Dog.prototype = new Animal();
这行代码的作用是将继承类(使用时将克隆父构造函数)的原型设置为父类的新实例,从而继承子类中方法中的任何方法或属性。
另请注意,一旦子类从父类继承,任何需要从父类访问的数据都可以从子类内部使用 this
运算符访问,因为方法和属性现在是子类对象的一部分。
继承方法
与属性类似,方法也可以在 JavaScript 中从父类继承,类似于属性的继承,如下所示
..
//Parent class constructor
function Animal(name)
{
this.name = name;
}
//Parent class method
Animal.prototype.alertName = function()
{
alert(this.name);
}
//Inherited class constructor
function Dog(name)
{
Animal.call(this, name);
this.collarText;
}
Dog.prototype = new Animal();
Dog.prototype.setCollarText = function(text)
{
this.collarText = text;
}
..
可以如下调用继承方法
var d = new Dog("Fido");
d.alertName();
这将调用 alertName()
,它是 Animal
的继承方法。
创建继承类的实例
继承自另一个类的类在 JavaScript 中可以像调用基类一样调用,方法和属性也可以类似地调用。
var d = new Dog("Fido"); //Creates an instance of the subclass
alert(d.name); //Retrieves data inherited from the parent class
d.setCollarText("FIDO"); //Calls a subclass method
子类中继承的方法也可以类似地用变量名代替 this
运算符来调用
var d = new Dog("Fido"); //Creates an instance of the subclass
d.alertName(); //Calls the inherited method
继承结论
继承是面向对象编程的三个重要概念之一。它在 JavaScript 中使用 prototype
和 call()
函数实现。
多态
多态性是面向对象编程中继承原则的扩展,也可以在 JavaScript 中使用 prototype
运算符实现。多态性是指一个类的子类可以在自己的上下文中调用相同的通用继承函数。例如
..
//Parent class constructor
function Animal(name)
{
this.name = name;
}
Animal.prototype.speak = function()
{
alert(this.name + " says:");
}
//Inherited class "Dog" constructor
function Dog(name)
{
Animal.call(this, name);
}
Dog.prototype.speak = function()
{
Animal.prototype.speak.call(this);
alert("woof");
}
//Inherited class "Cat" constructor
function Cat(name)
{
Animal.call(this, name);
}
Cat.prototype.speak = function()
{
Animal.prototype.speak.call(this);
alert("miaow");
}
..
这段代码意味着如果调用 Dog
的实例,然后调用 Dog
的 speak()
函数,它将覆盖父类的 speak()
函数。但是,尽管我们想对每个子类的 speak()
版本做一些不同的事情,但我们希望通过 Animal.prototype.speak.call(this);
调用父类的通用 speak()
函数,它在子类的上下文中调用继承的函数。然后在此之后我们用它做其他事情,对于 Dog
是 alert("woof");
,对于 Cat
是 alert("miaow");
如果调用,它将如下所示
var d = new Dog("Fido"); //Creates instance of Dog
d.speak(); //Calls Dog's speak() function
var c = new Cat("Lucy"); //Creates instance of Cat
c.speak(); //Calls Cat's speak() function
前两行将弹出“Fido says:” (父类的 speak()
函数),然后是“woof”(Dog
的 speak()
函数)。
后两行将弹出“Lucy says:” (父类的 speak()
函数),然后是“miaow”(Cat
的 speak()
函数)。
多态性结论
多态性是面向对象编程中一个非常有用的部分,虽然本文没有深入探讨,但其原理保持不变,并且可以在 JavaScript 中多态性的大多数方面以这种方式应用。
结论
阅读本文后,您应该能够
- 创建带方法和属性的类
- 创建继承类
- 创建多态函数
这只是一篇入门文章,但我希望您能将这些学到的技能用于更复杂的面向对象结构。
历史
- 2008 年 7 月 20 日:未修改的第一版
- 2008 年 8 月 4 日:编辑了封装部分