在Javascript中使用面向对象的编程

前面说过:现实生活中我们一般是先有对象,然后我们根据这些对象相同或相似的属性和行为抽象出一个类,而在编程的世界里,我们就是上帝,要造人的话在脑海中会先形成一个抽象的人的模型,这个模型就是类,比如上面的学生类,然后才会根据这个模型造出具体的人,所以,在程序设计中是先有类,再创建类的具体对象

==============================

在Javascript中使用面向对象的编程

在Javascript中使用面向对象的编程

(源:http://www.cnblogs.com/seewood/archive/2005/06/24/180740.html

Translated by Daniel.Seewood (http://seewood.isme.net
这是一篇,我个人认为最好的,Javascript面向对象编程的文章。翻译不好的地方,还望大家指正,谢谢。
如果您需要,可以访问下面的地址取得原文:http://mckoss.com/jscript/object.htm
介绍
 当不使用强类型的时候(变量不必先声明后使用),Javascript这种解析性的语言,可以巧妙的达成面向对象(object-oriented)的功能,包括:

•封装 (Encapsulation)
•多态 (Polymorphism )
•继承 (Inheritance)


简单对象(Simple Objects)
 在Javascript中,最简单的可构建的对象,就是机制内建的Object对象。在Javascript中,对象是指定名称的属性(property)的集合。做为解析性语言,Javascript允许在任何时间给一个对象创建任意个属性(不像C++,它的属性是可以在任何时间添加给对象。它们并不需要事先在对象的声明(definition)或者构造(constructor)中,进行定义)。

 所以,举例来说,我们可以创建一个对象,然后添加一系列的属性给它,就像这样:

obj = new Object;
obj.x = 1;
obj.y = 2;
这里,Javascript对象,可以用图形表示成这样的结构:

obj
x 1
y 2
prototype properties
constructor
function Object

 另外需要注意的是,我们创建的x和y属性, 我们的对象默认有一个属性constructor,他指向一个Javascript内部对象函数(funciton)。 (译者注:prototype,原型在后文会有进一步的说明)

对象的构造函数(Object Constructors)
 对于要定义的对象类型,Javascript允许我们自己给对象类型定义构造函数:

function Foo()
{
    this.x = 1;
    this.y = 2;
}
obj1 = new Foo;

obj1
x 1
y 2
prototype properties
constructor function Foo

这里要说明的是,我们可以创建任意多个Foo类型的对象实例,它们也都将分别初始化自己的x和y属性为1和2。

简单的方法的的实现(A Simple Method Implementation)
 为了封装对象的行为功能,向调用者隐藏执行过程,我们需要给对象创建方法(method)。Javascript允许你将任意一个函数(function)分配给对象的一个属性。当我们使用 obj.Function 的语法调用函数的时候,将把函数原来定义this 的指向当前这个对象(就像它在构造函数中的那样)。

function Foo()
{
    this.x = 1;
    this.y = 2;
    this.Bar = MyMethod;
}

function MyMethod(z)
{
    this.x += z;
}
obj2 = new Foo;

obj2
x 1
y 2
Bar function MyMethod
prototype properties
constructor function Foo

现在,我们简单的调用一下,做为对象的方法的Bar函数:

obj2.Bar(3);

obj2
x 4
y 2
Bar function MyMethod
prototype properties
constructor function Foo

所以,你可以方便的给对象定义构造函数和方法,使其对调用者而言,隐藏它的实现过程。同样的,因为Javascript不是强类型的,所以,我们可以通过定义有相同名字的方法的对象,来简单的实现多态性(polymorphism)。

function Foo()
{
    this.x = 1;
    this.DoIt = FooMethod;
}

function FooMethod()
{
    this.x++;
}

function Bar()
{
    this.z = 'Hello';
    this.DoIt = BarMethod;
}

function BarMethod()
{
    this.z += this.z;
}

obj1 = new Foo;
obj2 = new Bar;

obj1
x 1
DoIt function FooMethod
prototype properties
constructor function Foo
obj2
z Hello
DoIt function BarMethod
prototype properties
constructor function Bar

function Poly(obj)
{
    obj.DoIt();
}

Poly(obj1);
Poly(obj2);

obj1
x 2
DoIt function FooMethod
prototype properties
constructor function Foo
obj2
z HelloHello
DoIt function BarMethod
prototype properties
constructor function Bar

使用原型实现方法(Using Prototypes to Implement Methods)
 试想一下,每次我们都要创建名称没有使用意义的方法函数,然后在构造函数里,把它们分配给每个方法属性,这是很笨的办法。其实,我发现使用Javascript的原型(prototype)机制,是更为直接的方法。

 每个对象,可以参照一个原型对象,原型对象包含有自己的属性。它就好比是一个对象定义的备份。当代码引用一个属性的时候,它并不存在于对象本身里,那么Javascript将会自动的在原型的定义中查找这个属性。而且,一个对象的原型对象又可以参照另外一个原型对象,就这样以链式最终关联到基类对象的构造函数。(译者注:对于DOM对象等系统的对象,原型对象可以修改,但是不可以赋值改变的,只有自定义对象可以。)这是template模型(译者注:模板方法,《设计模式》中行为模式的一种),它可以简化我们对方法的定义,同时也可以产生强大的继承机制。

 在Javascript中,原型对象是被分配给构造函数的。所以,为了修改对象的原型,必须首先修改构造函数的原型对象的成员。然后,当对象从构造函数被构造的时候,对象将会引用到构造函数的原型。

function Foo()
{
    this.x = 1;
}Foo.prototype.y = 2;
obj = new Foo;
document.write('obj.y = ' + obj.y);


obj.y = 2
obj
x 1
prototype properties
constructor function Foo prototype
y 2
 
y 2

 即使我们并没有直接的把y属性分配给obj,obj对象仍然有一个y属性。当我们引用obj.y的时候,Javascript实际返回obj.constructor.prototype.y的引用。我们可以肯定的是,原型的值的改变,也将会反映到对象中。

Foo.prototype.y = 3;
document.write('obj.y = ' + obj.y);


obj.y = 3
obj
x 1
prototype properties
constructor function Foo prototype
y 3
 
y 3

我们也可以发现,一旦我们初始化一个属性的“私有”(private )的值,存放在原型中的值并不会收到影响:

obj.y = 4;
Foo.prototype.y = 3;

obj
x 1
y 4
prototype properties
constructor function Foo prototype
y 3
 

原型方法的命名(Prototype Method Naming)
我发现了可以直接定义类的原型的方法的语句,而不需要单独的函数的名称:

function Foo()
{
    this.x = 1;
}

function Foo.prototype.DoIt()
{
    this.x++;
}

obj = new Foo;

obj.DoIt(); obj
x 2
prototype properties
constructor function Foo prototype
DoIt function Foo.prototype.DoIt
 
DoIt function Foo.prototype.DoIt

基于原型的子类继承(Prototype-based Subclassing )
 一旦可以建立原型对象链,我们就可以使用它做为对象的子类的类型。这个方法要注意的是,我们创建了一个基类对象的实例,并把它做为我们的类的构造函数的原型对象。这么做,我们所创建的所有的对象,将继承基类对象的所有成员和(方法)。但是要注意,基类的构造函数只会被调用一次(译者注:从基类到子类的构造函数都是唯一的,即基类的构造函数)。这不像C++,基类的构造函数,对于每个继承的子类,都可以分别的调用。在后面,我将展示,当独立的构造函数被需要的时候,另外一种可选的方式来创建继承类。
function TextObject(st)
{
    this.st = st;
    this.fVisible = true;
}

function TextObject.prototype.Write()
{
    document.write('' + this.st);
}

function ItalicTextObject(st)
{
    this.st = st;
}

ItalicTextObject.prototype = new TextObject('x');
ItalicTextObject.prototype.Write = ITOWrite;

function ITOWrite()
{
    document.write('' + this.st + '');
}

obj1 = new TextObject('Hello, mom');
obj2 = new ItalicTextObject('Hello, world');
obj1.Write();
obj2.Write();


Hello, mom
Hello, world
obj1
st Hello, mom
fVisible true
prototype properties
constructor function TextObject prototype
Write function TextObject.prototype.Write
 
Write function TextObject.prototype.Write
obj2
st Hello, world
prototype properties
constructor function TextObject prototype
Write function TextObject.prototype.Write
 
fVisible true
Write function ITOWrite

这个结构存在两个问题。一个是,当每次构造继承的类的时候,基类的构造函数都不会被调用。假如,构造函数不做太多的事情,只是初始化一些成员变量为静态的值,这个问题就不是太明显了。第二个,注意,我将不能使用"function Obj.prototype.Method"的方式,来定义继承类的成员。这是因为,对于构造函数来说,我要把这些方法的定义,放入新创建的原型对象,而不是添加到,默认的原型对象。
另一种子类继承方式(An Alternate Subclassing Paradigm)
我们可以提出一种方法,更类似于反映C++类的概念和子类的定义,以及从子类反向存取基类的纯原型链的风格。它需要添加新的方法DeriveFrom给基类。

function Function.prototype.DeriveFrom(fnBase)
{
    var prop;
    if (this == fnBase)
        {
        alert("Error - cannot derive from self");
        return;
        }
    for (prop in fnBase.prototype)
        {
        if (typeof(fnBase.prototype[prop]) == "function" && !this.prototype[prop])
            {
            this.prototype[prop] = fnBase.prototype[prop];
            }
        }
    this.prototype[fnBase.StName()] = fnBase;
}
function Function.prototype.StName()
{
    var st;
    st = this.toString();
    st = st.substring(st.indexOf(" ")+1, st.indexOf("("))
    return st;
}
function TextObject(st)
{
    this.st = st;
    this.fVisible = true;
}

function TextObject.prototype.Write()
{
    document.write('' + this.st);
}

function TextObject.prototype.IsVisible()
{
    return this.fVisible;
}

function ItalicTextObject(st)
{
    this.TextObject(st);
}

ItalicTextObject.DeriveFrom(TextObject);

function ItalicTextObject.prototype.Write()
{
    document.write('' + this.st + '');
}

obj1 = new TextObject('Hello, mom');
obj2 = new ItalicTextObject('Hello, world');
obj1.Write();
obj2.Write();
Hello, mom
Hello, world
obj1
st Hello, mom
fVisible true
prototype properties
constructor function TextObject prototype
Write function TextObject.prototype.Write
IsVisible function TextObject.prototype.IsVisible
 
IsVisible function TextObject.prototype.IsVisible
Write function TextObject.prototype.Write
obj2
st Hello, world
fVisible true
prototype properties
constructor function ItalicTextObject prototype
Write function ItalicTextObject.prototype.Write
IsVisible function TextObject.prototype.IsVisible
TextObject function TextObject prototype
Write function TextObject.prototype.Write
IsVisible function TextObject.prototype.IsVisible
 
 
IsVisible function TextObject.prototype.IsVisible
TextObject function TextObject prototype
Write function TextObject.prototype.Write
IsVisible function TextObject.prototype.IsVisible
 
Write function ItalicTextObject.prototype.Write

我们还得到了一个额外的好处,那就是,我们可以从多个基类进行继承(多重的继承)。

------------------------------------------------------

JavaScript 编程引入命名空间

 JavaScript 代码一般最常见的语法格式就是定义函数 function xxx(){/*code...*/},经常有这样的一大堆函数定义。函数名很容易发生冲突,特别是引入多个js文件时,冲突的情况尤为明显。因此也就有引入命名空间的必要。

 Javascript 本身没有命名空间的概念,需要用对象模拟出来。
 比如定义一个命名空间的类,用于创建命名空间:

function NameSpace(){
}

 这是一个构造函数,但却不做任何事情,再来下面的代码:

var comment = new NameSpace();
comment.list = function(){/*code...*/};
comment.counter = 0;

 第一行创建所谓命名空间(其实就是一个空白对象),名为comment,第二、三行定义该空间下的两个方法。调用时可以使用 comment.list() 或者 comment.counter++ 等;
 再创建子命名空间:

comment.add = new NameSpace();
comment.add.post = function(){/*code...*/}
comment.add.check = function(){}

 之所以引入命名空间的概念,是为了避免函数名相同的问题。上面的过程也可以这样定义:

var comment = {
 list : function(){/*code...*/},
 add  : {
  post  :  function(){/*code...*/},
  check :  function(){/*code...*/}
 }
}

 prototype.js 里面就大量使用这种方式,虽然这种方式更直观地像一棵树,但只要节点稍多一些,眼睛就忙于寻找这些节点的关系,命名空间的做法是横向地描述这种关系树,层次关系直接表现在字面上,两种方式效果一致,但书写风格却各有特点。

再扩展一个方法:
NameSpace.prototype.appendChild = function(ns){
 for (var key in ns){
  this[key] = ns[key];
 }
 return this;
}
NameSpace.prototype.copyChild = NameSpace.prototype.appendChild;

------------------------------------------------------

领悟 JavaScript 中的面向对象

(源:http://www.javaeye.com/topic/155109?page=1,后面有详细讨论及文中问题指出)

 JavaScript 是面向对象的。但是不少人对这一点理解得并不全面。
 在 JavaScript 中,对象分为两种。一种可以称为“普通对象”,就是我们所普遍理解的那些:数字、日期、用户自定义的对象(如:{})等等。
 还有一种,称为“方法对象”,就是我们通常定义的 function。你可能觉得奇怪:方法就是方法,怎么成了对象了?但是在 JavaScript 中,方法的确是被当成对象来处理的。下面是一个简单的例子:

Js代码
1.function func() {alert('Hello!');} 
2.alert(func.toString()); 
 在这个例子中,func 虽然是作为一个方法定义的,但它自身却包含一个 toString 方法,说明 func 在这里是被当成一个对象来处理的。更准确的说,func 是一个“方法对象”。下面是例子的继续:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=func.name%20%3D%20%E2%80%9CI%20am%20func.%E2%80%9D%3B%0Aalert(func.name)%3B">1.func.name = “I am func.”; 
2.alert(func.name); 
 我们可以任意的为 func 设置属性,这更加证明了 func 就是一个对象。那么方法对象和普通对象的区别在哪里呢?首先方法对象当然是可以执行的,在它后面加上一对括号,就是执行这个方法对象了。
Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=func()%3B">1.func(); 
 所以,方法对象具有二重性。一方面它可以被执行,另一方面它完全可以被当成一个普通对象来使用。这意味着什么呢?这意味着方法对象是”可以完全独立于其他对象存在“的。这一点我们可以同 Java 比较一下。在 Java 中,方法必须在某一个类中定义,而不能单独存在。而 JavaScript 中就不需要。
 方法对象独立于其他方法,就意味着它能够被任意的引用和传递。下面是一个例子:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=function%20invoke(f)%20%7B%0A%20%20%20%20f()%3B%0A%7D%0Ainvoke(func)%3B">1.function invoke(f) { 
2.    f(); 
3.} 
4.invoke(func); 
 将一个方法对象 func 传递给另一个方法对象 invoke,让后者在适当的时候执行 func。这就是所谓的“回调”了。另外,方法对象的这种特殊性,也使得 this 关键字不容易把握。这方面相关文章不少,这里不赘述了。
 除了可以被执行以外,方法对象还有一个特殊的功用,就是它可以通过 new 关键字来创建普通对象。
 话说每一个方法对象被创建时,都会”自动的拥有一个叫 prototype 的属性。这个属性并无什么特别之处,它和其他的属性一样可以访问,可以赋值“。不过当我们用 new 关键字来创建一个对象的时候,prototype 就起作用了:它的值(也是一个对象)所包含的所有属性,都会被复制到新创建的那个对象上去。下面是一个例子:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=func.prototype.name%3D%E2%80%9Dprototype%20of%20func%E2%80%9D%3B%0Avar%20f%20%3D%20new%20func()%3B%0Aalert(f.name)%3B">1.func.prototype.name=”prototype of func”; 
2.var f = new func(); 
3.alert(f.name); 
 执行的过程中会弹出两个对话框,后一个对话框表示 f 这个新建的对象从 func.prototype 那里拷贝了 name 属性。而前一个对话框则表示 func 被作为方法执行了一遍。你可能会问了,为什么这个时候要还把 func 执行一遍呢?其实这个时候执行 func,就是起“构造函数”的作用。为了形象的说明,我们重新来一遍:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=function%20func()%20%7B%0A%20%20%20%20this.name%3D%E2%80%9Dname%20has%20been%20changed.%E2%80%9D%0A%7D%0Afunc.prototype.name%3D%E2%80%9Dprototype%20of%20func%E2%80%9D%3B%0Avar%20f%20%3D%20new%20func()%3B%0Aalert(f.name)%3B">1.function func() { 
2.    this.name=”name has been changed.” 
3.} 
4.func.prototype.name=”prototype of func”; 
5.var f = new func(); 
6.alert(f.name); 
 你就会发现 f 的 name 属性不再是"prototype of func",而是被替换成了"name has been changed"。这就是 func 这个对象方法所起到的“构造函数”的作用。所以,在 JavaScript 中,用 new 关键字创建对象是执行了下面三个步骤的:


1.创建一个新的普通对象;
2.将方法对象的 prototype 属性的所有属性复制到新的普通对象中去。
3.以新的普通对象作为上下文来执行方法对象。
 对于“new func()”这样的语句,可以描述为“从 func 创建一个新对象”。总之,prototype 这个属性的唯一特殊之处,就是在创建新对象的时候了。

 那么我们就可以利用这一点。比如有两个方法对象 A 和 B,既然从 A 创建的新对象包含了所有 A.prototype 的属性,那么我将它赋给 B.prototype,那么从 B 创建的新对象不也有同样的属性了?写成代码就是这样:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=A.prototype.hello%20%3D%20function()%7Balert('Hello!')%3B%7D%0AB.prototype%20%3D%20new%20A()%3B%0Anew%20B().hello()%3B">
1.A.prototype.hello = function(){alert('Hello!');} 
2.B.prototype = new A(); 
3.new B().hello(); 
 这就是 JavaScript 的所谓“继承”了,其实质就是属性的拷贝,这里利用了 prototype 来实现。如果不用 prototype,那就用循环了,效果是一样的。所谓“多重继承”,自然就是到处拷贝了。
 JavaScript 中面向对象的原理,就是上面这些了。自始至终我都没提到“类”的概念,因为JavaScript本来就没有“类”这个东西。面向对象可以没有类吗?当然可以。先有类,然后再有对象,这本来就不合理,因为类本来是从对象中归纳出来的,先有对象再有类,这才合理。像下面这样的:

Js代码 < type="application/x-shockwave-flash" width="14" height="15" src="file:///E:/javascripts/syntaxhighlighter/clipboard_new.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" allowscriptaccess="always" quality="high" flashvars="clipboard=var%20o%20%3D%20%7B%7D%3B%20%2F%2F%20%E6%88%91%E5%8F%91%E7%8E%B0%E4%BA%86%E4%B8%80%E4%B8%AA%E4%B8%9C%E8%A5%BF%E3%80%82%0Ao.eat%20%3D%20function()%7Breturn%20%22I%20am%20eating.%22%7D%20%20%2F%2F%20%E6%88%91%E5%8F%91%E7%8E%B0%E5%AE%83%E4%BC%9A%E5%90%83%EF%BC%9B%0Ao.sleep%20%3D%20function()%7Breturn%20%22ZZZzzz...%22%7D%20%20%2F%2F%20%E6%88%91%E5%8F%91%E7%8E%B0%E5%AE%83%E4%BC%9A%E7%9D%A1%EF%BC%9B%0Ao.talk%20%3D%20function()%7Breturn%20%22Hi!%22%7D%20%2F%2F%20%E6%88%91%E5%8F%91%E7%8E%B0%E5%AE%83%E4%BC%9A%E8%AF%B4%E8%AF%9D%EF%BC%9B%0Ao.think%20%3D%20function()%7Breturn%20%22Hmmm...%22%7D%20%2F%2F%20%E6%88%91%E5%8F%91%E7%8E%B0%E5%AE%83%E8%BF%98%E4%BC%9A%E6%80%9D%E8%80%83%E3%80%82%0A%0Avar%20Human%20%3D%20new%20Function()%3B%20%2F%2F%20%E6%88%91%E5%86%B3%E5%AE%9A%E7%BB%99%E5%AE%83%E8%B5%B7%E5%90%8D%E5%8F%AB%E2%80%9C%E4%BA%BA%E2%80%9D%E3%80%82%0AHuman.prototype%20%3D%20o%3B%20%2F%2F%20%E8%BF%99%E4%B8%AA%E4%B8%9C%E8%A5%BF%E5%B0%B1%E4%BB%A3%E8%A1%A8%E4%BA%86%E6%89%80%E6%9C%89%E2%80%9C%E4%BA%BA%E2%80%9D%E7%9A%84%E6%A6%82%E5%BF%B5%E3%80%82%0A%0Avar%20h%20%3D%20new%20Human()%3B%20%2F%2F%20%E5%BD%93%E6%88%91%E5%8F%91%E7%8E%B0%E5%85%B6%E4%BB%96%E5%90%8C%E5%AE%83%E4%B8%80%E6%A0%B7%E7%9A%84%E4%B8%9C%E8%A5%BF%EF%BC%8C%0Aalert(h.talk())%20%2F%2F%20%E6%88%91%E5%B0%B1%E7%9F%A5%E9%81%93%E5%AE%83%E4%B9%9F%E6%98%AF%E2%80%9C%E4%BA%BA%E2%80%9D%E4%BA%86%EF%BC%81">1.var o = {}; // 我发现了一个东西。 
2.o.eat = function(){return "I am eating."}  // 我发现它会吃; 
3.o.sleep = function(){return "ZZZzzz..."}  // 我发现它会睡; 
4.o.talk = function(){return "Hi!"} // 我发现它会说话; 
5.o.think = function(){return "Hmmm..."} // 我发现它还会思考。 
6. 
7.var Human = new Function(); // 我决定给它起名叫“人”。 
8.Human.prototype = o; // 这个东西就代表了所有“人”的概念。 
9. 
10.var h = new Human(); // 当我发现其他同它一样的东西, 
11.alert(h.talk()) // 我就知道它也是“人”了! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值