zoukankan      html  css  js  c++  java
  • JavaScript中的面向对象的讨论(转)

    前言

    今天,WEB2.0时代的到来,给了JavaScript又一次大展身手的机会。Web2.0借助JavaScript技术,使得客户端的Web体验更加丰富多彩,同时JavaScript面对的问题域也变得错综复杂起来,JavaScript代码也随着Web页面的多样化和功能的丰富而快速膨胀。以前的过程式的JavaScript开发方法已经不能适应Web2.0的开发需求,需要一种更先进的设计方法来指导JavaScript的开发,这就是这里我们要讨论的面向对象。

     

    面向对象概念的提出,是软件开发工程发展的一次革命,有了面向对象,很多复杂的大型应用程序的开发可以简化。可以说,有了面向对象,我们手里才得到了一把真正的利刃,可以得心应手的处理复杂而又规模庞大的问题域。

     

    面向对象的三个基本特征是:

    - 封装-把自己的数据和方法封装在对象内部,并让私有的数据和方法在对象外部不可见,而共有的数据和方法在对象外部可见。

    - 继承-在无需重新修改原来对象的前提下,让另一个对象获得原对象的属性和方法的能力

    - 多态-子类可以定义与父类具有相同signature的方法。

     

    因此我们就这三个基本特征展开JavaScript面向对象的讨论。

     

    基本概念

    JavaScript语言对对象提供了天然支持。数组(Array)是对象,函数(function)是对象。但是JavaScript不是一种真正意义上的面向对象语言。这是因为JavaScript并不具备完整的面向对象三个基本特征。

     

    在正式开始讨论之前,首先要对JavaScript的类,成员变量,成员函数,类变量,类方法这几个概念进行说明。

    Ÿ 类

    JavaScript中并没有类的概念,也就是说,JavaScript中并没有语法支持“类”。因此,JavaScript中的类其实都是使用function来模拟的。

    类的定义:

    1 function Circle(r){
    2   this.r = r;
    3 }

    类的实例化:var circle = new Circle(3);

     

    Ÿ 成员变量

    成员变量可以通过多种方式定义:

    构造器中: this.r = r;

    对象:circle.name="my circle";

     

    Ÿ 成员函数

    成员函数也可以通过多种方式定义:

    构造器中:

    1 function Circle(r){
    2   this.r = r;
    3   function sqr(){
    4     return r*r;
    5   }
    6 }

    prototype:

    1 Circle.prototype.getName=function(){
    2     return this.name;
    3 }

     

    Ÿ 类变量

    类变量是属于类的变量。就像java里用static声明的变量,可以直接用类就可以访问。由于类变量是属于类的,因此所有这个类的实例也可以访问这个变量。因此,任何实例都不应该修改类变量(JavaScript中没有类似java的final关键字使类变量不可变)。类变量与通过prototype定义的变量的功能类似,但是他们的访问方式不同。

    访问prototype变量:Circle.prototype.PI=3.14;

    访问类变量:Circle.PI=3.14;

     

    //使用prototype里的变量

    1 Circle.prototype.area1 = function(){
    2     return this.PI*this.r*this.r;
    3 }

    //使用类变量

    1 Circle.prototype.area2 = function(){
    2     return Circle.PI*this.r*this.r;
    3 }

    Ÿ 类方法

    类方法是直接在类上定义的方法。注意,在类方法中不能使用this关键字,就像是在java的static方法中,不能使用this一样。

    Circle.max = function(a,b){
        return a.r>b.r?a:b;
    }
    var maxCircle = Circle.max(new Circle(1),new Circle(2));

     

    面向对象的三个基本特性

    在有了上面的知识以后,下面就开始依据面向对象三个基本特征,对JavaScript的面向对象特性进行讨论。

     

    1. 封装

    JavaScript中,所有元素都可以认为是对象。这就是JavaScript对对象的天然支持。在JavaScript的对象里,还可以定义方法(function),数据变量(var)。这样,JavaScript对象可以把数据和方法封装在对象内部。

    JavaScript是如何实现对象数据的访问控制的呢?JavaScript跟别的面向对象语言一样,也是通过function和变量的不同的声明方式而实现访问控制的。

     

    Ÿ 公有成员

    对象的成员都是公有成员,任何函数都可以访问,修改或删除这些成员。有两种主要途径给对象添加公有成员:

    Ø 构造器:

    构造器中,使用this变量来向对象添加成员。注意,这里只是用this变量添加了属性成员。用this变量添加的function成员,而特权成员(见下文解释)而不是公有成员。

    1 function Container(param){
    2     this.member = param;
    3 }

    Ø 原型:

    使用prototype添加成员是很常用的一种给对象添加公共成员的方式:

    1 Container.prototype.stamp = function(string){
    2     return this.member+string;
    3 }

     

    Ÿ 私有成员

    私有成员要由构造器生成。构造器的参数,以及其中通过var声明成员都是私有成员。私有成员是无法被公有成员访问的。但是公有成员可以通过一些技巧来访问私有成员。例如,

     1 function Container(param){
     2     function dec(){
     3         if(secret>0){
     4             secret-=1;
     5             return true;
     6         }else{
     7             return false;
     8         }
     9     }
        this.member = param;
          var secret = 3;
          var self = this;
    10 }

    在上面的代码例子中,有三个私有成员:param,secret,self。它们在对象内部,并且在对象外部不能访问这三个成员,同时也不能被对象的公有方法访问。他们只对私有成员可见。

    这里有一个技巧,就是定义了一个私有的self变量。通过这个self变量,可以在让私有成员中访问公有成员。

     

    Ÿ 特权成员

    特权成员实际上是特指function成员。特权成员可以访问私有成员(变量和方法),同时它对公共域也是可见的。可以删除或替换一个特权成员,但是不能对他进行修改。

    特权方法是用this在构造器中定义的。

     1 function Container(param){
     2     function dec(){
     3         if(secret>0){
     4             secret-=1;
     5             return true;
     6         }else{
     7             return false;
     8         }
     9     } 
    10     this.member = param;
    11     var secret = 3;
    12     var self = this;
    13  
    14 //特权成员
    15 
    16     this.service = function(){
    17         if(dec()){
    18             return self.member;
    19         }else{
    20             return null;
    21         }
    22     };
    23 }

    上面的代码片段中,service方法就是一个特权成员。可以看到,在service方法中,调用了私有方法dec(),而dec又访问了私有变量secret.

    2. 继承

    JavaScript继承问题一直是网上被讨论最热烈的一个JavaScript问题。在详细讨论JavaScript的继承之前,我们先看一下“继承”的概念。

    继承,让子类具备父类的特性。继承,描述的是类型层面上而不是个体层面上的特性。因此,继承,只应该是描述类型的,也就是说,只能是类之间存在继承关系,而对象实体之间是不存在继承关系的。

    由于JavaScript并不是很清晰(类跟对象没有明显的区分),因此导致JavaScript中的继承实现要大费周折。很多人为JavaScript想出了各种各样的实现继承的方法,包括:构造器继承法,原型继承法,实例继承法,附加继承法。

    这里不去详细讨论每一种继承的实现思路。

    在《JavaScript权威指南1.5》中列出的是prototype继承。

    1 function SubCircle(x, y, r) { 
    2   this.x = x;
    3   this.y = y;
    4   this.r =r;
    5 } 
    6 SubCircle.prototype = new Circle(0);

    这种继承方式,会有一点问题,就是原来在SubCircle的prototype中定义的方法和属性丢失。

    在prototype1.5 framework中,通过属性复制定义另一中继承方式。核心概念就是把Circle的prototype中的所有成员复制到SubCircle中,这样可以确保SubCircle的prototype中的成员不会丢失,但是这样也有个问题,就是Circle中定义的公有成员,特权成员并没有被继承到SubCircle中。

    为了解决这个问题,可以综合上面两种继承的有点,进行如下的实现:

    3. 多态

    首先看一下例子:

    ///////////define: Cricle//////////////////
    
    function Circle(r) {
        this.r = r;
    }
    Circle.PI = 3.14;
    Circle.prototype.PI = 3.14;
    Circle.prototype.area = function() { return Circle.PI*this.r*this.r; }
    Circle.prototype.area2 = function() { return this.PI*this.r*this.r; } 
     
    //// test
    c = new Circle(3);
    //alert("area1 :"+c.area());
    //alert("area2 :"+c.area2());
    
     
    
    Circle.max = function(a, b) { return a.r>b.r ? a.r : b.r; }
    //alert("max is "+Circle.max(new Circle(1), new Circle(3)));
    
    c1 = new Circle(1);
    c2 = new Circle(1);
    c2.PI = 100;//Circle.prototype.PI=100;
    
    //alert("c1.area1 "+c1.area());
    
    //alert("c1.area2 "+c1.area2());
    
    //alert("c2.area1 "+c2.area());
    
    //alert("c2.area2 "+c2.area2());
    
     
    
    ////////////////////////define: SubCircle //////////////////
    
    function SubCircle(x, y, r) {
        this.x = x;
        this.y = y;
        this.r = r;
    }
    
    SubCircle.prototype = new Circle(0);
    SubCircle.prototype.PI = 100;
    SubCircle.prototype.move2 = function(x, y) { this.x = x; this.y = y;}
    SubCircle.prototype.area = function() { return this.PI*this.r*this.r*this.r; } 
    
    //// test
    
    sc = new SubCircle(0,0,2);
    alert(sc.area());
    

     

    调用sc.area()的执行顺序是:

    sc.PI->sc.prototype.PI->100;

    sc.area()->sc.prototype.area()->SubCircle(0,0,2).area()

    从上面的调用顺序可以看到,SubCircle的调用过程中,只要属性和方法在子类中存在,就完全用子类的属性和方法。

    如果要使用父类的属性和方法怎么办呢?这就要用到继承部分的支持。在继承的时候,子类的prototype中增加了一个属性superClass,这个superClass就是父对象的引用。因此就可以通过superClass调用父对象的属性和方法了。

     

    命名空间

    随着功能的增加,JavaScript代码也会变的越来越复杂,那么给JavaScript增加命名空间就显得尤为重要了。

    JavaScript语言中没有对命名空间提供直接的支持,因此只有通过模拟实现命名空间。具体实现是使用闭包特性,使JavaScript代码位于某些特定的Function内。下面是命名空间的代码片段:

     1 //最外层的包
     2 
     3 var laputa = {};
     4  
     5 //在laputa包内部定义了两个包:lang,util
     6 
     7 laputa.lang={};
     8 laputa.util = {};
     9  
    10 
    11 //在lang包里定义了String类,
    12 
    13 laputa.lang.String = {}; 
    14 
    15 //String类的trim方法
    16 
    17 laputa.lang.String.prototype.trim = function(){
    18     return this.replace(/(^s*)|(s*$)/g, ""); 
    19 };
    20 
    21 //String类的toUpperCase方法
    22 
    23 laputa.lang.String.prototype.toUpperCase = function(){
    24     return this.toUpperCase();
    25 }
    26 
    27 //在util包定义了DocumentUtil类
    28 
    29 laputa.util.DocumentUtil = {}; 
    30 
    31 //DocumentUtil类的$方法
    32 
    33 laputa.util.DocumentUtil.$ = function(id){
    34     return document.getElementById(id);
    35 }
    36 
    37 //DocumentUtil类的$v方法
    38 
    39 laputa.util.DocumentUtil.$v = function(id){
    40     var el = laputa.util.DocumentUtil.$(id);
    41     if(el){
    42         return el.value;
    43     }
    44     return null;
    45 }

     

  • 相关阅读:
    Vue.directive()方法创建全局自定义指令
    vue中通过ref属性来获取dom的引用
    v-cloak指令
    v-if和v-show
    vue中的v-on事件监听机制
    vue指令v-model
    vue中v-for系统指令的使用
    从零开始在虚拟机中搭建一个4个节点的CentOS集群(一)-----下载及配置CentOS
    MySQL-数据库创建与删除
    MySQL-PREPARE语句
  • 原文地址:https://www.cnblogs.com/wolm/p/3307649.html
Copyright © 2011-2022 走看看