javascript编写
上周在珀斯举行的Web边缘会议上,我赶上了Raphaël和gRaphaë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
自定义属性将在页面中的任何for
– in
循环中显示为对象属性。
简而言之,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ël和gRaphaë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编写