65.9K
CodeProject 正在变化。 阅读更多。
Home

面向对象的 JavaScript 菜单和其他控件

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2008年4月7日

CPOL

4分钟阅读

viewsIcon

27662

downloadIcon

223

使用面向对象编程原理编写 JavaScript 控件。

引言

本文是对 3 月 31 日(已被删除)文章的修改版本,附带的库同时兼容 Internet Explorer 和 Firefox 浏览器。文章介绍了 JavaScript 如何通过一些简单的约定、规则和技巧来弥补其不支持面向对象特性的不足。此外,我们还提供了下载一些基本的 JavaScript 控件,包括 MenuTextBoxCheckboxTableLayout,它们都是使用这些方法编写的。要查看示例并获取更高级的控件,如 GridComboBoxCalendar、各种输入掩码,请访问我们的 网站

如果您熟悉标准功能,请跳过以下部分。

标准面向对象 JavaScript 特性

自定义 JavaScript 对象(为简单起见,我们称之为)可以定义为一个函数

function Component(name, data)
{ 
	this._name = name;// public property
	this._data = data;// public property
	var someStaticData = null;// private static variable
	this.prototype.setName = _setName;// public member function
	this.prototype.getName = _getName;// public member function
	this.prototype.setData = _setData;// public member function
	this.prototype.getData = _getData;// public member function

	// function implementation
	function _setName(name)
	{
		this._name = name;
	}
	function _getName()
	{
		return this._name;
	}
	function _setData(data)
	{
		this._data = data;
	}
	function _getData()
	{
		return this._data;
	} 
	function someStaticFunction()
	{
		//some code here
	} 
} 

从上面的示例可以看出,在 Component 类上定义了四个 public 方法。可以像这样创建类的实例并调用实例上的方法:

var component = new Component(); 
component.setName("SomeName"); 
component.setData("some string data"); 

此外,_name_data 字段是类的属性。不幸的是,JavaScript 类中的所有属性都可以从外部访问,它们都是 public 的。

component._name = "SomeName"; 

在类内部声明的变量(参见 someStaticData)则不同。它们不能从外部访问,它们是 private 的。那么它们有什么用呢?它们是 static 的,并且这些变量在类的所有实例之间共享。
如果方法未分配给类的原型(参见上面的 someStaticFunction),它就像上面描述的 private static 变量一样——它不应该在其实现中包含任何类方法或属性。

扩展的面向对象约定

本章将解释如何利用 JavaScript 的有限能力使其成为一种面向对象的语言。我们还将展示一些优化性能的编程技巧。

初始化器

看看之前的 Component 类,我们如何使其可重用或实现第一个 OO 特性——继承?如果我们定义一个名为 Initializer 的函数,它接受一个参数(类),并为其分配所有必需的方法(为方便起见,我们使用后缀 T),会怎么样?

function ComponentT(obj)
{ 
	obj.prototype.setName = _setName;// public member function
	obj.prototype.getName = _getName;// public member function
	obj.prototype.setData = _setData;// public member function
	obj.prototype.getData = _getData;// public member function

	// function implementation
	function _setName(name)
	{
		this._name = name;
	}
	function _getName()
	{
		return this._name;
	}
	function _setData(data)
	{
		this._data = data;
	}
	function _getData()
	{
		return this._data;
	} 
} 

请注意,在原型赋值中,我们使用参数 obj 代替此操作。
然后我们从 Component 类中删除所有方法声明和实现:

function Component(name, data)
{ 
	this._name = name;// public property
	this._data = data;// public property
} 

使用 Initializer 还能带来巨大的性能提升——尝试创建 100 个类实例(在类构造函数中放入 10 个方法),然后对同一个类使用 Initializer,第二种方法的工作速度将是原来的 10 倍,因为原型只分配一次,而不是在每次创建对象时都进行。在这里,我们来实现第一个 OOP 特性——继承。

继承

现在,我们添加这一行代码:

ComponentT(Component); 

发生了什么?当调用函数时,它将 ComponentT 函数中的所有方法分配给 Component —— Component 继承了 ComponentT
如果只有一个 Component,这没什么用,但如果我们想在 Control 类中扩展它的功能怎么办?

function Control(parent)
{ 
	this._parent = parent;// public property
} 

然后我们可以这样调用它:

ComponentT(Control); 

Control 类现在继承自 Component。如果您想继承自某个特定类,应该提供 Initializer。因此,我们为 Control 类做同样的事情,但现在我们删除之前的代码行,将其放入 Initializer 中:

ControlT(Control); 
function ControlT(obj)
{ 
	ComponentT(obj);
	obj.prototype.setParent = _setParent;// public member function
	obj.prototype.getParent = _getParent;// public member function
	obj.prototype.paint = _paint;// public member function
	function _setParent(parent)
	{
		this._parent = parent;
	}
	function _getParent()
	{
		return this._parent;
	}
	function paint()
	{
		// some code
	}
} 

如果我们创建 TextBox,它扩展了 Control,而 Control 扩展了 Component,那么图景会更清晰(如果您还不明白的话):

function TextBox(parent, text)
{ 
	this._text = text; 
} 
TextBoxT(TextBox); 
function TextBoxT(obj)
{ 
	ControlT(obj);
	obj.prototype.setText = _setText;// public member function
	obj.prototype.getText = _getText;// public member function
	obj.prototype.paint = _paint;// public member function
	function _setText(text)
	{
		this._text = text;
	}
	function _getText()
	{
		return this._text;
	} 
	function paint()
	{
		// some code
	}
}

这还没完,那些未继承的属性怎么办?
这个问题可以通过一个简单的约定来解决:

  • 我们从所有类函数(ComponentControlTextbox)内部删除所有属性。
  • 在每个类的 Initializer 中放置一个成员函数,并将其命名为 initialize
  • 在该函数的实现中,放置相应的属性。
  • 从每个构造函数中调用此函数。

完整的最终代码将是:

function Component(name, data)
{ 
	this.initialize(name, data); 
}
function Control(parent)
{ 
	this.initialize(parent); 
}
function TextBox(parent, text)
{ 
	this.initialize(parent, text); 
}
function ComponentT(obj)
{ 
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setName = _setName;// public member function
	obj.prototype.getName = _getName;// public member function
	obj.prototype.setData = _setData;// public member function
	obj.prototype.getData = _getData;// public member function
	// function implementation
	function _initialze(name)
	{
		this.setName(name);
	}// rest of implementation here
}
function ControlT(obj)
{ 
	ComponentT(obj);
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setParent = _setParent;// public member function
	obj.prototype.getParent = _getParent;// public member function
	obj.prototype.paint = _paint;// public member function
	function _initialze(parent)
	{
		Component.prototype.initialze.call(this); 
		this.setParent(parent);
		this.paint(); 
	}
	// rest of implementation here
}
function TextBoxT(obj)
{ 
	ControlT(obj);
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setText = _setText;// public member function
	obj.prototype.getText = _getText;// public member function
	obj.prototype.paint = _paint;// public member function
	function _initialze(parent, text)
	{
		Control.prototype.initialze.call(this, parent); 
		this.setText(text);
	}// rest of implementation here
} 

现在一切都已继承。请注意 TextBoxTinitialize 方法中这个巧妙的 Control.prototype.initialze.call(this, parent);。如果您不知道,它会调用 Controlinitialize 方法实现,并将自身作为引用传递,告知函数内部的 this 实际上将传递 TextBox 实例。它还将 parent 作为第一个参数传递。这允许调用基类方法。正如您所见,我们遇到了下一个 OOP 特性——多态。

多态

从上面的示例中,请注意层次结构中的每个类都有自己的 initialize 方法,该方法被子类覆盖。那么,如果您有一个 TextBox 实例,它扩展了 Control,并通过调用 initialize 方法创建,而 initialize 又调用 Controlinitialize 方法,而 Controlinitialize 方法又调用 paint 方法,会发生什么?

var txtName = new TextBox(parent, "John Smith"); 

两个类都有 paint 方法,但将调用 TextBox 的正确方法——这就是所谓的多态

静态方法

您可以通过以下方式声明 static 方法:

  • 它被声明为普通的成员函数。
  • 在其实现中,您不能使用类的非 static 属性或函数。
  • 它通过类构造函数从外部调用:Control.prototype.foo();

接口

接口被创建为类,区别在于它们没有属性,并且在其函数实现中,会抛出一个错误——这迫使开发人员在接口是类层次结构的一部分时实现该方法。

function BinderT(obj)
{ 
	IBinder(obj); 
	obj.prototype.setControl = _setControl;// public member function
	obj.prototype.getControl = _getControl;// public member function
} 
function IBinder(obj)
{ 
	obj.prototype.setControl = _setControl;// public member function
	obj.prototype.getControl = _getControl;// public member function
	function _setControl()
	{
	throw "Please implement setControl method.";
	}
	function _getControl()
	{
		throw "Please implement getControl method.";
	}
}

历史

  • 2008年4月7日:初始帖子
© . All rights reserved.