Box 类继承 Shape 类。
Cube 类继承Box 类
/**
* Helper function that implements (pseudo)Classical inheritance inheritance.
* @see http://www.yuiblog.com/blog/2010/01/06/inheritance-patterns-in-yui-3/
* @param {Function} childClass
* @param {Function} parentClass
*/
function inherits(childClass, parentClass) {
/** @constructor */
var tempClass = function() {
};
tempClass.prototype = parentClass.prototype;
childClass.prototype = new tempClass();
childClass.prototype.constructor = childClass;
}
//////////////////////////////////////////////////////////////////////////////
/**
* The shape
* @constructor
*/
function Shape() {
// No implementation.
}
/**
* Get the size
* @return {number} The size.
*/
Shape.prototype.getSize = function() {
// No implementation.
};
//////////////////////////////////////////////////////////////////////////////
/**
* The Box.
* @param {number} width The width.
* @param {number} height The height.
* @constructor
* @extends {Shape}
*/
function Box(width, height) {
Shape.call(this);
/**
* @private
* @type {number}
*/
this.width_ = width;
/**
* @private
* @type {number}
*/
this.height_ = height;
}
inherits(Box, Shape);
/**
* @return {number} The width.
*/
Box.prototype.getWidth = function() {
return this.width_;
};
/**
* @return {number} The height.
*/
Box.prototype.getHeight = function() {
return this.height_;
};
/** @inheritDoc */
Box.prototype.getSize = function() {
return this.height_ * this.width_;
};
////////////////////////////////////////////////////////////////////////////
/**
* The Box.
* @param {number} width The width.
* @param {number} height The height.
* @param {number} depth The depth.
* @constructor
* @extends {Box}
*/
function Cube(width, height, depth) {
Box.call(this, width, height);
/**
* @private
* @type {number}
*/
this.depth_ = depth;
}
inherits(Cube, Box);
/**
* @return {number} The width.
*/
Cube.prototype.getDepth = function() {
return this.depth_;
};
/** @inheritDoc */
Cube.prototype.getSize = function() {
return this.depth_ * this.getHeight() * this.getWidth();
};
////////////////////////////////////////////////////////////////////////////
var cube = new Cube(3, 6, 9);
document.write(cube.getSize().toString());
上面的 JavaScript 代码有些长,但是源代码的大小会被简单的看作是输入的字符数。
那些文档中描述编码的注释、变量的名称、方法的名称会被编译器重新命名或移除掉。
3层级的类继承树会被看作是简单的函数,编译器会进行优化。
下面是编译后的代码:
function d(a, b) {
function c() {
}
c.prototype = b.prototype;
a.prototype = new c
}
function e() {
}
e.prototype.a = function() {
};
function f(a, b) {
this.c = a;
this.b = b
}
d(f, e);
f.prototype.a = function() {
return this.b * this.c
};
function g(a, b, c) {
f.call(this, a, b);
this.d = c
}
d(g, f);
g.prototype.a = function() {
return this.d * this.b * this.c
};
document.write((new g(3, 6, 9)).a().toString());
虽然所有的变量和方法都改名了,但是你也注意到:有些方法被移除掉,有些方法合成了一行内。比如:
Cube.prototype.getSize = function() {
return this.depth_ * this.getHeight() * this.getWidth();
};
变成了:
g.prototype.a = function() {
return this.d * this.b * this.c
};
显然,2个 getter 方法 getWidth()、getHeight() 被 this._width和this._height安全的替换掉。因此,那些 getter 已经没有用,并且被编译器移除掉了。
同时使用了 @private 和 getter 的方法是指私有属性 _width 对开发人员来说是只读的,无妨对其添加一个 getter 方法。
使用 @interface 和 @implements
我们对编写 OO 风格的 JavaScript 感兴趣了后,将上面的示例改成下面的代码。
// skip example code.
////////////////////////////////////////////////////////////////////////////
/**
* The shape
* @interface
*/
function Shape() {
}
/**
* Get the size
* @return {number} The size.
*/
Shape.prototype.getSize = function() {};
////////////////////////////////////////////////////////////////////////////
/**
* The Box.
* @param {number} width The width.
* @param {number} height The height.
* @constructor
* @implements {Shape}
*/
function Box(width, height) {
Shape.call(this);
/**
* @private
* @type {number}
*/
this.width_ = width;
/**
* @private
* @type {number}
*/
this.height_ = height;
}
/**
* @return {number} The width.
*/
Box.prototype.getWidth = function() {
return this.width_;
};
// skip example code.
由于 @interface 只用在编译的时候,经过编译后的代码更小了,并且不会输出包含接口的代码。
function d(a, b) {
this.c = a;
this.b = b
}
d.prototype.a = function() {
return this.b * this.c
};
function e(a, b, c) {
d.call(this, a, b);
this.d = c
}
(function(a, b) {
function c() {
}
c.prototype = b.prototype;
a.prototype = new c
})(e, d);
e.prototype.a = function() {
return this.d * this.b * this.c
};
document.write((new e(3, 6, 9)).a().toString());
使用包(命名空间的 JS 对象)
想要对 JS 对象使用命名空间的话,命名层级过深的问题不会影响运行时的性能,因为编译器会帮你解决掉。
// Create namespaces.
var demo = {};
demo.example = {};
demo.example.exercise = {};
/**
* @constructor
*/
demo.example.exercise.Foo = function() {
demo.example.exercise.Foo.print(this.value1);
demo.example.exercise.Foo.print(this.value2);
};
/**
* Static method
* @param {string} str String to print.
*/
demo.example.exercise.Foo.print = function(str) {
document.write(str);
};
/**
* @type {string}
*/
demo.example.exercise.Foo.prototype.value1 = 'abc';
/**
* @type {string}
*/
demo.example.exercise.Foo.prototype.value2 = 'def';
var foo = new demo.example.exercise.Foo();
编译后的代码:
function a() {
document.write(this.a);
document.write(this.b)
}
a.prototype.a = "abc";
a.prototype.b = "def";
new a;
也许,想要保留 JS 代码而避免与页面中其他脚本产生冲突的话,可以使用标帜 -output_wrapper,也不是全局的对象(除非是明确的导出)。
编译后的代码如下:
(function() {function a() {
document.write(this.a);
document.write(this.b)
}
a.prototype.a = "abc";
a.prototype.b = "def";
new a;})()
编译器会确保那些长的命名空间、属性、方法已经重命名,尽可能多的短名称。
进行类型检查是在构建时,而非运行时
在构建时进行类型检查可以减少不必要的在运行时进行的类型检查。比如:
function User() {
}
function UsersGroup() {
this.users_ = [];
}
UsersGroup.prototype.add = function(user) {
// Make sure that only user can be added.
if (!(user instanceof User)) {
throw new Error('Only user can be added.');
}
this.users_.push(user);
};
var me = new User();
var myGroup = new UsersGroup();
myGroup.add(me);
这种方法可以完成。
/**
* @constructor
*/
function User() {
}
/**
* @constructor
*/
function UsersGroup() {
/**
* @private
* @type {Array.<User>}
*/
this.users_ = [];
}
/**
* @param {User} user
*/
UsersGroup.prototype.add = function(user) {
this.users_.push(user);
};
注意 this.users_ 的数据类型为 @type {Array.<user>} 表示是 User 的一个列表。
应该使用有意义的数据结构,而不是视任何事物为原生的对象,否则非常容易出错。
使用 @enum
有些时候你想要处理多种情形:
function Project(status) {
this.status_ = status;
}
Project.prototype.isBusy = function() {
switch (this.status_) {
case 'busy':;
case 'super_busy':
return true;
default:
return false;
}
};
var p1 = new Project('busy');
var p2 = new Project('super_busy');
var p3 = new Project('idle');
document.write(p1.isBusy().toString());
document.write(p2.isBusy().toString());
document.write(p3.isBusy().toString());
可以考虑使用 @enum:
/**
* @constructor
* @param {Project.Status} status
*/
function Project(status) {
/**
* @type {Project.Status}
* @private
*/
this.status_ = status;
}
/**
* @enum {number}
*/
Project.Status = {
BUSY: 0,
SUPER_BUSY: 1,
IDLE: 2
};
/**
* @return {boolean}
*/
Project.prototype.isBusy = function() {
switch (this.status_) {
case Project.Status.BUSY:;
case Project.Status.SUPER_BUSY:
return true;
default:
return false;
}
};
var p1 = new Project(Project.Status.BUSY);
var p2 = new Project(Project.Status.SUPER_BUSY);
var p3 = new Project(Project.Status.IDLE);
document.write(p1.isBusy().toString());
document.write(p2.isBusy().toString());
document.write(p3.isBusy().toString());
编译后为:
function a(b) {
this.a = b
}
function c(b) {
switch(b.a) {
case 0:
;
case 1:
return true;
default:
return false
}
}
var d = new a(1), e = new a(2);
document.write(c(new a(0)).toString());
document.write(c(d).toString());
document.write(c(e).toString());
枚举变量被原始的数替换。使用枚举能够编写更多的可维护性的代码。
使用 @define 启用或禁用消息的记录
如果你想要在一个类中记录某些重要的消息的话,像每一位谨慎的程序员那样都可以做到。
/**
* namespace for the Logger.
*/
var Logger = {};
/**
* Whether logging should be enabled.
* @define {boolean}
*/
Logger.ENABLED = true;
/**
* the log API.
* @param {...*} args
*/
Logger.log = function(args) {
if (!Logger.ENABLED) {
// Don't do anything if logger is disabled.
return;
}
var console = window['console'];
if (console) {
console['log'].apply(console, arguments);
}
};
/**
* A User.
* @param {string} name
* @constructor
*/
function User(name) {
Logger.log('New User', name);
}
var me = new User('me');
代码会编译为:
function b() {
var a = window.console;
a && a.log.apply(a, arguments)
}
new function(a) {
b("New User", a)
}("me");
你可以添加标帜 –define Logger.ENABLED=false 来禁用记录器。也可以添加标帜 –jscomp_error unknownDefines 来捕获未知的 @define。
java -jar compiler.jar \
--js src/demo.js \
--js_output_file compiled/demo.js \
--warning_level VERBOSE \
--formatting PRETTY_PRINT \
--jscomp_error accessControls \
--jscomp_error checkTypes \
--jscomp_error unknownDefines
--define Logger.ENABLED=false
--compilation_level ADVANCED_OPTIMIZATIONS;
对开发人员来说,允许在生成代码时启用记录器,或是完全由编译器带所有的记录器调用到生产布署。
使用类型转换
有些时候你想要把 JSON 对象转换为未知的引用类型。比如:
/**
* The Model definition.
* @constructor
*/
function UserModel() {
/**
* @type {string}
*/
this.firstName = '';
/**
* @type {string}
*/
this.lastName = '';
}
/////////////////////////////////////////////////////////////////////////////
/**
* The User constructor.
* @constructor
* @param {string} firstName
* @param {string} lastName
*/
function User(firstName, lastName) {
/**
* @type {string}
*/
this.fullName = firstName + ' ' + lastName;
}
/**
* A static method that creates a User from a model.
* @param {UserModel} model
* @return {User} The user created.
*/
User.createFromUserModel = function(model) {
return new User(model.firstName, model.lastName);
};
/////////////////////////////////////////////////////////////////////////////
// Cast a simple JSON Object as {UserModel}.
var data = /** @type {UserModel} */({
firstName : 'foo',
lastName : 'bar'
});
// Create a user from the model.
var user = User.createFromUserModel(data);
document.write(user.fullName);
正如你的意料之中,model definition 会移除掉,属性 firstName 和 lastName 也会重命名。
var a = {a:"foo", c:"bar"};
document.write((new function(b, c) {
this.b = b + " " + c
}(a.a, a.c)).b);
在上面的示例中,纯对象转换为未知的引用类型,可以给该对象更详细的指定。
在 jQuery 1.4 中添加了新的 API isPlainObject 它是在运行时进行类型检查,而我将会不推荐,如果你有编译器在手的话,其实在 JS 中看来似乎是解决一大难题。
还有...
还有很多其他使用的东西,比如对常量使用 @const。
在此本人推荐 Closure 工具官网去学习更多的知识。
另外,有一本不错的书《Closure 权威指南》(打个广告)有所有 closure 工具的详细内容。
总结
本人已经使用 Google Closure Compiler 有两年多了,它完全改变了 JavaScript 开发方式。
总之,在此有以下的东西要分享:
-
想要坚持不错的、统一的代码、风格(比如缩进)、80或120字符宽度限制等等,请参考 Google JavaScript Style Guide(中文)。
请确保代码的可读性和可维护性。
- 编写兼具描述性与信息性的文档,有些时候需要编写更多的 JsDoc。
- 把时间和精力更多的放在不错的 OO 设计、算法、数据结构上,而不是浪费在细微的优化代码或是使用任何的忍者技,那样会不易读或搞昏。
- 编写代码快速而频繁,并且生成代码。
- 使用大型 JavaScript 库没有错误,只要你可以用编译器来生成代码。其实能够得到更少的代码。
-
在 Closure Compiler 的 ADVANCED_OPTIMIZATIONS 模式中确保代码的兼容。
确保很多质量更高的代码,会使生成整合其他现有的编译器兼容的 JS 代码更加容易。
(完)