转载地址:http://blog.csdn.net/zj831007/article/details/6831127
前面章节中的模块都不是以面向对象形式写的,但是在Closure中许多模块是,尽管Javascript支持基于原型编程,Closure库以原型来实现类的编程。为了管理内存,用传统的复制对象来创建对象。
许多Javascript开发者都回避使用面向对象,他们认为本身的弱类型是很优秀的功能。另一些人完全认为面试对象是有缺陷的,认为即使java的面向对象也不是一个好的设计,引用Effective Java中的第14条说:“组合优于继承”,反对者也提到第15条:“对继承写详细文档或都根本就不使用它”。这两点关于继承的使用在Closure库都被认真设计。
不管你支持那一方,理解Closure中类是怎样使用的是很重要的。本章将讲解Closure中类的使用,通过本章,你将能用库的样式创建对象 ,也能更好的理解库原代码。
Closure中类的例子
创建类的第一步是声明一个构造函数。它能被new关键字用来创建对象实例,这个函数中包含this来引用新创建的实例。
第二步是对函数的原型添加属性。类的每个实例在它的原型链中都有一个隐藏的引用来引用构造函数的原型。这使用对于类的所有实例原型中的信息都可用。
Closue例子
这是一个名为House的例子,它包含一个房子信息:
- /**
- * @fileoverview House contains information about a house, such as its
- * address.
- * @author bolinfest@gmail.com (Michael Bolin)
- */
- goog.provide('example.House');
- /**
- * Creates a new House.
- * @param {string} address Address of this house.
- * @param {number=} numberOfBathrooms Defaults to 1.
- * @param {Array.<Object>=} itemsInTheGarage
- * @constructor
- */
- example.House = function(address, numberOfBathrooms, itemsInTheGarage) {
- /**
- * @type {string}
- * @private
- */
- this.address_ = address;
- if (goog.isDef(numberOfBathrooms)) {
- this.numberOfBathrooms_ = numberOfBathrooms;
- }
- /**
- * @type {Array.<Object>}
- * @protected
- */
- this.itemsInTheGarage = goog.isDef(itemsInTheGarage) ?
- itemsInTheGarage : [];
- };
- /**
- * @type {number}
- * @private
- */
- example.House.prototype.numberOfBathrooms_ = 1;
- /**
- * @type {boolean}
- * @private
- */
- example.House.prototype.needsPaintJob_ = false;
- /** @return {string} */
- example.House.prototype.getAddress = function() {
- return this.address_;
- };
- /** @return {number} */
- example.House.prototype.getNumberOfBathrooms = function() {
- return this.numberOfBathrooms_;
- };
- /** @return {boolean} */
- example.House.prototype.isNeedsPaintJob = function() {
- return this.needsPaintJob_;
- };
- /** @param {boolean} needsPaintJob */
- example.House.prototype.setNeedsPaintJob = function(needsPaintJob) {
- this.needsPaintJob_ = needsPaintJob;
- };
- /** @param {string} color */
- example.House.prototype.paint = function(color) {
- /* some implementation */
- };
- /** @return {number} */
- example.House.prototype.getNumberOfItemsInTheGarage = function() {
- return this.itemsInTheGarage.length;
- };
就像第3章所说,goog.provide('example.House')确保有一个全局对象并有House属性。如果goog.provide()已经创建了一个House对象,它会重新分配一个空的新对象。
在16行,example.House被分配为一个函数。@constructor注解标明它是一个构造函数,也就是说它能用于new关键字。当new 用在一个没有@constructor注解的函数时,编译器会发出警告。
就像其它基于类的编程一样,构造函数是用来初使化它有四个参数,对参数的赋值都是应用在新创建的实例上。
example.House有四个字段,每一个都以不同方式赋值,address通过构造函数参数,因此为每房子只有一个地址,它没有默认值,因此是必须的参数。
numberOfBathrooms是可选的,由于它有默认值1以example.House.prototype方式声明,因此它能被类的多个实例共享。
itemsInTheGarage如果传入时将设为此值,否则为[]空数组。
needsPaintJob不通过构造函数赋值,它能被共享。
构造函数必须第一个声明,其它方法和属性可以任意顺序。每一个方法中至少包含一个到this的引用,当指向类的实例,如果它不含this,它会被重写为静态方法。为了理解this在方法中是如何工作的,参考如下例子:
- var whiteHouse = new example.House('1600 Pennsylvania Avenue', 35);
- whiteHouse.setNeedsPaintJob(true);
- whiteHouse.setNeedsPaintJob.call(whiteHouse, true);
- function(needsPaintJob) {
- this.needsPaintJob_ = needsPaintJob;
- }
Java中等价例子
- package example;
- /**
- * House contains information about a house, such as its address.
- * @author bolinfest@gmail.com (Michael Bolin)
- */
- public class House {
- private final String address;
- private final int numberOfBathrooms;
- private boolean needsPaintJob;
- protected Object[] itemsInTheGarage;
- public House(String address) {
- this(address, 1);
- }
- public House(String address, int numberOfBathrooms) {
- this(address, numberOfBathrooms, new Object[0]);
- }
- public House(String address, int numberOfBathrooms,
- Object[] itemsInTheGarage) {
- this.address = address;
- this.numberOfBathrooms = numberOfBathrooms;
- this.needsPaintJob = false;
- this.itemsInTheGarage = itemsInTheGarage;
- }
- public String getAddress() {
- return address;
- }
- public int getNumberOfBathrooms() {
- return numberOfBathrooms;
- }
- public boolean isNeedsPaintJob() {
- return needsPaintJob;
- }
- public void setNeedsPaintJob(boolean needsPaintJob) {
- this.needsPaintJob = needsPaintJob;
- }
- public void paint(String color) { /* some implementation */ }
- public int getNumberOfItemsInTheGarage() {
- return itemsInTheGarage.length;
- }
- }
静态成员
静态成员是和类相关联,而不是和实例关联。如house.js例子所示:
- example.House.prototype.numberOfBathrooms_ = 1;
尽管 numberOfBathrooms_ 是一个字段,不同实例有不同的值,默认值是声明在原型中,可以不通过类实例更改。同样,实例方法也不是真正受限于类的实例。
- var obj = {};
- example.House.prototype.setNeedsPaintJob.call(obj, true);
- alert(obj.needsPaintJob_); // alerts true
- /**
- * This would be referred to as an instance method of House because it is
- * defined on House's prototype.
- * @param {example.House} house2
- * @return {number}
- */
- example.House.prototype.calculateDistance = function(house2) {
- return goog.math.Coordinate.distance(this.getLatLng(), house2.getLatLng());
- };
- /**
- * This would be referred to as a static method of House because it is
- * defined on House's constructor function.
- * @param {example.House} house1
- * @param {example.House} house2
- * @return {number}
- */
- example.House.calculateDistance = function(house1, house2) {
- return goog.math.Coordinate.distance(house1.getLatLng(), house2.getLatLng());
- };
单例模式
像第三章所示,goog.addSingletonGetter()能够创建一个返回单例的静态方法。它能直接通过构造函数调用:
- goog.provide('example.MonaLisa');
- /** @constructor */
- example.MonaLisa = function() {
- // Probably a really fancy implementation in here!
- };
- goog.addSingletonGetter(example.MonaLisa);
- // Now the constructor has a method named getInstance() defined on it that
- // will return the singleton instance.
- var monaLisa1 = example.MonaLisa.getInstance();
- var monaLisa2 = example.MonaLisa.getInstance();
- // alerts true because getInstance() will always return the same instance
- alert(monaLisa1 === monaLisa2);
但是请注意,goog.addSingletonGetter()不能阻止使用构造函数构建对象。它通过getInstance()返回对象来代替通过构造方法。
Closure子类示例
如果成员只对子类可见,可以用@protected标明,按惯例,私有成员在Closure中声明用下划线开关,受保护成员则不用。
Closure 示例
- /**
- * @fileoverview A Mansion is a larger House that includes a guest house.
- * @author bolinfest@gmail.com (Michael Bolin)
- */
- goog.provide('example.Mansion');
- goog.require('example.House');
- /**
- * @param {string} address Address of this mansion.
- * @param {example.House} guestHouse This mansion's guest house.
- * @param {number=} numberOfBathrooms Number of bathrooms in this mansion.
- * Defaults to 10.
- * @constructor
- * @extends {example.House}
- */
- example.Mansion = function(address, guestHouse, numberOfBathrooms) {
- if (!goog.isDef(numberOfBathrooms)) {
- numberOfBathrooms = 10;
- }
- example.House.call(this, address, numberOfBathrooms);
- /**
- * @type {example.House}
- * @private
- */
- this.guestHouse_ = guestHouse;
- };
- goog.inherits(example.Mansion, example.House);
- /**
- * Donates all of the items in the garage.
- * @param {example.Goodwill} Goodwill Organization who receives the donations.
- */
- example.Mansion.prototype.giveItemsToGoodwill = function(goodwill) {
- // Can access the itemsInTheGarage field directly because it is protected.
- // If it were private, then the superclass would have to expose a public or
- // protected getter method for itemsInTheGarage.
- var items = this.itemsInTheGarage;
- for (var i = 0; i < items.length; i++) {
- goodwill.acceptDonation(items[i]);
- }
- this.itemsInTheGarage = [];
- };
- /** @inheritDoc */
- example.Mansion.prototype.paint = function(color) {
- example.Mansion.superClass_.paint.call(this, color);
- this.getGuestHouse_.paint(color);
- };
1,Jsdoc注释有@extends注解,这对编译器很有帮助。对于类型检查也很有用,如一个接受example.House的方法也接受example.Mansion。
2,方法中应改显示调用父类构造函数:
- example.House.call(this, address, numberOfBathrooms);
- goog.inherits = function(childCtor, parentCtor) {
- /** @constructor */
- function tempCtor() {};
- tempCtor.prototype = parentCtor.prototype;
- childCtor.superClass_ = parentCtor.prototype;
- childCtor.prototype = new tempCtor();
- childCtor.prototype.constructor = childCtor;
- };
在子类中声明字段
在字类和父类中字段是共享的,因此要确保不要重名
- goog.provide('example.Record');
- goog.provide('example.BankRecord');
- goog.require('goog.string');
- /** @constructor */
- example.Record = function() {
- /**
- * A unique id for this record
- * @type {string}
- * @private
- */
- this.id_ = goog.string.createUniqueString();
- };
- /** @return {string} */
- example.Record.prototype.getId = function() {
- return this.id_;
- };
- /**
- * @param {number} id
- * @constructor
- * @extends {example.Record}
- */
- example.BankRecord = function(id) {
- example.Record.call(this);
- // THIS IS A PROBLEM BECAUSE IT COLLIDES WITH THE SUPERCLASS FIELD
- /**
- * A unique id for this record
- * @type {number}
- * @private
- */
- this.id_ = id;
- };
- goog.inherits(example.BankRecord, example.Record);
- /** @return {number} */
- example.BankRecord.prototype.getBankRecordId = function() {
- return this.id_;
- };
@override和@inheritDoc
@overide用于子类覆盖父类时:
- /** @return {example.House} */
- example.Person.prototype.getDwelling = function() { /* ... */ };
- /**
- * In this example, RichPerson is a subclass of Person.
- * @return {example.Mansion}
- * @override
- */
- example.RichPerson.prototype.getDwelling = function() { /* ... */ };
- /** @return {string} A description of this object. */
- Object.prototype.toString = function() { /* ... */ };
- /**
- * @return {string} The format of the string will be "(x,y)".
- * @override
- */
- Point.prototype.toString = function() { /* ... */ };
使用goog.base()简单调用父类
在example.Mansion调用父类很麻烦,可用如下方法:
- /**
- * @param {string} address Address of this mansion.
- * @param {example.House} guestHouse This mansion's guest house.
- * @param {number=} numberOfBathrooms Number of bathrooms in this mansion.
- * Defaults to 10.
- * @constructor
- * @extends {example.House}
- */
- example.Mansion = function(address, guestHouse, numberOfBathrooms) {
- if (!goog.isDef(numberOfBathrooms)) {
- numberOfBathrooms = 10;
- }
- goog.base(this, address, numberOfBathrooms);
- /**
- * @type {example.House}
- * @private
- */
- this.guestHouse_ = guestHouse;
- };
- goog.inherits(example.Mansion, example.House);
- /** @inheritDoc */
- example.Mansion.prototype.paint = function(color) {
- goog.base(this, 'paint', color);
- this.guestHouse_.paint(color);
- };
要声明一个只用于子类覆盖的方法,将其值声明为:goog.abstractMethod:
- goog.provide('example.SomeClass');
- /** @constructor */
- example.SomeClass = function() {};
- /**
- * The JSDoc comment should explain the expected behavior of this method so
- * that subclasses know how to implement it appropriately.
- */
- example.SomeClass.prototype.methodToOverride = goog.abstractMethod;
如果子类不覆盖此方法将会在运行时方法调用时报错:
- goog.provide('example.SubClass');
- /**
- * @constructor
- * @extends {example.SomeClass}
- */
- example.SubClass = function() {
- goog.base(this);
- };
- goog.inherits(example.SubClass, example.SomeClass);
- var subClass = new example.SubClass();
- // There is no error from the Compiler saying this is an abstract/unimplemented
- // method, but executing this code will yield a runtime error thrown by
- // goog.abstractMethod.
- subClass.methodToOverride();
到写本书时为止,编译器不会在编译时发出警告。
接口
声明接口很简单:
- goog.provide('example.Shape');
- /**
- * An interface that represents a two-dimensional shape.
- * @interface
- */
- example.Shape = function() {};
- /**
- * @return {number} the area of this shape
- */
- example.Shape.prototype.getArea = function() {};
要实现一个接口:
- goog.provide('example.Circle');
- /**
- * @param {number} radius
- * @constructor
- * @implements {example.Shape}
- */
- example.Circle = function(radius) {
- this.radius = radius;
- };
- /** @inheritDoc */
- example.Circle.prototype.getArea = function() {
- return Math.PI * this.radius * this.radius;
- };
对于方法参数为example.Shape时可用example.Circle:
- goog.provide('example.ShapeUtil');
- /** @param {!example.Shape} shape */
- example.ShapeUtil.printArea = function(shape) {
- document.write('The area of the shape is: ' + shape.getArea());
- };
- // This line is type checked successfully by the Closure Compiler.
- example.ShapeUtil.printArea(new example.Circle(1));
多继承
Closure中不支持多继承,但开发者可以有方案实现它,可借助于goo.mixin():
- goog.provide('example.Phone');
- goog.provide('example.Mp3Player');
- goog.provide('example.AndroidPhone');
- /** @constructor */
- example.Phone = function(phoneNumber) { /* ... */ };
- example.Phone.prototype.makePhoneCall = function(phoneNumber) { /* ... */ };
- /** @constructor */
- example.Mp3Player = function(storageSize) { /* ... */ };
- example.Mp3Player.prototype.playSong = function(fileName) {
- var mp3 = this.loadMp3FromFile(fileName);
- mp3.play();
- return mp3;
- };
- /**
- * @constructor
- * @extends {example.Phone}
- */
- example.AndroidPhone = function(phoneNumber, storageSize) {
- example.Phone.call(this, phoneNumber);
- example.Mp3Player.call(this, storageSize);
- };
- goog.inherits(example.AndroidPhone, example.Phone);
- goog.mixin(example.AndroidPhone.prototype, example.Mp3Player.prototype);
枚举
枚举用@enum标注:
- /** @enum {number} */
- example.CardinalDirection = {
- NORTH: Math.PI / 2,
- SOUTH: 3 * Math.PI / 2,
- EAST: 0,
- WEST: Math.PI
- };
- /**
- * @param {example.CardinalDirection} direction
- * @return {example.CardinalDirection}
- */
- example.CardinalDirection.rotateLeft90Degrees = function(direction) {
- return (direction + (Math.PI / 2)) % (2 * Math.PI);
- };
- /** @type {Array.<example.CardinalDirection>} */
- example.CardinalDirection.values = [
- example.CardinalDirection.NORTH,
- example.CardinalDirection.SOUTH,
- example.CardinalDirection.EAST,
- example.CardinalDirection.WEST
- ];
goog.Disposable
如果一个对象不再被使用,它要求显示被释放内存,它需要继承goog.Disposable,它含有两个方法:
- goog.Disposable.prototype.dispose = function() {
- if (!this.disposed_) {
- this.disposed_ = true;
- this.disposeInternal();
- }
- };
- goog.Disposable.prototype.disposeInternal = function() {
- // No-op in the base class.
- };
覆盖disposeInternal()方法:
覆盖disposeInternal()要完成5个工作:
1,调用父类disposeInternal方法
2,释放类中引用的所有对象
3,移除类的监听器
4,移除类的DOM节点
5,移除类的COM对象
- goog.provide('example.AutoSave');
- goog.require('goog.Disposable');
- /**
- * @param {!Element} container into which the UI will be rendered.
- * @param {!goog.ui.Button} saveButton Button to disable while saving.
- * @constructor
- * @extends {goog.Disposable}
- */
- example.AutoSave = function(container, saveButton) {
- // Currently, goog.Disposable is an empty function, so it may be tempting
- // to omit this call; however, the Closure Compiler will remove this line
- // for you when Advanced Optimizations are enabled. It is better to
- // leave this call around in case the implementation of goog.Disposable()
- // changes.
- goog.Disposable.call(this);
- /**
- * @type {!Element}
- */
- this.container = container;
- /**
- * @type {function(Event)}
- */
- this.eventListener = goog.bind(this.onMouseOver, this);
- // Although this usage follows the standard set by the W3C Event model,
- // this is not the recommended way to manage events in the Closure Library.
- // The correct way to add event listeners is explained in the next chapter.
- container.addEventListener('mouseover', this.eventListener, false);
- /**
- * @type {!goog.ui.Button}
- */
- this.saveButton = saveButton;
- };
- goog.inherits(example.AutoSave, goog.Disposable);
- /** @type {XMLHttpRequest} */
- example.AutoSave.prototype.xhr;
- /** @type {Element} */
- example.AutoSave.prototype.label;
- /**
- * Dialog to display if auto-saving fails; lazily created after the first
- * failure.
- * @type {goog.ui.Dialog}
- */
- example.AutoSave.prototype.failureDialog;
- example.AutoSave.prototype.render = function() {
- this.container.innerHTML = '<span style="display:none">Saving...</span>';
- this.label = this.container.firstChild;
- };
- /** @param {Event} e */
- example.AutoSave.prototype.onMouseOver = function(e) { /* ... */ };
- /** @inheritDoc */
- example.AutoSave.prototype.disposeInternal = function() {
- // (1) Call the superclass's disposeInternal() method.
- example.AutoSave.superClass_.disposeInternal.call(this);
- // (2) Dispose of all Disposable objects owned by this class.
- goog.dispose(this.failureDialog);
- // (3) Remove listeners added by this class.
- this.container.removeEventListener('mouseover', this.eventListener, false);
- // (4) Remove references to COM objects.
- this.xhr = null;
- // (5) Remove references to DOM nodes, which are COM objects in IE.
- delete this.container;
- this.label = null;
- };