JavaScript面向对象编程

开发 开发工具
在这篇教程中,你将学习基于JavaScript的面向对象编程。其中的代码示例是基于EcmaScript 5(JavaScript的标准定义)来实现。

随着HTML5标准的成熟和在移动开发领域的大规模使用,JavaScript正成为Web开发领域最热门的开发语言,而且随着NodeJS等技术的发展,JavaScript的应用也从传统前端开发领域延伸到了服务器端开发。但同时需要注意的是,我们项目中的JavaScript代码规模也变得越来越大和更加复杂。这就要求开发人员能够编写高效且可维护的JavaScript代码,虽然JavaScript不像Java那样对面向对象设计有那么好的支持,但我们可以通过在JavaScript中应用这些面向对象的设计模式,来使我们写出更优秀的JavaScript代码。

在这篇教程中,你将学习基于JavaScript的面向对象编程。其中的代码示例是基于EcmaScript 5(JavaScript的标准定义)来实现。

Java与JavaScript的比对

对象类型定义- Object Type

  1. function MyType(){    if (!(this instanceof MyType))        throw new Error("Constructor can’t be called as a function"); 
  2. }var myInstance = new MyType(); 
  3. MyType(); // Error: Constructor can’t be called as a function 

在Eclipse的JavaScript视图中,构造器,实例成员,静态成员和内部函数都能被识别,并在Outline视图中显示出来。

实例成员 - Instance Members

通过"new"关键字可以创建一个实例对象,而实例成员(变量或方法)能够通过这个实例对象来访问。实例成员可以通过"this"关键字,原型(prototype),构造器或Object.defineProperty来定义。

  1. function Cat(name){    var voice = "Meow";    this.name = name;    this.say = function(){      return voice; 
  2.     } 
  3. Cat.prototype.eat = function(){    return "Eating"
  4. }var cat = new Cat("Fluffy");Object.defineProperty(cat, "numLegs",{value: 4,writable:true,enumerable:true,configurable:tr 
  5. ue});console.log(cat.name); // Fluffyconsole.log(cat.numLegs); // 4console.log(cat.say()); // Meowconsole.log(cat.eat()); // Eating 

静态成员 - Static Members

JavaScript中并不直接支持静态成员。你可以通过构造器来创建静态成员。静态成员不允许通过"this"关键字直接访问。

公共静态成员

  1. function Factory(){ 
  2. }// public static methodFactory.getType = function (){    return "Object Factory"
  3. };// public static fieldFactory.versionId = "F2.0"
  4. Factory.prototype.test = function(){    console.log(this.versionId); // undefined 
  5.     console.log(Factory.versionId); // F2.0 
  6.     console.log(Factory.getType()); // Object Factory}var factory = new Factory(); 
  7. factory.test(); 

私有静态成员

  1. var Book = (function () {    // private static field 
  2.     var numOfBooks = 0;    // private static method 
  3.     function checkIsbn(isbn) {        if (isbn.length != 10 && isbn.length != 13)            throw new Error("isbn is not valid!"); 
  4.     }    function Book(isbn, title) { 
  5.         checkIsbn(isbn);        this.isbn = isbn;        this.title = title; 
  6.         numOfBooks++;        this.getNumOfBooks = function () {            return numOfBooks; 
  7.         } 
  8.     }    return Book; 
  9. })();var firstBook = new Book("0-943396-04-2""First Title");console.log(firstBook.title); // First Titleconsole.log(firstBook.getNumOfBooks()); // 1var secondBook = new Book("0-85131-041-9""Second Title");console.log(firstBook.title); // First Titleconsole.log(secondBook.title); // Second Titleconsole.log(firstBook.getNumOfBooks()); // 2console.log(secondBook.getNumOfBooks()); // 2 

抽象类型 - Abstract Types

JavaScript是一个弱类型语言,所以当你声明一个变量时,不需要指定它的类型。这就减弱了对于像接口这样的抽象类型的依赖。但有时候,你仍然希望使用抽象类型来将一些共有的功能放在一起,并采用继承的机制,让其他类型也具有相同的功能,你可以参考下面的示例:

  1. (function(){    var abstractCreateLock = false;    // abstract type 
  2.     function BaseForm(){        if(abstractCreateLock)            throw new Error("Can’t instantiate BaseForm!"); 
  3.     } 
  4.  
  5.     BaseForm.prototype = {}; 
  6.     BaseForm.prototype.post = function(){        throw new Error("Not implemented!"); 
  7.     }    function GridForm(){ 
  8.     } 
  9.  
  10.     GridForm.prototype = new BaseForm(); 
  11.     abstractCreateLock = true
  12.     GridForm.prototype.post = function(){        // ... 
  13.         return "Grid is posted."
  14.     }    window.BaseForm = BaseForm;    window.GridForm = GridForm; 
  15. })();var myGrid = new GridForm();console.log(myGrid.post()); // Grid is posted.var myForm = new BaseForm(); // Error: Can’t instantiate BaseForm! 

接口 - Interfaces

JavaScript同样没有对接口的直接支持。你可以通过下面代码中实现的机制来定义接口。

  1. var Interface = function (name, methods) {    this.name = name;    // copies array 
  2.     this.methods = methods.slice(0); 
  3. }; 
  4.  
  5. Interface.checkImplements = function (obj, interfaceObj) {    for (var i = 0; i < interfaceObj.methods.length; i++) {        var method = interfaceObj.methods[i];        if (!obj[method] || typeof obj[method] !=="function"
  6.             thrownewError("Interfacenotimplemented! Interface: " + interfaceObj.name + " Method: " + method); 
  7.     } 
  8. };var iMaterial = new Interface("IMaterial", ["getName""getPrice"]);function Product(name,price,type){ 
  9.     Interface.checkImplements(this, iMaterial);    this.name = name;    this.price = price;    this.type = type; 
  10.  
  11. Product.prototype.getName = function(){    return this.name
  12. }; 
  13. Product.prototype.getPrice = function(){    return this.price; 
  14. };var firstCar = new Product("Super Car X11",20000,"Car");console.log(firstCar.getName()); // Super Car X11delete Product.prototype.getPrice;var secondCar = new Product("Super Car X12",30000,"Car"); // Error: Interface not implemented! 

单例对象 - Singleton Object

如果你希望在全局范围内只创建一个某一类型的示例,那么你可以有下面两种方式来实现一个单例。

  1. var Logger = { 
  2.     enabled:true
  3.     log: function(logText){      if(!this.enabled)        return;      if(console && console.log)        console.log(logText);      else 
  4.         alert(logText); 
  5.     } 
  6. 或者 
  7. function Logger(){ 
  8. Logger.enabled = true
  9. Logger.log = function(logText){    if(!Logger.enabled)        return;    if(console && console.log)        console.log(logText);    else 
  10.         alert(logText); 
  11. }; 
  12. Logger.log("test"); // testLogger.enabled = false
  13. Logger.log("test"); // 

创建对象 - Object Creation

通过new关键字创建

可以使用"new"关键字来创建内置类型或用户自定义类型的实例对象,它会先创建一个空的实例对象,然后再调用构造函数来给这个对象的成员变量赋值,从而实现对象的初始化。

  1. //or var dog = {};//or var dog = new MyDogType();var dog = new Object(); 
  2. dog.name = "Scooby"
  3. dog.owner = {}; 
  4. dog.owner.name = "Mike"
  5. dog.bark = function(){   return "Woof"
  6. };console.log(dog.name); // Scoobyconsole.log(dog.owner.name); // Mikeconsole.log(dog.bark()); // Woof 

通过字面量直接创建

通过字面量创建对象非常简单和直接,同时你还可以创建嵌套对象。

  1. var dog = { 
  2.   name:"Scoobyî"
  3.   owner:{ 
  4.     name:"Mike" 
  5.   }, 
  6.   bark:function(){    return "Woof"
  7.   } 
  8. };console.log(dog.name); // Scoobyconsole.log(dog.owner.name); // Mikeconsole.log(dog.bark()); // Woof 

成员作用域 - Scoping

私有字段 - Private Fields

在JavaScript中没有对私有字段的直接支持,但你可以通过构造器来实现它。首先将变量在构造函数中定义为私有的,任何需要使用到这个私有字段的方法都需要定义在构造函数中,这样你就可以通过这些共有方法来访问这个私有变量了。

  1. function Customer(){  // private field 
  2.   var risk = 0;  this.getRisk = function(){    return risk; 
  3.   };  this.setRisk = function(newRisk){ 
  4.     risk = newRisk; 
  5.   };  this.checkRisk = function(){    if(risk > 1000)      return "Risk Warning";    return "No Risk"
  6.   }; 
  7.  
  8. Customer.prototype.addOrder = function(orderAmount){  this.setRisk(orderAmount + this.getRisk());  return this.getRisk(); 
  9. };var customer = new Customer();console.log(customer.getRisk()); // 0console.log(customer.addOrder(2000)); // 2000console.log(customer.checkRisk()); // Risk Warning 

私有方法 - Private Methods

私有方法也被称作内部函数,往往被定义在构造体中,从外部无法直接访问它们。

  1. function Customer(name){  var that = this;  var risk = 0;  this.name = name;  this.type = findType();  // private method 
  2.   function findType() {     console.log(that.name);     console.log(risk);     return "GOLD"
  3.    } 

或者

  1. function Customer(name){  var that = this;  var risk = 0;  this.name = name;  // private method 
  2.   var findType = function() {     console.log(that.name);     console.log(risk);     return "GOLD"
  3.   };  this.type = findType(); 
  4. }var customer = new Customer("ABC Customer"); // ABC Customer 
  5.  // 0console.log(customer.type); // GOLDconsole.log(customer.risk); // undefined 

如果私有内部函数被实例化并被构造函数返回,那么它将可以从外部被调用。

  1. function Outer(){  return new Inner();  //private inner 
  2.   function Inner(){     this.sayHello = function(){        console.log("Hello"); 
  3.      } 
  4.    } 
  5. (new Outer()).sayHello(); // Hello 

特权方法 - Privileged Methods

原型方法中的一切都必须是公共的,因此它无法调用类型定义中的私有变量。通过在构造函数中使用"this."声明的函数称为特权方法,它们能够访问私有字段,并且可以从外部调用。

  1. function Customer(orderAmount){  // private field 
  2.   var cost = orderAmount / 2;  this.orderAmount = orderAmount;  var that = this;  // privileged method 
  3.   this.calculateProfit = function(){    return that.orderAmount - cost; 
  4.   }; 
  5.  
  6. Customer.prototype.report = function(){  console.log(this.calculateProfit()); 
  7. };var customer = new Customer(3000); 
  8. customer.report(); // 1500 

公共字段 - Public Fields

公共字段能够被原型或实例对象访问。原型字段和方法被所有实例对象共享(原型对象本身也是被共享的)。当实例对象改变它的某一个字段的值时,并不会改变其他对象中该字段的值,只有直接使用原型对象修改字段,才会影响到所有实例对象中该字段的值。

  1. function Customer(name,orderAmount){  // public fields 
  2.   this.name = name;  this.orderAmount = orderAmount; 
  3.  
  4. Customer.prototype.type = "NORMAL"
  5. Customer.prototype.report = function(){  console.log(this.name);  console.log(this.orderAmount);  console.log(this.type);  console.log(this.country); 
  6. }; 
  7.  
  8. Customer.prototype.promoteType = function(){  this.type = "SILVER"
  9. };var customer1 = new Customer("Customer 1",10);// public fieldcustomer1.country = "A Country"
  10. customer1.report(); // Customer 1 
  11.                      // 10 
  12.                      // NORMAL 
  13.                      // A Countryvar customer2 = new Customer("Customer 2",20); 
  14. customer2.promoteType();console.log(customer2.type); // SILVERconsole.log(customer1.type); // NORMAL 

公共方法 - Public Methods

原型方法是公共的,所有与之关联的对象或方法也都是公共的。

  1. function Customer(){  // public method 
  2.   this.shipOrder = function(shipAmount){     return shipAmount; 
  3.   }; 
  4. }// public methodCustomer.prototype.addOrder = function (orderAmount) {    var totalOrder = 0;    for(var i = 0; i < arguments.length; i++) { 
  5.       totalOrder += arguments[i]; 
  6.     }    return totalOrder; 
  7.   };var customer = new Customer();// public methodcustomer.findType = function(){   return "NORMAL"
  8. };console.log(customer.addOrder(25,75)); // 100console.log(customer.shipOrder(50)); // 50console.log(customer.findType()); // NORMAL 

继承 - Inheritance

有几种方法可以在JavaScript中实现继承。其中"原型继承"——使用原型机制实现继承的方法,是最常用的。如下面示例:

  1. function Parent(){  var parentPrivate = "parent private data";  var that = this;  this.parentMethodForPrivate = function(){     return parentPrivate; 
  2.   };  console.log("parent"); 
  3.  
  4. Parent.prototype = { 
  5.   parentData: "parent data"
  6.   parentMethod: function(arg){    return "parent method"
  7.   }, 
  8.   overrideMethod: function(arg){    return arg + " overriden parent method"
  9.   } 
  10. }function Child(){  // super constructor is not called, we have to invoke it 
  11.   Parent.call(this);  console.log(this.parentData);  var that = this;  this.parentPrivate = function(){     return that.parentMethodForPrivate(); 
  12.   };  console.log("child"); 
  13. }//inheritanceChild.prototype = new Parent();// parentChild.prototype.constructor = Child;//lets add extented functionsChild.prototype.extensionMethod = function(){  return "child’s " + this.parentData; 
  14. };//override inherited functionsChild.prototype.overrideMethod = function(){  //parent’s method is called 
  15.   return "Invoking from child" + Parent.prototype. 
  16.   overrideMethod.call(this, " test"); 
  17. };var child = new Child();// parent// parent data 
  18.  // childconsole.log(child.extensionMethod()); //child’s parent dataconsole.log(child.parentData); //parent dataconsole.log(child.parentMethod()); //parent methodconsole.log(child.overrideMethod()); //Invoking from child testoverriden parent methodconsole.log(child.parentPrivate()); // parent private dataconsole.log(child instanceof Parent); //trueconsole.log(child instanceof Child); //true 

当一个成员字段或函数被访问时,会首先搜索这个对象自身的成员。如果没有找到,那么会搜索这个对象对应的原型对象。如果在原型对象中仍然没有找到,那么会在它的父对象中查找成员和原型。这个继承关系也被成为 "原型链"。下面这张图就反映了原型链的继承关系。

模块化 - Modularization

当我们的项目中,自定义的对象类型越来越多时,我们需要更有效地组织和管理这些类定义,并控制他们的可见性,相互依赖关系以及加载顺序。"命名空间"和"模块"能够帮助我们很好地解决这个问题。(EcmaScript 6已经实现了模块系统,但因它还没有被所有浏览器实现,此处我们仍以ES5为例来进行说明)

命名空间 - Namespaces

JavaScript中并没有命名空间的概念。我们需要通过对象来创建命名空间,并将我们定义的对象类型放入其中。

  1. //create namespacevar myLib = {}; 
  2. myLib.myPackage = {};//Register types to namespacemyLib.myPackage.MyType1 = MyType1; 
  3. myLib.myPackage.MyType2 = MyType2; 

模块 - Modules

模块被用来将我们的JavaScript代码分解到包中。模块可以引用其他模块或将自己定义的对象类型对外暴露,以供其他模块使用。同时它能够用来管理模块间的依赖关系,并按照我们指定的顺序进行加载。目前有一些第三方库可以用来实现模块的管理。

下面的例子中,我们在模块里定义新的类型,并且引用其他模块并将自身的公共类型对外暴露。

  1. Module.define("module1.js"
  2.                ["dependent_module1.js","dependent_module2.js",...],               function(dependentMod1, dependentMod2) {//IMPORTS 
  3.  
  4.   //TYPE DEFINITIONS 
  5.   function ExportedType1(){    // use of dependent module’s types 
  6.     var dependentType = new dependentMod1.DependentType1(); 
  7.     ... 
  8.   }  function ExportedType2(){ 
  9.   } 
  10.  
  11.   ...  // EXPORTS 
  12.   return { ExportedType1: ExportedType1, ExportedType2:ExportedType2,...}; 
  13. });//To use a module (can work asynchronously or synchronously):Module.use(["module1.js"], function(aModule){ 
  14.   console.log("Loaded aModule!");  var AType = aModule.AnExportedType;  var atype1Instance = new AType(); 
  15. }); 

自定义异常 - Custom Exceptions

JavaScript中有一些内部定义的异常,如Error、TypeError和SyntaxError。它们会在运行时被创建和抛出。所有的异常都是"unchecked"。一个普通的对象也可以被用作一个异常,并在throw语句中抛出。因此,我们可以创建自己定义的异常对象,并且在程序中捕获它们进行处理。一个异常处理的***实践是,扩展JavaScript中标准的Error对象。

  1. function BaseException() {} 
  2. BaseException.prototype = new Error(); 
  3. BaseException.prototype.constructor = BaseException; 
  4. BaseException.prototype.toString = function() {  // note that name and message are properties of Error 
  5.   return this.name + ":"+this.message; 
  6. };function NegativeNumberException(value) {  this.name = "NegativeNumberException";  this.message = "Negative number!Value: "+value; 
  7. NegativeNumberException.prototype = new BaseException(); 
  8. NegativeNumberException.prototype.constructor = NegativeNumberException;function EmptyInputException() {  this.name = "EmptyInputException";  this.message = "Empty input!"
  9. EmptyInputException.prototype = new BaseException(); 
  10. EmptyInputException.prototype.constructor = EmptyInputException;var InputValidator = (function() {  var InputValidator = {}; 
  11.   InputValidator.validate = function(data) {    var validations = [validateNotNegative, validateNotEmpty];    for (var i = 0; i < validations.length; i++) {      try { 
  12.         validations[i](data); 
  13.       } catch (e) {        if (e instanceof NegativeNumberException) {          //re-throw 
  14.           throw e; 
  15.         } else if (e instanceof EmptyInputException) {          // tolerate it 
  16.           data = "0"
  17.         } 
  18.       } 
  19.     } 
  20.   };  return InputValidator;  function validateNotNegative(data) {    if (data < 0)      throw new NegativeNumberException(data) 
  21.   }  function validateNotEmpty(data) {    if (data == "" || data.trim() == "")      throw new EmptyInputException(); 
  22.   } 
  23. })();try { 
  24.   InputValidator.validate("-1"); 
  25. } catch (e) {  console.log(e.toString()); // NegativeNumberException:Negative number!Value: -1 
  26.   console.log("Validation is done."); // Validation is done.} 

自定义事件 - Custom Events

自定义事件能够帮助我们减小代码的复杂度,并且有效地进行对象之间的解耦。下面是一个典型的自定义事件应用模式:

  1. function EventManager() {}var listeners = {}; 
  2.  
  3. EventManager.fireEvent = function(eventName, eventProperties) {  if (!listeners[eventName])    return;  for (var i = 0; i < listeners[eventName].length; i++) { 
  4.     listeners[eventName][i](eventProperties); 
  5.   } 
  6. }; 
  7.  
  8. EventManager.addListener = function(eventName, callback) {  if (!listeners[eventName]) 
  9.     listeners[eventName] = []; 
  10.   listeners[eventName].push(callback); 
  11. }; 
  12.  
  13. EventManager.removeListener = function(eventName, callback) {  if (!listeners[eventName])    return;  for (var i = 0; i < listeners[eventName].length; i++) {    if (listeners[eventName][i] == callback) {      delete listeners[eventName][i];      return
  14.     } 
  15.   } 
  16. }; 
  17.  
  18. EventManager.addListener("popupSelected"function(props) {  console.log("Invoked popupSelected event: "+props.itemID); 
  19. }); 
  20. EventManager.fireEvent("popupSelected", { 
  21.   itemID: "100"}); //Invoked popupSelected event: 100 

 【本文是51CTO专栏作者“陈逸鹤”的原创文章,如需转载请联系作者本人(微信公众号:techmask)】

戳这里,看该作者更多好文

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2012-01-17 09:34:52

JavaScript

2012-02-27 09:30:22

JavaScript

2011-05-25 10:59:26

Javascript继承

2011-05-25 10:21:44

Javascript

2011-06-28 14:11:33

JavaScript

2010-10-08 09:13:15

oop模式JavaScript

2011-05-25 11:15:02

Javascript继承

2023-02-22 18:06:35

函数javascript面向对象编程

2019-11-18 17:05:02

JavaScript面向对象程序编程Java

2010-11-17 11:31:22

Scala基础面向对象Scala

2021-10-21 18:47:37

JavaScript面向对象

2022-07-30 23:41:53

面向过程面向对象面向协议编程

2012-12-13 11:01:42

IBMdW

2011-05-13 09:58:46

javascript

2011-05-13 10:51:25

javascript

2011-05-13 11:27:59

javascript

2011-05-13 11:17:18

javascript

2011-05-13 12:38:58

javascript

2011-05-13 11:05:52

javascript

2013-06-07 10:55:10

点赞
收藏

51CTO技术栈公众号