导航

面向对象的Javascript

Posted on 2013-08-22 13:51  WeiWill  阅读(760)  评论(4)    收藏  举报

最初接触javascript的时候,认为这是一门面向过程的语言,并没有丰富的语法结构,而且语法很随意。而最近有时间深度重温javascript,在拜读了一些大牛的文章后,发现这是一门自由且博大精深的语言。总会不住感叹,原来还可以这样!这里,我来分享一下,我整理的一些粗浅的有关Javascript面向对象编程的思想,以求抛砖引玉,希望可以与大家探讨。

面向对象的语言都有三个显著特性,继承、封装、多态,我们就从三个方面分别来看看在javascript中,如何实践这些思想。

封装

封装1的一种解释是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。目的是为了增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。

我认为,在上述描述中需要注意的是,这里的安全性是不能理解为传统意义上的软件安全性,只是为了防止接口的调用者使用智能感知功能调用了不应使用的方法,而导致意外结果。实际上封装的实现只是一个语法糖,并不存在实际的限制,举个简单的例子,在.net环境下,我们使用反射就能访问到类中的私有方法(根据版本不同,可能需要权限支持)。

1封装的实现

在javascript中,封装的实现涉及到一个javascript常用模式---module模式。关于module模式,我们先来看一段代码。

 //module模式
  //1 简单的封装
  var moduleObject = function (a) {
    function add(a, b) {
      return a + b;
    }
    function multiply(a, b) {
      return a * b;
    }
    return {
      Calculate: function (a, b) {
        return multiply(add(a, b), b);
      }
    };
  } ();
console.log(moduleObject.Calculate(2,5) );
console.log(moduleObject.multiply(2,5) );
View Code

 

这段代码定义了匿名闭包,并把所有逻辑代码添加到闭包中,这样在其中声明的局部变量和函数的作用域都会只在这个函数内。从而就能达到了想要的效果。运行代码可以得出,返回的对象字面量 Calculate被公开,而包内声明的multiply方法被隐藏。

2封装的扩展

为类添加扩展方法,是.net 3.0新增的一个语法糖,它有效的实践了面向对象开发中的开闭原则。同样javascript中,也能实现类似功能。

 1 <script src="ModuleClass.js" type="text/javascript"></script>
 2 <script type="text/javascript">
 3   //封装
 4   var moduleObject = function (oriObject) {
 5     oriObject.ShowMessage = function () {
 6       return "this is the message!";
 7     }
 8     return oriObject;
 9   } (moduleObject);
10   console.log(moduleObject.ShowMessage());
11   console.log(moduleObject.Calculate(2, 5));
12   //console.log(moduleObject.multiply(2, 5));此句会报错
13 </script>
View Code

 

引入的ModuleClass是就上面所讲到“封装的实现”中的函数主体,引入文件的下边就是同样采取匿名闭包,再次添加了一个函数属性。这样的实现对多人协同开发下的代码进行更好的管理。

作为javascript编程的基本模式之一,module模式还有许多功能可供展示,实际上,上述的功能代码也不是尽善尽美的。举个例子,在“封装的扩展”这个小节中,如果多个引用文件包含同一个对象,如何解决对象覆盖问题等等,想了解更多,请参见注释2

继承

继承3的一种解释是特殊类(或子类、派生类)的对象拥有其一般类(或称父类、基类)的全部属性与服务,称作特殊类对一般类的继承。

在javascript中,并没有采用和c++、c#等语言一样的类继承。而是采用了原型继承来扩展类。关于类继承我想大家已经十分熟悉,就不再赘述了。主要介绍一下原型继承,以及其在javascript中的实现。

1原型继承

1)简单继承

说到原型继承,我们先根据一段简单的代码来看一下它是如果实现的。

 1   //继承
 2   var Parent = function () {
 3     this.lastName = "Smith";
 4   }
 5   Parent.prototype.GetLastName = function () {
 6     return this.lastName;
 7   }
 8 
 9   var Child = function () { };
10   Child.prototype = new Parent();
11   console.log("my lastname is " + new Child().GetLastName());
View Code

 

我们可以向父类的prototype属性添加子属性的方式,并且让子类的prototype指向父类的实例,来使子类获得父类的行为。

2)屏蔽构造函数

直接将子类的prototype属性指向父类的prototype属性,就能使子类屏蔽父类里声明的变量。

1   //2 屏蔽构造函数
2   var ChildNotInLaw = function () { };
3   ChildNotInLaw.prototype = Parent.prototype;
4   console.log("my lastname is " + new ChildNotInLaw().GetLastName());
View Code

 

3)声明覆盖

如果多次操作子类prototype属性,就会形成声明覆盖,只能保留最后一次的设置;另外如果使用对象字面量创建原型方法,也会改变prototype,而不是prototype得子属性。

 1   //3 声明覆盖
 2   Child.prototype.GetLastName = function () {
 3     return "Morre";
 4   };
 5   console.log("my lastname is " + new Child().GetLastName());
 6   //会使原型中GetLastName方法消失
 7   Child.prototype = {
 8     GetLastName2: function () {
 9       return "Will";
10     }
11   };
12   console.log("my lastname is " + new Child().GetLastName());
View Code

 

2原型链

说到原型继承就不能不提原型链,我们通过观察可以发现,javascript所有的对象(注意不是对象实例)都有prototype这一属性。在javascript,所有的引用类型都继承自Object,而这个继承就是通过原型链实现的。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实际都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎么样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。5

image

从上图可以看出,原型链是逐级继承,那么如果操作原型链的某个方法,它的查找过程是就如图中反应的一样, JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined。

总之,恰当使用原型开发能提高我们的工作效率,但是如果使用错误,也会加大bug排除的难度,所以请使用原型开发时注意相应的问题,详见注释4、5;

多态

网上来看,大家关于多态的理解并不是不统一,为了减少下文的复杂度或者可能的歧义。对于多态的解释,我倾向于:对不同类型的相同操作,产生不同的操作结果。从这样的解释来看,既然存在相同操作,那么这些“不同类型”一定有某些共性,用面向对象的思想来看,就是说这些类型有相同的父类或者接口,所以多态和继承是密不可分的,继承强调了类型间的“同”,而相反,多态强调的是“异”。

在javascript中对象的自由度很高,可以随时接受扩展和改写,所以可能因为“只缘身在此山中”,而造成错觉。下面基于我对多态的理解,给出一段示例。

 1 //多态
 2   var Person = function () {
 3   };
 4   Person.prototype.SayHello = function () {
 5     return this.Hello();
 6   };
 7   Person.prototype.Hello = function () {
 8   }
 9   var American = function () { };
10   American.prototype = new Person();
11   American.prototype.Hello = function () {
12     return "Hi!";
13   }
14   var Chinese = function () { };
15   Chinese.prototype = new Person();
16   Chinese.prototype.Hello = function () {
17     return "你好!";
18   }
19   console.log(new American().SayHello());
20   console.log(new Chinese().SayHello());
View Code

此处使用了对原型的重写,使生成的实例具有不同的特性,Person对象类似于c#中的抽象类,而American和Chinese就是它的两个子类。

总结

以上简要介绍了javascript应用面向对象开发技术时的实现方法,大部分出自网络整理,也有部分是我自己的理解,谬误之处在所难免,望大家发现后指正。源码只在chrome上运行通过,chrome版本24.0.1312.57。

代码示例地址:https://files.cnblogs.com/weiwill/%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81.rar

注释

1 封装的百度百科:

http://baike.baidu.com/view/154910.htm

2 全面解析Module模式:

http://www.cnblogs.com/TomXu/archive/2011/12/30/2288372.html

3 继承的百度百科

http://baike.baidu.com/view/125322.htm#3

4 原型与原型链

http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html

5 原型链解析

http://www.w3cmm.com/javascript/prototype-chain.html