javascript编写_Google关闭:如何不编写JavaScript

javascript编写

上周在珀斯举行的Web边缘会议上,我赶上了RaphaëlgRaphaëlJavaScript库的创建者Dmitry Baranovskiy 。 这些库所做的最重要的事情也许就是在JavaScript性能相对较差的Internet Explorer中使复杂的矢量图形成为可能。 因此,德米特里(Dmitry)对写得不好JavaScript缺乏耐心,例如他在Google刚刚发布的Closure Library中找到的代码。

在会议上发表了关于如何编写自己JavaScript库的演讲( 详细注释 ),第二天早上,德米特里在早餐时分享了他对新库的想法。 他说:“这正是世界所需要的-另一个糟糕JavaScript库。” 当我问他是什么让它变得“烂”时,他阐述了。 “这是由Java开发人员编写JavaScript库,他们显然没有获得 JavaScript。”

在一天的余下时间里,对于任何愿意听的人,德米特里都举了一个例子,列举了他在通过Closure进行挖掘时发现的可怕代码的例子。 他告诉我,他最大的担心是,人们会凭借Google名称的优势从真正出色JavaScript库(如jQuery)切换到Closure。

我告诉他:“我给你交易。” “向我发送此可怕代码的一些示例,我将其发布在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中很常见,并且在Java中会很好地执行。 但是,在JavaScript中,每次开发人员检查特定HTML元素是否被允许带有子元素时,该switch语句的执行效果就像狗一样。

有经验JavaScript开发人员知道创建一个对象来封装此逻辑要快得多:

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

通过设置该对象,检查标签是否接受子代的功能可以更快地运行:

return !takesChildren[node.tagName];

使用hasOwnProperty可以使该代码进一步防弹,以防止外界干扰(有关此内容的完整说明,请参见下文)。

return !takesChildren.hasOwnProperty(node.tagName);

如果我们期望Google做出一件事,那就是关注性能。 哎呀,谷歌发布了自己的浏览器谷歌浏览器,主要是为了使JavaScript性能再上一个台阶!

看到这样的代码,人们不得不怀疑Google是否可以通过教导其工程师编写更好JavaScript代码来实现同一目标。

六个月的漏水船

暗示Google 忽略了构建Closure的性能是不公平的。 实际上,该库提供了一种通用方法来缓存运行缓慢的函数的结果,但是对于给定的参数集,该函数的结果始终会返回相同的结果。 从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库中采用的巧妙的性能技巧。 问题是,Google没有提供任何限制缓存大小的方法! 如果只用一小部分不同的参数调用缓存的函数,那很好,但这通常是一个危险的假设。

例如,根据鼠标指针的坐标来缓存函数的结果,此代码的内存占用将Swift失去控制,并使浏览器缓慢爬行。

用德米特里(Dmitry)的话说,“我不确定在Java中这种模式是什么,但是在JavaScript中它被称为“内存泄漏”。”

真空编码

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

在关于构建JavaScript库的演讲中,Dmitry将JavaScript的全球范围与公共厕所进行了比较。 他说:“你不可避免地要去那里。” “但是当您这样做时,请尝试限制与表面的接触。”

为了使通用JavaScript库可靠,它不仅必须避免干扰可能在其旁边运行的任何其他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库中本质上是危险的,因为您永远不知道页面中可能还会运行其他JavaScript代码以及它可能添加到JavaScript的标准Object.prototype

Object.prototype是JavaScript对象,其中包含所有JavaScript对象共享的属性。 向Object.prototype添加一个新函数,页面中运行的每个JavaScript对象都将添加该函数-即使它是事先创建的! 早期JavaScript库(例如Prototype)Object.prototype中添加了各种便利功能。

不幸的是,与Object.prototype提供的内置属性Object.prototype ,添加到Object.prototype自定义属性将在页面中的任何forin循环中显示为对象属性。

简而言之,Closure库不能与任何向Object.prototype添加功能JavaScript代码共存。

Google可以使用hasOwnProperty来检查for - in循环中的每个项目,以确保其属于对象本身,从而使其代码更加健壮:

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库的另一个特别脆弱的部分。 从base.js的 677行:

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

此函数检查特定变量是否已定义值。 否则,除非第三方脚本将全局undefined变量设置为其他变量,否则它会这样做。 页面中任何地方的这一行代码都会导致Closure Library崩溃:

var undefined = 5;

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

您可能会认为,给undefined值赋值的任何人都应该得到他们所得到的,但是这种情况下的解决方法很简单:只需声明一个局部undefined变量以在函数中使用!

goog.isDef = function(val) {
  var undefined;
  return val !== undefined;
};

典型的困惑

对于来自其他语言的开发人员来说,JavaScript最令人困惑的方面之一是其数据类型系统。 Closure库包含大量的信息,进一步揭示了它的作者缺乏对JavaScript精要点的丰富经验。

string.js中 ,第97行:

// 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根据需要自动在这两种表示形式之间来回转换字符串。 此功能称为“自动装箱”,并以许多其他语言显示。

不幸的是,对于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)也会发挥作用。

fx.js的 465行中可以看到带有Java语言JavaScript代码的另一个示例:

goog.fx.Animation.prototype.updateCoords_ = function(t) {
  this.coords = new Array(this.startPoint.length);
  for (var i = 0; i

看看他们如何在第二行创建数组?

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

尽管在Java中是必需的,但在JavaScript中提前指定数组的长度完全没有意义。 创建一个新的变量来存储数字, var i = new Number(0);同样有意义var i = new Number(0); 而不是var i = 0;

相反,您可以只设置一个空数组,并允许其在填充时增长。不仅代码缩短了,而且运行速度也更快:

this.coords = [];

哦,您发现该函数的另一个效率低下for循环了吗?

API设计

如果上面所有低级代码质量的问题都不能使您信服,那么我不建议您尝试使用Google内置在Closure Library中的一些API。

例如,Closure的图形类是围绕HTML5 canvas API建模的,这与您对HTML标准机构设计JavaScript API的期望差不多。 简而言之,它是重复性的,效率低下的,彻头彻尾的代码,令人讨厌。

作为RaphaëlgRaphaël的作者,Dmitry在设计可用JavaScript API方面具有丰富的经验。 如果您想完全了解canvas API(以及扩展名Closure的图形API),请查看Dmitry在“ Web Directions South 2009 ”主题演讲中的音频和幻灯片。

Google对代码质量的责任

至此,我希望您相信Closure Library并不是Web必须提供的最佳JavaScript代码的出色示例。 如果您正在寻找,我是否可以推荐像jQuery这样的老牌玩家?

但是您可能会想:“那又如何呢? Google愿意的话可以发布糟糕的代码-没人强迫使用它。” 而且,如果这是一个由某些Google员工以自己的名字在旁边发布的个人项目,我会同意您的看法,但是Google认可Closure Library,并在其上打上了Google品牌的标签。

事实是,开发人员切换到Closure,因为它带有Google名称,这才是真正的悲剧。 不管您喜欢与不喜欢,Google在开发社区中都是一个值得信赖的名字,在决定像Closure这样的图书馆值得公众关注之前,Google有责任对该社区做些功课。

翻译自: https://www.sitepoint.com/google-closure-how-not-to-write-javascript/

javascript编写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值