原文:Google Closure: How not to write JavaScript
原译文:Google Closure: 糟糕的JavaScript
译者注:google在2009年11月6号开源了自己在 gmail、google reader 等几乎所有重要 google 产品中使用的javascrpt : google closure ,包括一套庞大的类似与 dojo 的 library、一套与之相应的 compiler、一套 template 系统。closure 完成了很多事情,包括一直困扰前端开发们的开发效率和运行效率之间的平衡(closure 使用library来提升开发效率,使用侵入性极强的 compiler 来去除无用代码,保证执行效率缩减 js 的大小,这与 YUI 等 library 采用的 combo-handling 是不一样的思路,但对于单独的页面,js 的代码量将更少是肯定的)总之,google 这次开源 Closure 是一个很棒的事情,网上有这太多关于这件事的讨论,大家可以到文章结尾的相关链接处看到更多的相关讨论的文章。这里翻译的是一篇 sitepoint 上指出的一些 Closure 的 javascript 的细节处理的错误,虽然有这些 stupid 的部分,却并不妨碍 google closure 是一个伟大的工具(据创始人 erik 说,现在有超过400名google工程师贡献了closure的代码),在这种规模下,代码还是尽量的stupid一些好了。虽然这么说,了解一些聪明的 javascript代码也并不会妨碍我们成为一个好的程序员,评价一个东西很糟糕也总是比创建一个新的东西容易得多。哈哈,废话不多说了,正文开始。
上周在澳大利亚佩恩的Edge of the Web会议上我碰到了javascript library Raphaël 和 gRaphaël 的创建者Dmitry Baranovskiy。这两个library做的最重要的事情也许就是使在javascript效率相对低下的IE上面绘制一些复杂的矢量图变得了可能。然而,Dmitry 却很不爽,因为他找到的一些实现的很糟糕的代码,在Google刚刚发布的Closure Library中。
在会议上做了一个名为how to write your own JavaScript library的演讲(详细笔记)之后,Dmitry在第二天早上早餐之后分享了他关于这个新library的想法:“就是这个世界现在需要的东西——另一个糟糕的JavaScript library”。当我问道是什么使它如此“糟糕”的时候,他解释说:“它是一个由不懂JavaScript的Java程序员们开发的JavaScript library。”
在那一天接下来的时间里面,Dmitry向那些愿意倾听的展示了他在Closure代码中发现的一个接一个的可怕的代码的例子。他告诉我,他最大的担忧是人们会因为Closure挂着强大的Google的招牌而从放弃一些真的很棒的例如jQuery这样的library转而使用它。
“我和你做个交易吧”,我告诉他,“给我一些可怕的代码的例子,我把他们发布在SitePoint上。”
缓慢的循环
文件 array.js,63行:
for (var i = fromIndex; i < arr.length; i++) {
这个 for 循环每一次循环都查找了数组 (arr) 的.length 属性,简单的在开始循环的时候设置一个变量来存储这个数字,可以让循环跑得更快:
for (var i = fromIndex, ii = arr.length; i < ii; i++) {
Google的程序员们在同一个文件里面稍后的地方似乎发现了这个技巧,文件 array.js,153行:
var l = arr.length; // must be fixed during loop... see docs
⋮
for (var i = l - 1; i >= 0; --i) {
这个循环避免了在每次循环中的属性查找,但是这个for循环是如此的简单以至于它可以进一步的被简化成一个while循环,而且可以运行得更快:
var i = arr.length;
⋮
while (i--) {
但不是所有的Closure Library的效率都是由于没有优化好的循环造成的,文件 dom.js,797行:
switch (node.tagName) {
case goog.dom.TagName.APPLET:
case goog.dom.TagName.AREA:
case goog.dom.TagName.BR:
case goog.dom.TagName.COL:
case goog.dom.TagName.FRAME:
case goog.dom.TagName.HR:
case goog.dom.TagName.IMG:
case goog.dom.TagName.INPUT:
case goog.dom.TagName.IFRAME:
case goog.dom.TagName.ISINDEX:
case goog.dom.TagName.LINK:
case goog.dom.TagName.NOFRAMES:
case goog.dom.TagName.NOSCRIPT:
case goog.dom.TagName.META:
case goog.dom.TagName.OBJECT:
case goog.dom.TagName.PARAM:
case goog.dom.TagName.SCRIPT:
case goog.dom.TagName.STYLE:
return false;
}
return true;
这类型的代码在Java中是相当普遍的,而且运行起来还不错。然而在JavaScript中,switch语句在每次一个程序员想要检查某个特定的HTML元素是否允许有子元素的时候都会低效的执行。
有经验的JavaScript程序员知道创建一个包含这个逻辑的object来做这个判断是快得多的:
var takesChildren = {}
takesChildren[goog.dom.TagName.APPLET] = 1;
takesChildren[goog.dom.TagName.AREA] = 1;
⋮
建立这样一个object后,检查是否某个标签接收子元素的函数将运行的快得多:
return !takesChildren[node.tagName];
这段代码可以进一步通过使用hasOwnProperty(下文有对此的详细解释)对外界干扰免疫:
return !takesChildren.hasOwnProperty(node.tagName);
如果我们对Google有所期待的话,那就是执行效率了。好玩的是,Google发布了它自己的浏览器,Google Chrome,主要是为了提升JavaScript的执行效率到高一个层次!
看着这样的代码,我们不得不怀疑是不是Google通过培训他们自己的开发者写好一些的JavaScript代码也可以达到同样的目的。
漏水船中的六个月
说Google在构建Closure的时候忽略了开发效率是不公平的。实际上,这个library提供了一个通用的方法来缓存那些执行缓慢的函数的结果,这个方法被再次以同样的参数被调用的时候,结果会被立即返回。文件memoize.js,39行:
goog.memoize = function(f, opt_serializer) {
var functionHash = goog.getHashCode(f);
var serializer = opt_serializer || goog.memoize.simpleSerializer;
return function() {
// Maps the serialized list of args to the corresponding return value.
var cache = this[goog.memoize.CACHE_PROPERTY_];
if (!cache) {
cache = this[goog.memoize.CACHE_PROPERTY_] = {};
}
var key = serializer(functionHash, arguments);
if (!(key in cache)) {
cache[key] = f.apply(this, arguments);
}
return cache[key];
};
};
这是一个被很多大型JavaScript library采用的提升执行效率的聪明技巧;问题是,Google没有提供任何的方法来限制缓存的大小!当被缓存的方法只被很少的参数组合调用的时候这是没问题的,但这个方法如果通用的话就是危险的。
假如缓存一个方法的参数是鼠标的坐标位置的话,这段代码的内存占用将会失去控制的飞快增长,并且拖慢浏览器的速度。
用Dmitry的话来说就是:“我不太清楚在Java里面这个代码风格叫什么,但在JavaScript里面,这叫‘内存泄漏’”。
真空中的代码
是在他的关于开发一个JavaScript library的讲演中,Dmitry把JavaScript的全局作用域比做一个公共厕所。“你不能避免去那里”,他说,“但是如果可以的话尽量避免表面的接触。”
一个通用的JavaScript library如果要是可信赖的,它不仅仅要避免影响其他任何可能在同一空间运行的JavaScript代码,它同样要保护自身不被其它不那么礼貌的代码所影响。
在文件object.js,31行:
goog.object.forEach = function(obj, f, opt_obj) {
for (var key in obj) {
f.call(opt_obj, obj[key], key, obj);
}
};
像这样的for-in循环在JavaScript library中是绝对危险的,因为你不会知道有其他的什么JavaScript代码可能在页面中运行,也不知道它可能会添加一些什么东西到 JavaScript标准的Object.prototype中。(stauren注:这里是Dmitry不了解Closure的整个设计理念了,看过 Closure Compiler的ADVANCE模式的高侵入式压缩方法就知道,它需求整个页面上有且仅有这一段js代码,否则编译会失败)
Object.prototype是一个包含着所有的JavaScript object共享属性的JavaScript object。给Object.prototype添加一个方法,当前页面上每一个JavaScript object都会包含这个方法——就算这个对象之前已经被创建!早期的像Prototype这样的JavaScript library 为Object.prototyp添加了大量各种的方便特性。
不幸的是,和Object.prototype中原生就有属性不一样,添加到Object.prototype的自定义属性会在任何页面上的for-in循环中被列举出来。
简单来说,Closure library不能与任何往Object.prototype添加特性的JavaScript代码共存。(stauren注:没错,google就是这么设计的。)
Google可以使用for-in循环中使用hasOwnProperty检查属性是否真的属于该object来让代码更健壮:
goog.object.forEach = function(obj, f, opt_obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
f.call(opt_obj, obj[key], key, obj);
}
}
};
这是另一个Closure Library中特别脆弱的部分,来自 base.js, 667行:
goog.isDef = function(val) {
return val !== undefined;
};
这个函数检查一个特定的变量的值是否被定义。但如果有第三方的脚本将全局变量 undefined 设定为另一个值,它将会失效(stauren注:这是因为undefined在JavaScript中不是保留字)。只需要页面上任何一个位置有下面一行 js就会把Closure Library搞崩溃:
var undefined = 5;
依赖全局变量 undefined 是JavaScript library作者犯的另一个菜鸟错误。
你也许会想,那些乱给 undefined 变量赋值的人活该他们倒霉,但修正这个错误的代价是小的:简单的在函数内声明一个本地的 undefined 变量就好了!
goog.isDef = function(val) {
var undefined;
return val !== undefined;
};
混乱的类型
在其他语言的开发者看来,JavaScript中最让人迷惑的部分莫过于数据类型系统了。Closure Library包含这方面大量的错误,进一步显示了作者对于JavaScript这部分细节的经验缺乏。
// We cast to String in case an argument is a Function. …
var replacement = String(arguments[i]).replace(…);
这行代码使用了 String 转换函数把 arguments[i] 转换为一个字符串对象。这恐怕是做这样的一个转换的最慢的方式了,虽然对于其他语言的开发者来说这也许是最明显的办法。
一个快的多的方法是在你需要转换的值上面加一个空白字符串(”"):
var replacement = (arguments[i] + "").replace(…);
下面是一个更和字符串相关的类型混乱。来自文件 base.js,742行:
goog.isString = function(val) {
return typeof val == 'string';
};
JavaScript实际上用两种方式来表现文本字符串——原生字符串类型和字符串对象:
var a = "I am a string!";
alert(typeof a); // Will output "string"
var b = new String("I am also a string!");
alert(typeof b); // Will output "object"
绝大多数时候用原生字符串类型来表示字符串是更有效的(上面的变量a),但要调用任何字符串上的原生的方法(例如toLowerCase),这个变量必须先被转换成一个字符串对象(上面的变量b)。JavaScript会在需要的时候自动的在2种类型之间转换。这个特性叫做“自动装箱 (autoboxing)”,在很多其他的语言中也有。
不幸的是,在Google的只懂Java的程序员们眼中看来,Java只将字符串表示为对象。这是我对于为什么Closure Library会忽略JavaScript中第二种类型的字符串的最靠谱的猜想。
var b = new String("I am also a string!");
alert(goog.isString(b)); // Will output FALSE
下面是另一个Java带来的类型混乱的例子。来自文件 color.js, 633行:
return [
Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]),
Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]),
Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2])
];
以上的那些 1.0 说明了问题。像Java这样的语言用 代表整形数据使用的(1)与代表浮点数据的(1.0)是不一样的。但在JavaScript中,数字类型就是数字类型。(1 – factor)一样会运行得很好。
另一个有着Java味道的JavaScript代码的例子可以在 fx.js 中找到,465行:
goog.fx.Animation.prototype.updateCoords_ = function(t) {
this.coords = new Array(this.startPoint.length);
for (var i = 0; i < this.startPoint.length; i++) {
this.coords[i] = (this.endPoint[i] - this.startPoint[i]) * t +
this.startPoint[i];
}
};
看到第二行里面他们是怎么构造一个数组的吗?
this.coords = new Array(this.startPoint.length);
虽然在Java中这是必须的,但在JavaScript中在运行前指定数组的长度是完全没有意义的。这就和使用 var i = new Number(0); 而不是 var i=0; 来新建一个存储数字用的变量一样没有意义。
实际上,你可以只是遍历一个空白的数组,让它自己在被填入值的时候自己变大。这样做代码不但更短,运行得也更快:
this.coords = [];
啊,你们有没有注意到这个函数里面还有另外一个效率低下的for循环呢?
API 设计
如果所有这些底层的代码质量缺陷还不能让你信服,我觉得你应该试试Google在Closure Library中包含的一些API。
例如Closure里面的图形类(graphics classes),是以HTML5 canvas API为基础构建的,你应该很奇怪为什么一个JavaScript API会以一个HTML标准来设计。简单来说,这是冗余、低效的,完全比不上同类代码。
作为Raphaël 和 gRaphaël 的作者,Dmitry在设计可用的JavaScript API方面相当有经验。如果你想感受一下canvas API的全部恐怖(当然,Closure的图形API也有所贡献),看看Dmitry在Web Directions South 2009讲演上面关于这个话题的音频和ppt吧。
Goolgle对于代码质量的责任
到这个时候我想你应该确信了在网上的最好的JavaScript代码中,Closure Library不是一个闪闪发光的明星了。如果你想找的是这样的代码,我可以向你你推荐一下更声名远扬的就像jQuery这样的library吗?
但你也许会想“这又怎么样?Google想发布什么垃圾代码就发布什么垃圾代码——又没人强迫你用它。”如果这是一个某google员工以自己名义发布的个人项目,我同意这个观点,但Google通过给Closure Library打上Google 商标的行为认可了它。
事实上,程序员们会因为 Closure Library 有着Google的名字而使用它,这就真的是一个杯具了。你喜欢也罢不喜欢也罢,Google在开发社区中是一个被信任的名字,所以Google应该抱着对开发社区负责的态度,在决定像Closure这样的library是否值得向公众曝光之前好好的自己检查一下。
—
译者注:说it sucks总是很容易,Closure自然有种种的不足,不过完全没有抹杀它为JavaScript界带来的一些新想法,包括强大的Google Compiler。要完全的了解一个东西,最好各方的想法都看一看,如下:
* Erik Arvidsson, founder of Closure, awesome JS hackers, on the history
* Alex Russell, founder of Dojo and Chrome Frame
* Dimitry presentation on JavaScript libraries
* Learning Closure with goog.net
* Inheritance Patterns in JavaScript
* Getting Closure: Don’t just use it, don’t just abuse it
分享到:
相关推荐
涵盖了Google开发的Closure工具集,这个工具集包括了Closure Library、Closure Templates、Closure Compiler以及Closure Testing Framework,这些工具为构建大规模JavaScript应用提供了丰富的资源。书中不仅仅关注于...
Closure Linter-Google JavaScript样式指南的ESLint插件和配置遵循针对JavaScript进行了大量定制的ESLint配置和插件。 查看。Google样式指南的特定功能将goog.scope识别为不会增加缩进的立即调用的函数表达式(IIFE...
Closure makes it easy for experienced JavaScript developers to write and maintain large and complex codebases -- as Google has demonstrated by using Closure with Gmail, Google Docs, and Google Maps. ...
Google Closure 是一个强大的JavaScript开发工具集,由Google开源并维护。这个框架包含了多个部分,旨在帮助开发者编写高质量、高性能的JavaScript代码。Closure的核心组件包括: 1. **Closure Library**:这是一个...
谷歌闭包编译器(Google Closure Compiler)是一款强大的JavaScript代码优化工具,由Google开发并维护。它通过对JavaScript代码进行语法分析和压缩,帮助开发者减小代码体积,提高网页加载速度,同时也能消除潜在的...
closure-compiler-v20170521.jar,以及一个.chm使用说明:‘Getting Started with the Closure Compiler Application’,‘Advanced Compilation and Externs’,‘Understanding the Restrictions Imposed by the ...
**使用Google Closure Compiler进行JavaScript压缩** Google Closure Compiler是一款强大的JavaScript优化工具,由Google开发,它能够对JavaScript代码进行压缩、优化和格式化,以提高网页加载速度和减少网络带宽...
在JavaScript的世界里,Closure Tools是由Google提供的一套强大的开发工具,包括了编译器、库、测试框架等,旨在帮助开发者编写高效、可维护的代码。这个名为"closure:用于Google Closure Tools开发的项目结构工作...
"flynetworks/google-closure" : "2.*" } 基本配置 安装软件包后,您可以使用一个新的配置文件“GoogleClosure.yaml”。 该文件基本上具有以下结构: UniqueIdentifier: compiler: options: 例子 MyUniqueKey:...
Closure编译器是Google开发的一款强大的JavaScript优化工具,它的纯JavaScript版本为开发者提供了一种高效、先进的代码构建方案。此工具旨在提升JavaScript代码的质量、性能和可维护性,通过压缩、优化以及处理代码...
Closure Library是Google开发的一个强大的、模块化的JavaScript库,旨在提供高效、可维护的代码解决方案。这个库被设计为可跨浏览器、跨平台使用,确保在各种JavaScript环境中的一致性。Closure Library的核心理念是...
标题中的“用谷歌的closure-compiler + maven-antrun-plugin”表明了本文将探讨如何结合Google的Closure Compiler工具和Maven的Antrun插件来优化JavaScrip项目。Closure Compiler是一款强大的JavaScript代码压缩器,...
Google Closure库的Vieux示例 该项目实现了具有Vieux体系结构的示例聊天应用程序。 它演示了如何构建复杂的有状态应用程序。 安装 $ git clone --recursive git@github.com:vieuxio/example-chat-closure.git $ cd ...
安装sudo gem install closure-compiler用法Closure::Compiler有一个compile方法,可以将其传递给字符串或打开的IO对象,并返回已编译JavaScript。 结果以字符串形式返回,或者,如果传递了一个块,则将其作为IO对象...
Closure Compiler Maven 插件是开发JavaScript应用程序的一个强大工具,它集成在Maven构建流程中,利用Google的Closure Compiler对JavaScript代码进行优化和压缩。Closure Compiler以其先进的语法分析和优化技术著称...
Google的通用JavaScript库Learning 这只是我正在学习的一件事。 和 Closure库是您调用的库,而不是调用您的框架(如React / Angular)。 库:更具灵活性。 框架:更少的样板。 Closure还带有用于各种事物的低级实用...
Google Closure Compiler是一款强大的JavaScript压缩工具,它可以优化、压缩和检查代码,以提高页面加载速度和减少带宽消耗。它通过分析代码结构和模式,进行高级优化,如变量名混淆、死代码删除等。然而,`eval()`...
《Google Closure Library 20180405:JavaScript编程的艺术与实践》 Google Closure Library,简称Closure Library,是Google开发的一个强大的JavaScript库,它为开发者提供了丰富的工具和资源,帮助构建高质量、高...
"closure-library-master" 是一个与 Google 的 Closure Library 相关的开源项目。Closure Library 是一个广泛使用的 JavaScript 库,尤其在构建大型、高性能的 Web 应用程序时非常有用。这个库提供了大量的实用工具...