`
chensulong
  • 浏览: 3248 次
  • 性别: Icon_minigender_1
  • 来自: 烟台
文章分类
社区版块
存档分类
最新评论

How not to Write Javascript

阅读更多

文:Google Closure: How not to write 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行:

  1. for (var i = fromIndexi < arr.lengthi++) {

这个 for 循环每一次循环都查找了数组 (arr) 的.length 属性,简单的在开始循环的时候设置一个变量来存储这个数字,可以让循环跑得更快:

  1. for (var i = fromIndexii = arr.lengthi < iii++) {

Google的程序员们在同一个文件里面稍后的地方似乎发现了这个技巧,文件 array.js,153行:

  1. var l = arr.length;  // must be fixed during loop... see docs
  2. for (var i = l - 1i >= 0; --i) {

这个循环避免了在每次循环中的属性查找,但是这个for循环是如此的简单以至于它可以进一步的被简化成一个while循环,而且可以运行得更快:

  1. var i = arr.length;
  2. while (i--) {

但不是所有的Closure Library的效率都是由于没有优化好的循环造成的,文件 dom.js,797行:

  1. switch (node.tagName) {
  2.   case goog.dom.TagName.APPLET:
  3.   case goog.dom.TagName.AREA:
  4.   case goog.dom.TagName.BR:
  5.   case goog.dom.TagName.COL:
  6.   case goog.dom.TagName.FRAME:
  7.   case goog.dom.TagName.HR:
  8.   case goog.dom.TagName.IMG:
  9.   case goog.dom.TagName.INPUT:
  10.   case goog.dom.TagName.IFRAME:
  11.   case goog.dom.TagName.ISINDEX:
  12.   case goog.dom.TagName.LINK:
  13.   case goog.dom.TagName.NOFRAMES:
  14.   case goog.dom.TagName.NOSCRIPT:
  15.   case goog.dom.TagName.META:
  16.   case goog.dom.TagName.OBJECT:
  17.   case goog.dom.TagName.PARAM:
  18.   case goog.dom.TagName.SCRIPT:
  19.   case goog.dom.TagName.STYLE:
  20.   return false;
  21. }
  22. return true;

这类型的代码在Java中是相当普遍的,而且运行起来还不错。然而在JavaScript中,switch语句在每次一个程序员想要检查某个特定的HTML元素是否允许有子元素的时候都会低效的执行。

有经验的JavaScript程序员知道创建一个包含这个逻辑的object来做这个判断是快得多的:

  1. var takesChildren = {}
  2. takesChildren[goog.dom.TagName.APPLET] = 1;
  3. takesChildren[goog.dom.TagName.AREA] = 1;

建立这样一个object后,检查是否某个标签接收子元素的函数将运行的快得多:

  1. return !takesChildren[node.tagName];

这段代码可以进一步通过使用hasOwnProperty(下文有对此的详细解释)对外界干扰免疫:

  1. return !takesChildren.hasOwnProperty(node.tagName);

如果我们对Google有所期待的话,那就是执行效率了。好玩的是,Google发布了它自己的浏览器,Google Chrome,主要是为了提升JavaScript的执行效率到高一个层次!

看着这样的代码,我们不得不怀疑是不是Google通过培训他们自己的开发者写好一些的JavaScript代码也可以达到同样的目的。

漏水船中的六个月

说Google在构建Closure的时候忽略了开发效率是不公平的。实际上,这个library提供了一个通用的方法来缓存那些执行缓慢的函数的结果,这个方法被再次以同样的参数被调用的时候,结果会被立即返回。文件memoize.js,39行:

  1. goog.memoize = function(fopt_serializer) {
  2.   var functionHash = goog.getHashCode(f);
  3.   var serializer = opt_serializer || goog.memoize.simpleSerializer;
  4.   return function() {
  5.     // Maps the serialized list of args to the corresponding return value.
  6.     var cache = this[goog.memoize.CACHE_PROPERTY_];
  7.     if (!cache) {
  8.       cache = this[goog.memoize.CACHE_PROPERTY_] = {};
  9.     }
  10.     var key = serializer(functionHasharguments);
  11.     if (!(key in cache)) {
  12.       cache[key] = f.apply(thisarguments);
  13.     }
  14.     return cache[key];
  15.   };
  16. };

这是一个被很多大型JavaScript library采用的提升执行效率的聪明技巧;问题是,Google没有提供任何的方法来限制缓存的大小!当被缓存的方法只被很少的参数组合调用的时候这是没问题的,但这个方法如果通用的话就是危险的。

假如缓存一个方法的参数是鼠标的坐标位置的话,这段代码的内存占用将会失去控制的飞快增长,并且拖慢浏览器的速度。

用Dmitry的话来说就是:“我不太清楚在Java里面这个代码风格叫什么,但在JavaScript里面,这叫‘内存泄漏’”。

真空中的代码

是在他的关于开发一个JavaScript library的讲演中,Dmitry把JavaScript的全局作用域比做一个公共厕所。“你不能避免去那里”,他说,“但是如果可以的话尽量避免表面的接触。”

一个通用的JavaScript library如果要是可信赖的,它不仅仅要避免影响其他任何可能在同一空间运行的JavaScript代码,它同样要保护自身不被其它不那么礼貌的代码所影响。

在文件object.js,31行:

  1. goog.object.forEach = function(objfopt_obj) {
  2.   for (var key in obj) {
  3.     f.call(opt_objobj[key]keyobj);
  4.   }
  5. };

像这样的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来让代码更健壮:

  1. goog.object.forEach = function(objfopt_obj) {
  2.   for (var key in obj) {
  3.     if (obj.hasOwnProperty(key)) {
  4.       f.call(opt_objobj[key]keyobj);
  5.     }
  6.   }
  7. };

这是另一个Closure Library中特别脆弱的部分,来自 base.js, 667行:

  1. goog.isDef = function(val) {
  2.   return val !== undefined;
  3. };

这个函数检查一个特定的变量的值是否被定义。但如果有第三方的脚本将全局变量 undefined 设定为另一个值,它将会失效(stauren注:这是因为undefined在JavaScript中不是保留字)。只需要页面上任何一个位置有下面一行js就会把Closure Library搞崩溃:

  1. var undefined = 5;

依赖全局变量 undefined 是JavaScript library作者犯的另一个菜鸟错误。

你也许会想,那些乱给 undefined 变量赋值的人活该他们倒霉,但修正这个错误的代价是小的:简单的在函数内声明一个本地的 undefined 变量就好了!

  1. goog.isDef = function(val) {
  2.   var undefined;
  3.   return val !== undefined;
  4. };

混乱的类型

在其他语言的开发者看来,JavaScript中最让人迷惑的部分莫过于数据类型系统了。Closure Library包含这方面大量的错误,进一步显示了作者对于JavaScript这部分细节的经验缺乏。

文件 string.js, 97行:

  1. // We cast to String in case an argument is a Function. …
  2. var replacement = String(arguments[i]).replace();

这行代码使用了 String 转换函数把 arguments[i] 转换为一个字符串对象。这恐怕是做这样的一个转换的最慢的方式了,虽然对于其他语言的开发者来说这也许是最明显的办法。

一个快的多的方法是在你需要转换的值上面加一个空白字符串(“”):

  1. var replacement = (arguments[i] + "").replace();
  2. 下面是一个更和字符串相关的类型混乱。来自文件 base.js742行:
  3.  
  4. goog.isString = function(val) {
  5.   return typeof val == 'string';
  6. };

JavaScript实际上用两种方式来表现文本字符串——原生字符串类型和字符串对象:

  1. var a = "I am a string!";
  2. alert(typeof a)// Will output "string"
  3. var b = new String("I am also a string!");
  4. alert(typeof b)// Will output "object"

绝大多数时候用原生字符串类型来表示字符串是更有效的(上面的变量a),但要调用任何字符串上的原生的方法(例如toLowerCase),这个变量必须先被转换成一个字符串对象(上面的变量b)。JavaScript会在需要的时候自动的在2种类型之间转换。这个特性叫做“自动装箱(autoboxing)”,在很多其他的语言中也有。

不幸的是,在Google的只懂Java的程序员们眼中看来,Java只将字符串表示为对象。这是我对于为什么Closure Library会忽略JavaScript中第二种类型的字符串的最靠谱的猜想。

  1. var b = new String("I am also a string!");
  2. alert(goog.isString(b))// Will output FALSE
  3. 下面是另一个Java带来的类型混乱的例子。来自文件 <a href="http://code.google.com/p/closure-library/source/browse/trunk/closure/goog/color/color.js?r=2">color.js</a>,  633行:
  4.  
  5. return [
  6.   Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]),
  7.   Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]),
  8.   Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2])
  9. ];

以上的那些 1.0 说明了问题。像Java这样的语言用 代表整形数据使用的(1)与代表浮点数据的(1.0)是不一样的。但在JavaScript中,数字类型就是数字类型。(1 – factor)一样会运行得很好。

另一个有着Java味道的JavaScript代码的例子可以在 fx.js 中找到,465行:

  1. goog.fx.Animation.prototype.updateCoords_ = function(t) {
  2.   this.coords = new Array(this.startPoint.length);
  3.   for (var i = 0i < this.startPoint.lengthi++) {
  4.     this.coords[i] = (this.endPoint[i] - this.startPoint[i]) * t +
  5.     this.startPoint[i];
  6.   }
  7. };

看到第二行里面他们是怎么构造一个数组的吗?

  1. this.coords = new Array(this.startPoint.length);

虽然在Java中这是必须的,但在JavaScript中在运行前指定数组的长度是完全没有意义的。这就和使用 var i = new Number(0); 而不是 var i=0; 来新建一个存储数字用的变量一样没有意义。

实际上,你可以只是简历一个空白的数组,让它自己在被填入值的时候自己变大。这样做代码不但更短,运行得也更快:

  1. 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。要完全的了解一个东西,最好各方的想法都看一看,如下:

分享到:
评论

相关推荐

    深入浅出javascript

    write JavaScript code that makes web pages do all kinds of cool things that are impossible with HTML alone. If you can answer “yes” to any of these: this book is for you. this book is not for you. ...

    Simplifying [removed] Writing Modern JavaScript with ES5, ES6, and Beyond pdf

    Learn to write modern JavaScript not by memorizing a list of new syntax, but with practical examples of how syntax changes can make code more expressive. Starting from variable declarations that ...

    Beginning HTML, XHTML, CSS, and JavaScript.pdf

    Packed with real-world examples, the book not only teaches you how to write Web sites using XHTML, CSS and JavaScript, but it also teaches you design principles that help you create attractive web ...

    Programming JavaScript Applications

    By applying the design patterns outlined in this book, you’ll learn how to write flexible and resilient code that’s easier—not harder—to work with as your code base grows., JavaScript has become ...

    Wiley.JavaScript.Bible,7th.Edition

    the standard and proprietary details when they diverge, but also to show you how to write scripts that blend the two so that they work on the wide array of browsers visiting your sites or web ...

    JavaScript Best Practice

    This book presents modern JavaScript best practice, utilizing the features now available in the language that enable you to write more powerful code that is clean, performant, maintainable, and ...

    Beginning JavaScript with DOM Scripting and Ajax, 2nd Edition

    This book will teach you about JavaScript and how to use it in a practical manner. After you read it, you’ll be able to Understand JavaScript syntax and structures. • Create scripts that are easy ...

    Learning [removed] JavaScript programming Vol 1: The language core

    You will learn how to write JavaScript code that not only works, but that you can be proud of. The latest, completely revised edition takes into account the most current JavaScript language version. ...

    Functional.Programming.in.JavaScript.1784398225

    It's followed by a comprehensive roundup of functional programming libraries for JavaScript that minimizes the burden of digging deep into JavaScript to expose a set of tools that makes functional ...

    Drupal 6 JavaScript and JQuery

    This book gives you the keys to the toolbox, showing you how to use Drupal's JavaScript libraries to make your modules and themes more dynamic, interactive and responsive, and add effects to make your...

    Reliable JavaScript(Wrox,2015)

    Write the code to pass the unit tests, so you not only develop your technique for structuring large-scale applications, but you also learn how to test your work. You'll come away with hands-on ...

    Developing Hybrid Applications for the iPhone: Using HTML, CSS, and JavaScript to Build Dynamic Apps for the iPhone

    “For those not ready to tackle the complexities of Objective-C, this is a great way to get started building iPhone apps. If you know the basics of HTML, JavaScript, and CSS, you’ll be building apps ...

    Modern JavaScript TOOLS & SKILLS

    [removed] Best Practice presents articles discussing modern JavaScript best practice, enabling you to write more powerful code that is clean, performant, maintainable, and reusable. 6 JavaScript ...

    Test-Driving JavaScript Applications: Rapid, Confident, Maintainable Code

    Debunk the myth that JavaScript is not easily testable. Whether you use Node.js, Express, MongoDB, jQuery, AngularJS, or directly manipulate the DOM, you can test-drive JavaScript. Learn the craft of ...

    Closure The Definitive Guide

    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. ...

    Web Developer's Reference Guide(PACKT,2016)

    Finally, you will take a walk-through Node.js, which is a server-side framework that allows you to write programs in JavaScript. What You Will Learn Explore detailed explanations of all the major ...

    web scraping with python collecting more data from the modern web 2nd

    Learn web scraping and crawling techniques to access unlimited data from any web source in any format....Understand how to scrape JavaScript Learn image processing and text recognition

    AJAX and PHP.pdf

    common pitfalls, how to write efficient AJAX code, and how to achieve functionality that is easy to integrate into current and future web applications, without requiring you to rebuild the whole ...

Global site tag (gtag.js) - Google Analytics