JavaScript实战-JavaScript、jQuery、HTML5、Node.js实例大全(第2版)
上QQ阅读APP看书,第一时间看更新

6.3 类和抽象

很多语言都有类和抽象,1967年挪威计算中心的Kisten Nygaard和Ole-Johan Dahl开发了Simula 67语言,它提供了比子程序更高一级的抽象和封装,引入了数据抽象和类的概念,被认为是第一个面向对象语言。

6.3.1 基于对象(Object-Based)和面向对象(Object-Oriented)

基于对象和面向对象是两个极易混淆的概念。基于对象(Object-Based)通常指的是对数据的封装,以及提供一组方法对封装过的数据操作,比如C的IO库中的FILE *就可以看成是基于对象的。面向对象(Object Oriented, OO),用纯粹的理论去理解就是必须具备封装、继承、多态三大特点,缺一不可。

程序=基于对象操作的算法+以对象为最小单位的数据结构。对代码的封装使得代码得以复用,减少了代码的体积,同样使问题简化。合理的数据结构使得数据量减少,或者简化数据操作算法。

Java是面向对象的程序设计语言,JavaScript很像Java但并不支持所有Java面向对象的功能,只能支持其中一部分。

在JavaScript里,你所知的所有东西几乎都是对象,但是它的语法里并没有class (类),所以我们只能把它理解为基于对象。VB(非VB.net)、Flash ActionScript 2.0和VBScript等都和JavaScript一样是基于对象的。

面向对象和基于对象之间的界限既清楚又模糊。说它清楚,是因为面向对象语言必须从语法上直接支持继承和动态绑定,而基于对象语言无法从语法上直接做到这一点。说它模糊,是因为基于对象的语言可以在没有语法直接支持的情况下,通过一些技巧达成与面向对象语言相同的效果。

6.3.2 用JavaScript创建一个类

JavaScript虽然不能直接从语法上使用class,但是能够模拟出“类”的效果。Stoyan Stefanov(Yahoo YSLOW项目的架构师)编写的Object-Oriented JavaScript这本书介绍了很多种面向对象的编程方法。在这里介绍一种最容易理解的构造函数法。

这是经典方法,在很多JavaScript代码中都可以见到。它用构造函数模拟“类”,在其内部用this关键字指代实例对象。

        function book(){
            this.name = "JavaScript 实例大全"
        }

生成实例的时候,使用new关键字。

        var mybook = new book();
        alert(mybook.name);                      //JavaScript 实例大全

如果不想每次都用new关键字,还可以这样改造构造函数。

        function book(){
            if (! (this instanceof book)) { return new book() };
            this.name = "JavaScript 实例大全"
        }

这时候,就可以省去等号后面的new关键字。instanceof操作符专门用来检测对象是否是某个类的实例,如果不是就自动返回一个new过的对象。

6.3.3 静态属性、方法和动态属性、方法

属性和方法是类最基本的组成,动态方法和属性必须要实例化后才能访问,而静态方法和属性无须实例化就可以使用,这是类的基础功能。

对于6.3.2小节中的代码,没有实例化book时,是无法访问到book.name这个属性的,也就是说this挂载的都是动态属性和方法。如何才能设置静态属性和方法呢?

        book.material = "纸质";
        book.getSize = function(type){
            switch(type){
                  case 16:
                          return "16K"
                  case 32:
                          return "32K"
                  default:
                          return "16K"
            }
        };

一般大家看的书都是纸质书,所以book.material就是一个静态属性,无须实例化。另外,book.getSize()是一个静态方法,书一般分16开或32开,通过传递16或32的值即可返回一个开数值,默认是16开。

说了静态成员,再介绍一下动态属性和方法的挂载方法,那就是通过prototype属性,这对于不想用this或不能用this的情况是一个很好的补充。

        book.prototype.pages = 460;
        book.prototype.randomInfo = function(){
            var l = ["JavaScript 基础应用"
                  ,"JavaScript 与HTML5 表单应用"
                  ,"JavaScript 与HTML5 高级应用"
                    ,"JavaScript 与jQuery 综合应用"
                    ,"JavaScript 与Node.js 综合应用"];
              return l[(Math.random()*l.length)>>0]
          };

调用时忽略prototype,直接是实例名.属性和实例名.方法,如mybook.pages和mybook.randomInfo()。书页和书的目录都是不同的,它们应该是动态变化的。

6.3.4 JavaScript继承

假如笔者写了很多书,但是笔者的基本信息是不变的,所以每本书的作者信息相同。用prototype也可以实现继承,【范例6-2】整合前面的代码后又加入了继承。

【范例6-2 JavaScript继承】

    1.      function z3fBook(){};                       //父类
    2.      z3fBook.prototype.author ={
    3.              name:’张三封’
    4.               , QQ:'10590986'
    5.               , web:'z3f.me'
    6.      }
    7.      function book(){
    8.               if (! (this instanceof book)) { return new book() };
    9.              this.name = "JavaScript 实例大全"
    10.     }
    11.    book.prototype = new z3fBook();           //继承父类信息
    12.     book.material = "纸质";
    13.     book.getSize = function(type){
    14.             switch(type){
    15.                      case 16:
    16.                              return "16K"
    17.                      case 32:
    18.                              return "32K"
    19.                      default:
    20.                            return "16K"
    21.             }
    22.     };
    23.     book.prototype.pages = 460;
    24.     book.prototype.randomInfo = function(){
    25.             var l = ["JavaScript 基础应用"
    26.                     , "JavaScript 与HTML5 表单应用"
    27.                     , "JavaScript 与HTML5 高级应用"
    28.                     , "JavaScript 与jQuery 综合应用"
    30.                     , "JavaScript 与Node.js 综合应用"];
    31.     return l[(Math.random()*l.length)>>0]
    32.     };
    33.     var yourBook = book();
    34.     alert(yourBook.author.name);       //张三封
    35.     alert(yourBook.author.QQ);         //10590986
    36.     alert(yourBook.pages);               //460

从以上代码可以看到,z3fBook()作为一个基类,保存了作者的基本信息。作者编写的每一本书book都应该继承该信息。在读者拿到每一本具体的书时就相当于yourBook实例化后的book对象,所以在输出yourBook.author.name这些只属于基类提供的信息时也能正确取值,表明继承成功。

6.3.5 私有属性和方法

只要不挂在this和prototype上的属性和方法都是私有的,把【范例6-2】中的book构造函数改造如下:

        function book(){
          if (! (this instanceof book)) { return new book() };
          var bookname = "JavaScript 实例大全"
          this.getName = function(){
                  return bookname;
          }
          this.setName = function(name){
              bookname = name;
          }
        }

如果不提供getName()和setName()方法,bookname就只能在内部用。外部要获取和操作只能通过公共的方法:

        alert(yourBook.getName());             //JavaScript 实例大全

6.3.6 抽象

抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃非本质的特征。例如,苹果、香蕉、梨、葡萄、桃子等,它们共同的特性就是水果,得出水果概念的过程就是一个抽象的过程。

要抽象,就必须进行比较,没有比较就无法找到在本质上共同的部分。共同特征是指那些能把一类事物与他类事物区分开的特征,这些具有区分作用的特征又称本质特征。因此抽取事物的共同特征就是抽取事物的本质特征,舍弃非本质的特征。所以抽象的过程也是一个裁剪的过程。

在抽象时,同与不同,决定于从什么角度来抽象。抽象的角度取决于分析问题的不同目的。

抽象是一种逻辑思维活动过程,正确的和具有实用性的概念应该是简洁和清晰的,否则它不但不能成为我们进行思维的利器,反而会成为思维的包袱。所以在抽象思维的时候应当了解各种基础概念,似是而非只会给程序留下漏洞。

抽象出来的特征通常可以分成3类:

● 相同

● 相似,如move、moveTo

● 相反,如open和close、show和hide