就像第一章例子所描述一样,javascript文件通过base.js引导Closure库,在base.js中创建了goog根对象,所有其它的Closure库属性都存在于对象下。因为定义在base.js 中的所有函数对使用Closure库的任何javascript库都是可用的,因此,这称为基本Closure库。本章会详细讲解这些基本内容。
通过对base.js中的api的讲述,本章也会引领读者理解Closure库高级的的设计理念。每一小节讲述base.js中的一个功能
依赖管理
第一章的“Hello World”例子演示了goog.provide()和goog.require(),它们用来维护Closure中的依赖关系。本节讲述库中依赖系统的具体功能实现。
calcdeps.py
在java中,可以把相互引用的类编译到一起,javascript不一样,由于javascript依赖于页面中<script>标签的线性引用顺序,因此不能包含前置声明。为了在没有前置声明的情况下保证一个顺序,clacdeps.py使用goog.require()和goog.provide()来输入产生逻辑顺序,并在文件之前加载。如果输入中有循环依赖,clacdeps.py会抛出一个错误,calcdeps.py的官方问题这个里:http://code.google.com/closure/library/docs/calcdeps.html.
calcdeps.py通过--output_mode参数可以产生四种类型输出:
script: 根据依赖顺序将javascript文件中的内容连合到一起,它能够通过<script>标签加载,但它比较大。
list: 根据依赖顺序产生一个javascript文件列表,这结一些工具通过每次处理一个javascript文件很有用
deps: 产生一系统函数goog.addDependency()调用,这在本章后面将讲述,这被用来在运行地产生依赖图。
compiled: 产生一个编译版本,因此需要安装Closure编译器,这将在第12章详细介绍。
clacdeps.py有三种输入类型,每种都有各自的参数,每个参数都可以出现多次:
--input: 用来指定产生输入时需要依赖的文件或目录
--path: 用来指定--input指定的文件需要依赖的文件或目录。在deps模式下,每个文件的依赖都支持--path参数,一般来说,在script,list,呀compiled模式下,只有通过--path指定的文件只会在输入文件中存在,例如:如果--path指定的目录包含所有Closure库,一个文件通过--input指定goog.string,因此只有goog/string/string.js会出现在输出中,而不是全部Closure库。
--dep: 只在deps模式中存在,用来表示已经提供依赖关系的路径,像Closure库,那的deps.js已经生成。通过--dep指定的文件不会被包含在输出中。
还有一个--exclude参数,来来排除在--input和--path中指定目录中的文件或子目录。
实际上,最常用的模式是deps和compiled.deps模式常用来加载本地文件用来测试和开发,compiled模式常用来编译发布最终产品。
goog.global
goog.global是加载Closure库的frame的window对象的别名,因为,在浏览器中,windo可以在javascriipt执行的任务地方被引用,在15.1章节中的ES5规范会讲到,全局对象是一个声明如NaN,Infinity,undefined,eval(),parseInt()等等全局属性的一个对象。全局对象是可变的,任何值都可以做为goog.global的一个属性。window中声明的属性也可能通过ES5中规范的全局对象访问:
-
- goog.global['callback'] = function(json) { };
-
-
- window.setTimeout(someFunction, 100);
-
-
- window.location = 'http://www.example.com/';
goog.global详细介绍
如前面所讲,全局对象是window,在base.js中,goog.global对指定到this中,在浏览器中,this和window都在全局域中代表同一个对象。因此也可以用goog.global代替。
但是,Closure工具可以支持ECMAScript规范,而不仅是javascript,也支持第三方ECMAScript规范,这规范在现代浏览器中和javascript很相似。
全局变量中的this可以是window对象,也可能不是,如在Microsoft Windows Script Host或Firefix扩展中,它可能不能指定任何东西。通过this 指定给goog.global而不是将window对象指定给它,使得Closure在非浏览器环境能更好工作,也能通过编译器对其改名。
COMPILED
compiled是全局域中除goog外的另一个等级常量。像goog.require()中说明的一样,这个变量决定Closure库在执行中是如何进行依赖管理的。
许多开发都错误的将COMPILED状态用来判断是否应该包含高度信息。goog.DEBUG才是正确的用法。
goog.provide(namespace)
goog.provide()用一个字符串声明一个命名空间,由于javascript本身没有命名空间概念,因此这是一个很大进步,但要避免的是当使用多个javascript库时的命令空间冲突。
goog.provide通过点分开的名字产生命名空间,它会先从左到右依次判断该名字在全局中是否存在 ,如果存在都使用存在的,不存在才产生新的对象:
-
- goog.provide('example.of.a.long.namespace');
-
-
-
-
-
-
-
-
-
- example.of.a.long.namespace.isOver = function() { alert('easy, eh?'); };
比如如果调用:goog.provide('example.of.another.namespace'),如果example.of已经存在 ,another 将直接加在其上,而不会创建一个新的example.of对象。
在Closure库中,每个Javascript中至少会有一个goog.provide调用,所有添加到这个命名空间中的元素都会被加入到此文件中,对于Java,命名空间是和文件目录一一对应的。这使得通过命名空间能够很容易定位到文件中,这种习惯在Closure中是我们推荐的。
goog.provide()背后的动机
传统上,有两个创建javascript命名空间的方法,第一种是,对同一命名空间的方法使用同一个前缀,并且别的库不能使用。例如,google在使用Closure之前所使用的Greasemonkey API,通过GM_前缀来声明方法。在以前的google map api中,使用G前缀,这很简单但是对全局命名空间却很浪费。
第二种方法,即Closure中所用的,只有一个全局对象,并声明所有方法作为那个对象的属性。一个命名空间下面再分为多个命名空间:
-
- var goog = {};
-
- goog.array = {};
-
- goog.array.binarySearch = function(arr, target, opt_compareFn) { };
-
- goog.array.sort = function(arr, opt_compareFn) { };
用点分开的命名空间和java中的包一样,这样即使用javascript中根本没有包或命名空间概念,开发者也能引用goog.array 下的各种方法。更重要的是,在全局对象下只有一个goog对象,而不像其它类库一样每个方法一个名字。这使得使用多个库时能很好地避免冲突。
但使用对象命名空间有两个缺点,第一个是增加命名空间的查找,比如调用goog.arry.sort([3,4,5]),首先解释器会先查找goog对象,然后查看其arrar属性,再查找sort属性,最后才调用方法,命名空间越长,查找越深。每二个是使用更多类型,命名空间将更长。
13章中会讲到,编译会重写javascript,并消除命名空间的属性查找。但是,它却不能消除开发者写一个很长命名空间的负担。
goog.require(namespace)
goog.require()和goog.provide()协同工作。在文件中所用的命名空间都必须有一个goog.require()调用。如果一个命名空间在provide之前require,将会出错。
goog.require()和java中的import不一样,在参数中引用的类型不用被加载,比如下面的例子, goog.math.Coordinate不需加载:
- goog.provide('example.radius');
-
-
-
-
-
- example.radius.isWithinRadius = function(radius, point) {
- return Math.sqrt(point.x * point.x + point.y * point.y) <= radius;
- };
如果方法如下重写,就需要明确调用goog.requier加载 goog.math.Coordinate :
- goog.provide('example.radius');
- goog.require('goog.math.Coordinate');
-
-
-
-
-
- example.radius.isWithinRadius = function(radius, point) {
- var origin = new goog.math.Coordinate(0, 0);
- var distance = goog.math.Coordinate.distance(point, origin);
- return distance <= radius;
- };
再回到第一章中的"Hello World"例子,页面中有两个<script>标签,一个指向base.js,另一个声明sayHello()方法。对于使用未编译的库,goog.require()不会出错,即使goog.provide('goog.do')根本没有被调用--这是为什么呢?
因为COMPILED默认值是false,base.js产生如下代码:
- document.write('<script type="text/javascript" src="deps.js"></script>');
deps.js在base.js相同目录下,它包含许多 goog.addDependency()调用。这是calcdeps.py生成的用来加载Closure依赖。当COMPILED为false时,goog.require()从依赖中选择goog.dom依赖,当找到每一个依赖后,添加另一个<script>标签指向正确的文件。
每11章中讨论用Closure模板生成javascript文件,每个生成文件中都有goog.provide()调用,因此使用模板的文件也可以用goog.require()调用。
goog.addDependency(relativePath, provides, requires)
goog.addDependency()是未编译的javascript中的goog.require()查找依赖用的。当文件被编译后,goog.provide()和goog.require()被用来确定在provided之前不会被required.
当检查完成后, goog.provide() 将被具体对象替换,goog.require()会被移除。编译后,依赖关系将没有用处,当COMPILED为true时,它们会被去除。
未编译的代码依赖goog.addDependency()来加载javascript文件。如下面例子:example.view依赖example.Model:
-
- goog.provide('example.Model');
- example.Model.getUserForEmailAddress = function(emailAddress) {
- if (emailAddress == '[email protected]') {
- return { firstName: 'Michael', lastName: 'Bolin' };
- }
- };
-
- goog.provide('example.View');
- goog.require('example.Model');
- example.View.displayUserInfo = function(emailAddress) {
- var user = example.Model.getUserForEmailAddress(emailAddress);
- document.write('First Name: ' + user.firstName);
-
- };
model.js和view.js在primitive目录中,和Closure库位于同级目录。在primitives目录下,calcdeps.py用如下命令创建deps.js依赖文件:
- python ../closure-library-r155/bin/calcdeps.py \
- --output_mode deps \
- --dep ../closure-library-r155/goog/ \
- --path model.js \
- --path view.js > model-view-deps.js
产生 model-view-deps.js文件,内容如下:
-
- goog.addDependency("../../primitives/model.js", ['example.Model'], []);
- goog.addDependency("../../primitives/view.js", ['example.View'], ['example.Model']);
对每一个传递给calcdeps.py的输入文件都会产生一个goog.addDependency()。它文件中的 goog.require() 和goog.provide()值。这用来在客户端生成依赖图,使得goog.require()能够加载依赖:
- <!doctype html>
- <html>
- <head></head>
- <body>
- <script src="../closure-library-r155/goog/base.js"></script>
- <!--
- When base.js is loaded, it will call:
- document.write('<script src="../closure-library-r155/goog/deps.js"></script>');
- The deps.js file contains all of the calls to goog.addDependency() to build
- the dependency graph for the Closure Library. The deps.js file will be
- loaded after the base.js script tag but before any subsequent script tags.
- -->
- <!--
- This loads the two calls to goog.addDependency() for example.Model and
- example.View.
- -->
- <script src="model-view-deps.js"></script>
- <script>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- goog.require('example.View');
-
-
- var main = function() {
- example.View.displayUserInfo('[email protected]');
- };
-
-
-
-
- alert(typeof example);
- </script>
- <script>
-
-
- alert(typeof example);
- main();
- </script>
- </body>
- </html>
从上面例子可以看出,上面的依赖会动态生成<script>标签。加载如果多的文件会有很大的性能问题,这种方法只能用于开发环境,这能更好调试代码。
函数
在应用局部,函数有一个强大的功能就是预先包含一些参数,而其它的参数在函数调用时确定,在这一节中,将会讲述“当一个函数被调用时this所指定的对象”,主要讲在函数调用时this所指定的值是怎么被解释的,特别是在对call()和apply()方法调用时。
goog.partial(functionToCall, ...)
goog.partial()函数参数为一个方法名和其它一些参数,很像javascript所有函数中的call()方法,和call()方法不同的是,它用指定的参数执行所传递的方法,goog.partial()将返回一个新的函数,在这函数被调用时,会根据传的和参数执行,下面的例子中,a()和b()将会产生相同效果:
- var a = function() {
- alert('Hello world!');
- };
- var b = goog.partial(alert, 'Hello world!');
a()和b()在执行时都会弹出"Hello world!"信息,初看,好像goog.partial()仅仅是匿名函数的快捷方式,但是它还能有如下新的特性:
-
-
-
- var atLeastTen = goog.partial(Math.max, 10);
- atLeastTen(-42, 0, 7);
- atLeastTen(99);
- atLeastTen();
atLeastten()是一个用Math.max()计算10和其它参数的函数,也就是说,goog.partial()创建了atLeaseTen()方法,它不是先用参数10调用Math.max,然后再Math.max()执行其它参数---实际上,在atLeaseten()调用前,Math.max()根本没有被执行。
因此,goog.partial()对于某个函数如果已知几个参数,但其它参数在以后才知道的情况下很有用。这种特性在事件处理中很常见,因为事件处理函数只知道注册的事件监听器,而最终参数只有在事件被触发时才可用。
使用goog.partial()也对防止内存泄露很有帮助。如下面的例子:
- var createDeferredAlertFunction = function() {
-
- var xhr = new XMLHttpRequest();
- return function() {
- alert('Hello world!');
- };
- };
- var deferredAlertFunction = createDeferredAlertFunction();
- </pre>deferredAlertFunction()在他的函数域中引用了所有的变量,XMLHttpRequest对象永远不会被使用。对这种在函数中声明函数,会产生很惊奇的效果,如果你不信,请看下面的例子:<p></p><p></p><pre name="code" class="javascript">var createDeferredEval = function() {
-
- var xhr = new XMLHttpRequest();
- return function(handleResult, str) {
-
- var result = eval(str);
- handleResult(result);
- };
- };
- var deferredFunction = createDeferredEval();
-
- deferredFunction(alert, 'xhr');
正常情况下,createDeferredEval()在执行结束后,由于xhr没有被任何对象组长,它以应该被垃圾回收,但是即使后面的内部方法没有引用XMLHttpRequest,但是它仍然能访问它,因为,内部方法保持着对声明它的作用域的引用。因此,只要内部方法引用存在,它的声明域就会存在,域中的对象就不会被垃圾回收。
如下的例子会更奇怪:
- var createDeferredEval = function() {
- var xhr = undefined;
- var theDeferredFunction = function(handleResult, str) {
-
- var result = eval(str);
- handleResult(result);
- };
- xhr = new XMLHttpRequest();
- return theDeferredFunction;
- };
- var deferredFunction = createDeferredEval();
-
- deferredFunction(alert, 'xhr');
即使在theDeferredFunction()创建前xhr是undfined,但当deferredFunction调用时,它使用它声明域中的最后状态,这此时xhr被指向了XMLHttpRequest。使用goog.partial()能有效解决这个问题,因为新函数被创建时,它的作用域中只包含传递给goog.partial()的参数。
- var evalAndHandle = function(handleResult, str) {
-
- var result = eval(str);
- handleResult(result);
- };
- var createDeferredEval = function() {
- var xhr = new XMLHttpRequest();
- return goog.partial(evalAndHandle, alert);
- };
- var deferredFunction = createDeferredEval();
-
- deferredFunction('handleResult');
-
- deferredFunction('createDeferredEval');
-
-
- deferredFunction('xhr');
在这个例子中,deferredFunction()有两个域项:evalAndHandle()产生的新域和全局域。当eval()执行时,先检查局部域,再检查全局域,handleResult在evalAndHandle()域中可见,createDeferredEval()在全局域中可见,但是xhr在所有域中都不存在。因为在createDeferredEval()方法结束后,由于没有任何对象引用它,因此它被垃圾回收掉了。
goog.bind(functionToCall, selfObject, ...)
goog.bind()和goog.partial()有点相似,不同的是,第二个参数是由goog.bind()产生并执行的对象,这对避免如下错误很有用:
- ProgressBar.prototype.update = function(statusBar) {
- if (!this.isComplete()) {
- var percentComplete = this.getPercentComplete();
- statusBar.repaintStatus(percentComplete);
-
- var updateAgain = function() { this.update(statusBar); };
- setTimeout(updateAgain, 500);
- }
- };
上面例子中声明在ProgressBar.prototype中,它被设计在ProgressBar对象中执行。当执行是,this将会指向ProgressBar实例。虽然对update()来说它是true,但是是updateAgain() 却不是,因为订时函数setTimeout()会在全局上下文中执行,这就是说此时this将指向全局对象,在浏览器中,即window对象。对这文件最通用解决方法是重命名为self:
- ProgressBar.prototype.update = function(statusBar) {
- if (!this.isComplete()) {
- var percentComplete = this.getPercentComplete();
- statusBar.repaintStatus(percentComplete);
-
- var self = this;
- var updateAgain = function() { self.update(statusBar); };
- setTimeout(updateAgain, 500);
- }
- };
因为self没有this那样的特殊意义,在updateAgain()执行时,它不会被全局对象替换。所以update()方法会在原始的ProgressBar对象中执行,在Closure中,更好的解决方法是用goog.bind():
- ProgressBar.prototype.update = function(statusBar) {
- if (!this.isComplete()) {
- var percentComplete = this.getPercentComplete();
- statusBar.repaintStatus(percentComplete);
-
- var updateAgainWithGoogBind = goog.bind(this.update, this, statusBar);
- setTimeout(updateAgainWithGoogBind, 500);
- }
- };
像goog.partial()一样,goog.bind()在限制域中创建函数,因此updateAgainWithGoogBind()将不会维护percentComplete引用 ,前一例子中updateAgain却会并阻止垃圾回收。goog.bind应该在上面例子情况下多被使用。
导出
在13中将会讲到,当编译器开启侵入式重命名后,所有用户声明的变量都会被重命名。为确定一个变量能在编译后以原来名字引用,就得将变量导出。
goog.getObjectByName(name, opt_object)
goog.getObjectByName()传入一个名字返回对象,这对访问另外的javascript库中的变量很有用:
- var GMap2 = goog.getObjectByName('google.maps.GMap2');
- var map = new GMap2(document.body);
或从全局环境中取得对象:
-
- var href = goog.getObjectByName('window.location.href');
初看上去好像这样写很冗长,因此还有简单方法实现些功能:
- var href = window.location.href;
但这样有可能会返回null,因为一些对象可能不存在,如果window已经声明,但是location没有声明,当调用herf时会抛出错误,如果不用goog.getObjectName(),必须像下面这样做安全检查:
- var href = (window && window.location && window.location.href) || null;
对于检查一个浏览器插件是否存在,可以用如下方法:
- var workerPool = goog.getObjectByName('google.gears.workerPool');
goog.exportProperty(object, propertyName, value)
goog.exportProperty()用来给对象设置一个属性值,通常对象已经声明了一个属性,使用goog.exportProperty能确定在编译后还通过这个名字访问该属性,对于如下未编译代码:
- goog.provide('Lottery');
- Lottery.doDrawing = function() {
- Lottery.winningNumber = Math.round(Math.random() * 1000);
- };
-
- goog.exportProperty(Lottery, 'doDrawing', Lottery.doDrawing);
编译后代码如下:
- var a = {};
- a.a = function() { };
- a.doDrawing = a.a;
在编译后的版本中,Lottery对象有两个属性a和doDrawing都指向同一个函数。导出doDrawing属性没有替换重命名的属性。这样做的目的是为了减少代码大小。
注意,当导出一个可变属性时将产生错误的结果,如下面导出Lottery的winningNumber属性:
- Lottery.winningNumber = 747;
- goog.exportProperty(Lottery, 'winningNumber', Lottery.winningNumber);
- Lottery.getWinningNumber = function() { return Lottery.winningNumber; };
- goog.exportProperty(Lottery, 'getWinningNumber', Lottery.getWinningNumber);
当编译后,代码变成:
- var a = {};
- a.b = function() { a.a = Math.round(Math.random() * 1000); };
- a.doDrawing = a.b;
- a.a = 747;
- a.winningNumber = a.a;
- a.c = function() { return a.a; };
- a.getWinningNumber = a.c;
现在考虑使用Lottery库的如下代码:
- var hijackLottery = function(myNumber) {
- Lottery.doDrawing();
- Lottery.winningNumber = myNumber;
- return Lottery.getWinningNumber();
- };
当使用未编译版本,hijackLottery()将返回正确myNumber值,但编译后,将返回一个随机值,正确做法是导出一个setter方法:
- Lottery.setWinningNumber = function(myNumber) {
- Lottery.winningNumber = myNumber;
- };
- goog.exportProperty(Lottery, 'setWinningNumber', Lottery.setWinningNumber);
现在对于编译时和未编译版可如下调用:
- var hijackLottery = function(myNumber) {
- Lottery.doDrawing();
- Lottery.setWinningNumber(myNumber);
- return Lottery.getWinningNumber();
- };
这并不是所只能导出函数,其实也可以导出一些只读的基本类型属性:
- Lottery.MAX_TICKET_NUMBER = 999;
- goog.exportProperty(Lottery, 'MAX_TICKET_NUMBER', Lottery.MAX_TICKET_NUMBER);
你也可以通过如下方法导出可变函数:
- Lottery.doDrawingFunction_ = function() {};
- Lottery.setDoDrawingFunction = function(f) {
- Lottery.doDrawingFunction_ = f;
- };
- goog.exportProperty(Lottery, 'setDoDrawingFunction',Lottery.setDoDrawingFunction);
- Lottery.doDrawing = function() {
- Lottery.doDrawingFunction_.apply(null, arguments);
- };
- goog.exportProperty(Lottery, 'doDrawing', Lottery.doDrawing);
这不是重设Lottery的doDrawing属性,而是给Lottery.setDoDrawingFunction()传入一个新的doDrawoing函数,当doDrawing被调用时将执行传入的函数,这在编译和未编译版本都正确。
goog.exportSymbol(publicPath, object, opt_objectToExportTo)
goog.exportSymbol()和goog.exportProperty()相似,只是它通过一个全路径导出而不是仅仅通过属性名。如果路径中的中间部分不存在,goog.exportSymbol()会自动创建它,下面例子用goog.exportSymbol()代替goog.exportProperty():
- goog.exportSymbol('Lottery.doDrawing', Lottery.doDrawing);
- goog.exportSymbol('Lottery.getWinningNumber', Lottery.getWinningNumber);
- goog.exportSymbol('Lottery.setWinningNumber', Lottery.setWinningNumber);
- goog.exportSymbol('Lottery.MAX_TICKET_NUMBER', Lottery.MAX_TICKET_NUMBER);
使用goog.exportSymbol()的优势在于它能自动创建新的对象Lottery,并添加四个前面列出的属性。和goog.exportProperty()例子不一样,Lottery对象没有属性重命名版本,goog.exportSymbol()创建新的对象,而不是在存在的对象中添加属性。使用goog.exportSymbol()在多数情况下是个更好的方法。
类型断言
本节介绍一些测试变量类型的函数,应优先使用这些函数而不是使用自定义的类型判断方法,因为编译器能在开启类型检查时使用这些功能。例如像下面代码使用example.customNullTest()而不是使用goog.inNull():
-
-
-
-
- example.getElementCssClasses = function(el) {
- return el.className.split(' ');
- };
-
-
-
-
- example.customNullTest = function(arg) {
- return arg === null;
- };
-
-
-
-
- example.getElementCssClassesById = function(id) {
- var el = document.getElementById(id);
-
- if (example.customNullTest(el)) {
- return null;
- } else {
-
-
- return example.getElementCssClasses(el);
- }
- };
在编译时开启类型检查时,将出现如下错误:
- type-assertions.js:33: ERROR - actual parameter 1 of example.getElementCssClasses
- does not match formal parameter
- found : (HTMLElement|null)
- required: Element
- return example.getElementCssClasses(el);
- ^
在example.getElementCssClassesById()中用goog.isNull()替换example.custonNullTest()方法将避免这些错误。
goog.typeOf(value)
goog.typeOf()方法和javascript中的typeof一样,这有利于跨浏览器,可能返回值是:object, function, array, string, number, boolean, null 或 undefined。每个返回值都有一个相对应的goog.isXXX()函数,除了undefined,因为他是能通过 is(!goog.isDef(value))。无论如何,使用goog.isXXX()检查一个值都是比goog.typeOf()更直接而且会更少出拼写错误。
-
- if (goog.typeOf(value) == 'strnig')
-
- if (goog.isStrnig(value))
唯一一个应该最好使用goog.typeOf()的是在switch块中:
- switch (goog.typeOf(someArg)) {
- case 'string': doStringThing(someArg); break;
- case 'number': doNumberThing(someArg); break;
- case 'boolean': doBooleanThing(someArg); break;
-
- }
goog.isDef(value)
goog.isDef(value)在value!=undefined时返回true, 因此对于null也会返回true,这在其它boolean上下文中却是false,看下面例子:
- var obj = { "a": undefined };
- goog.isDef(obj);
- goog.isDef(obj.a);
- ('a' in obj)
- goog.isDef(obj.b);
- ('b' in obj)
- goog.isDef(undefined);
- goog.isDef(0);
- goog.isDef(false);
- goog.isDef(null);
- goog.isDef('');
goog.isDef()在对可选参数的判断上经常用到:
-
-
-
-
- var payServer = function(bill, tipAmount) {
- if (goog.isDef(tipAmount)) {
- pay(bill + tipAmount);
- } else {
- pay(bill * 1.15);
- }
- };
goog.isNull(value)
当value==null时,goog.isNull(value)返回true,因此对于undefined等其它值都会返回false。
goog.isDefAndNotNull(value)
当value即不是null也不是undefined时返回true。
goog.isArray(obj)
在javascript中,判断array中是否存在一个对象很困难,最通常做法如下:
- var isArray = function(arr) {
- return arr instanceof Array;
- };
问题是,Array是javascript运行环境所在window中引用 的函数。在一个页面中有多个frame,每一个中都有独立的Array函数。因此一个frame中array对象不能instanceof另一个frame中的对象。goog.isArray()用来判断对象是否存在数组中。
goog.isArrayLike(obj)
在array.js中,Closure中有一个goog.array.ArrayLike类型。用来包含和array相似的javascript 对象。比如包含document.getElementByTagName()返回的参数对象或节点列表。它包含一个数字类型的length属性,这用于array.js中的工具函数对数字索引的数组对象的操作。goog.isArrayLike()用来判断一个对象是不是goog.array.ArrayLike对象。
goog.isDateLike(obje)
goog.isDateLike()在对象为Date对象时返回true,和goog.isArray()一样,由于可能在不同的frame中传递javascript对象 ,instanceof Date不能简单用来判断一个对像是否为Date类型。goog.isDateLike通过检查是否存在getFullYear()函数还判断对象是否是日期型的。这和instanceof Date不完全相等,它可以为goog.date.Date和goo.date.DateTime.
goog.isString(obj), goog.isBoolean(obj), goog.isNumber(obj)
goog.isString(obj), goog.isBoolean(obj), goog.isNumber(obj)使用typeof来判断类型,因此下面将返回false:
- goog.isString(new String('I am a capital S String'));
Java有对基本类型的封装类型,Javascript也一样,对strings, booleans,和number也有对应的封装类型。在javascript中有两种string类型,在typeof和instanceof中不同:
- var s = 'I am an ordinary string';
- typeof s;
- s instanceof String;
- var S = new String('I am a string created using the new operator');
- typeof S;
- S instanceof String;
因为使用封装类型创建新的string会占用更多内存,比文本型string占用更多空间,在Closure中使用instanceof String是不可用的。在switch中使用封闭类型不能正确工作,而且Boolean封装也很混乱:
- var wrapperString = new String('foo');
- var wrapperBoolean = new Boolean(false);
-
-
- switch(wrapperString) {
- case 'foo':
- break;
- default:
- if (wrapperBoolean) alert('You lose!');
- }
在switch中其它封装类型Boolean, Number也会返回true,因此即使没有Closure库,也应该避免封装类型,但是他们可用做强制类型转换:
- var b = Boolean(undefined);
- typeof b;
goog.isFunction(obj)
goog.isFunction用来判断对象是否为一个函数,在Closure中,如果为函数,typeof obj必须返回"function",并且obj必须有一个"call"属性,除了RegExps,NodeLists和一些HTML元素,一些浏览器对这些对象用typeof检查function类型。
goog.isObject(obj)
当为非空对象时,goog.isObject()返回true,非对基本类型则相反,那就是说传统对象,function, array或正则表态式都返回true, 而string, number, boolean, null或undefined返回false。
唯一标识符
因为在javascript中,对象是string类型key的字典结构,本章将演示判断对象类型的map对象持技巧。
goog.getUid(obj)
goog.getUid()对传入的对象返回一个唯一标识符。这会给对象添加一个单一的数字类型属性。对应的是goog.getUid(),返回对象中存在的此属性。对样就可以在单一的目录结构中创建对象类型的map:
-
- example.Map;
-
- example.createMap = function() {
- return {};
- };
-
-
-
-
-
- example.put = function(map, key, value) {
- map[goog.getUid(key)] = value;
- };
-
-
-
-
-
- example.get = function(map, key) {
- return map[goog.getUid(key)];
- };
这种方式有点像函数中使用哈希值,但是,函数中的数据不会计算UID,这和Java不一样,当对象改变时,UID不会改变,有相同属性的对象没有相同的UID。和和Java中有相同hashCode()时equals()相同也不一样。
因为UID作为一个object中的属性,goog.getUid()如果他的UID没有添加之前可能改变对象。
goog.removeUid(obj)
goog.removeUid()将移除goog.getUid()添加的UID。移除对象的UID只有在函数时调用时执行,这确保在其上没有逻辑。对访问可能有UID的对像调用很困难。为避免在基本类型中调用goog.getUid(),通常将其序列化成JSON串。
国际化
在写本书为此,在Closure库中并不是所有实现国际化的代码都开源,那就是现在库中代码中还存在一些翻译文本的原因。在Closure中,另一个方法是用goog.getMsg(),并将消息存在Closure模板中,并在需要本地化消息时使用模板库。
在Closure库中,编译器将每一个本地消息文件编译一每一个Javascript文件中,为其它方案不同,在一个javascript文件中使用的消息变量的翻译文本都在一个本地文件中,现在的Closure库中使用国际化实现成两个文件是可选的。翻译消息可以像如下这样重新声明goog.getMsg()方法:
-
- var MSG_HELLO_WORLD = goog.getMsg('hello world');
-
- goog.getMsg = function(str, values) {
- switch (str) {
- case 'hello world': return 'hola mundo';
- default: throw Error('No translation for: ' + str);
- }
- };
编译器不用两个文件处理,而是使用单一文件,这样用户可以下载更少javascript。缺点是资源文件在运行时不能改变。
goog.LOCALE
goog.LOCALE默认将javascript编译成“en”. 本地化语言可以为 fr, pt-BR, zh-Hans-CN。 通过把goog.LOCALE设计成常量,编译器能够消除使用本场资源消息的死代码。如果每一个都通过goog.LOCALE.goog.i18n.DateTimeSymbols将会产生更多大空间浪费。
goog.getMsg(str, opt_values)
goog.getMsg()参数为字符串,并用{$placeholder}作为占位符,假如string中包含占位符,opt_values的值最终会替换它们:
-
- var MSG_FILE_MENU = goog.getMsg('File');
-
- var MSG_SAMPLE_SENTENCE = goog.getMsg(
- 'The quick brown {$quickAnimal} jumps over the lazy {$lazyAnimal}',
- {'quickAnimal': 'fox', 'lazyAnimal': 'dog'});
一般的,goog.getMsg()应该赋值给一个变量,并以MSG_开头。
像goog.require()和goog.provide()一样,,编译器只请允许goog.getMsg传入文本字符串参数:
-
-
- var MSG_GREETING = goog.getMsg(useFormalGreeting ? 'Sir' : 'Mr.');
-
- var MSG_SIR = goog.getMsg('Sir');
- var MSG_MISTER = goog.getMsg('Mr.');
- var msgGreeting = useFormalGreeting ? MSG_SIR : MSG_MISTER;
对MSG_变量,通常用@desc标注JSDOC,这对翻译很有帮助:
-
- var MSG_FILE_MENU = goog.getMsg('File');
假如在没有任何上下文下翻译器要翻译''File",这是很困难的,因此@desc提供了更多必需的信息。
面向对象
Closure库支持面向对象,第5章中将详细介绍,本节将介绍base.js中支持面向对象编程的成员。
goog.inherits(childConstructorFunction, parentConstructorFunction)
goog.inherits()用来在子类和父类中建立父子关系。
goog.base(self, opt_methodName, var_args)
goog.base()用来调用父类中的方法。在上下文中,不光能调用父类构造函数,也能调用父类中由参数传入的方法。
goog.nullFunction
goog.nullFunction是一个空函数。常用来作为一个类型函数参数的默认值。这是一个在 HTML5数据库中调用成功回调例子:
- var database = openDatabase({"name": "wiki"});
- database.transaction(function(tx) {
-
-
- tx.executeSql('SELECT * FROM user', [], goog.nullFunction, alert);
- });
goog.nullFunction用来保存数据,尽管在使用空方法的地方都可以使用这个函数,但是构造对象时应避免使用:
-
- var Foo = goog.nullFunction;
- Foo.prototype.toString = function() { return 'Foo'; };
-
- var Bar = goog.nullFunction;
- Bar.prototype.toString = function() { return 'Bar'; };
- var foo = new Foo();
- alert(foo);
- alert(foo instanceof Foo);
- alert(foo instanceof Bar);
因为Foo和Bar是不同的类型,他们应该有自己独立的构造函数而不是引用相同的函数。goog.nullFunction也不应该用作函数参数,因为参数将被随时修改。在前一个例子中,goog.nullFunction在添加toString到原型时被 修改了。新的写法如下 :
-
- var Foo = function() {};
- Foo.prototype.toString = function() { return 'Foo'; };
-
- var Bar = function() {};
- Bar.prototype.toString = function() { return 'Bar'; };
当一个类被设计用来子类化时,goog.nullFunction可用来可被子类覆盖方法的默认实现。
goog.abstractMethod
goog.abstractMethod将会抛出错误消息:“unimplemented abstract method”、用来声明可在子类中覆盖的原型方法。
goog.addSingletonGetter(constructorFunction)
对于构造函数中没有任何参数的类,goog.addSingletonGetter()函数添加一个getInstance()的静态方法,在任何时候都返回相同一对象,用来 代替构造函数。
附加工具
base.js也提供了一些使用频率较少的一些工具。
goog.DEBUG
goog.DEBUG标记的代码将在代码编译后被编译器移除。默认情况下它的值为true,因此要移除if(goo.DEBUG) 块,编译选项得用: --define goog.DEBUG=false, 这使在开发过程中能提供更详细的错误信息:
- if (badThingHappened) {
- if (goog.DEBUG) {
- throw new Error('This is probably because the database on your ' +
- 'development machine is down. Check to make sure it is running ' +
- 'and then restart your server.');
- }
- }
这也能够只用在在高度模式下移除toString()方法:
- if (goog.DEBUG) {
- SomeClass.prototype.toString = function() {
- return 'foo: ' + this.foo_ + ', bar: ' + this.bar_;
- };
- }
虽然可以用COMPILED 代替goog.DEBUG来标识需要移除的代码,但最好不要这样做,13章中将会讲述,编译后的代码也可以包含高度信息。
goog.now()
返回1970年1月1日0点到目前为止的毫秒数,它是Date.now的别名,但它在单元调试中很有用。
goog.globalEval(script)
将string类型的javascript代码在全局上下文中执行,对于延时加载的javscript字符串,应该慎重使用此方法,因为当前域的变量应该被保护。
goog.getCssName(className, opt_modifier),goog.setCssNameMapping(mapping)
编译器用这两个函数来重命名css,不幸的是,在本书时,Closure还没有提供在样式表中重命名的方法,但是此处有关于这些的讨论:http://groups.google.com/group/closure-compiler-discuss/browse_thread/thread/1eba1d4f9f4f6475/aff6de7330df798a.