维护JS代码的三种方法
维护。在其他语言中,考虑将已存在的对象作为库用来完成开发任务。在JS中,我们可以将已存在的对象视为一种背景,在这之上可以做任何事情。你应该把已存在的JS对象如一个使用工具函数库一样来对待。
不覆盖方法
不新增方法
不删除方法
当项目中只有你一个开发者时,因为你了解它们,对它们有预期,这些种类的修改很容易处理。当与一个团队一起在做一个大型的项目时,像这些情况的修改会导致大量的混乱,也会浪费很多时间。
不覆盖方法
在JS中,有史以来最糟糕的实践是覆盖一个非自己拥有的对象的方法,JS中覆盖一个已存在的方法是难以置信的容易。即使那个神圣的document.getElementById()方法也不例外,可以被轻而易举地覆盖。也许你看过类似下面的模式(这种做法也叫“函数劫持”):
// 不好的写法document._originalGetElementById = document.getElementById;document.getElementById = function (id) { if (id === 'window') { return window; } else { return document._originalGetElementById(id); } }
上例中,将一个原生方法document.getElementById()的“指针”保存在document._originalGetElementById中,以便后续使用。然后,document.getElementById()被一个新的方法覆盖了。新方法有时也会调用原始的方法,其中有一种情况不调用。这种“覆盖加可靠退化”的模式至少和覆盖原生方法一样不好,也许会更糟,因为document.getElementById()时而符合预期,时而不符合。 在一个大型的项目中,一个此类问题就会导致浪费大量时间和金钱。
不新增方法
在JS中为已存在的对象新增方法是很简单的。只需要创建一个函数赋值给一个已存在的对象的属性,使其成为方法即可。这种做法可以修改所有类型的对象。
// 不好的写法 - 在 DOM对象 上增加了方法document.sayImAwesome = function () { alert("You're awesome."); }// 不好的写法 - 在原生对象上增加了方法Array.prototype.reverseSort = function () { return this.sort().reverse(); }// 不好的写法 - 在库对象上增加了方法YUI.doSomething = function () { // 代码}
几乎不可能阻止你为任何对象添加方法(ES5新增了三个方法可以做到,后面会介绍)。为非自己拥有的对象增加方法一个大问题,会导致命名冲突。因为一个对象此刻没有某个方法不代表它未来也没有。 更糟糕的是如果将来原生的方法和你的方法行为不一致,你将陷入一场代码维护的噩梦。
我们要从Prototype JS类库的发展历史中吸取教训。从修改各种各样的JS对象角度而言Prototype非常著名。它很随意地为DOM和原生的对象增加方法。实际上,库的大多数代码定义为扩展已存在的对象,而不是自己创建对象。Prototype的开发者将该库看作是对JS的补充。在小于1.6的版本中,Prototype实现了一个document.getElementsByClassName()方法。也许你认识该方法,因为在HTML5中是官方定义的,它标准化了Prototype的用法。
Prototype的document.getElementsByClassName()方法返回包含了指定CSS类名的元素的一个数组。Prototype在数组上也增加了一个方法,Array.prototype.each(),它在该数组上迭代并在每个元素上执行一个函数。这让开发者可以编写如下代码:
document.getElementsByClassName('selected').each(doSomething);
在HTML5标准化该方法和浏览器开始原生地实现之前,代码是没有问题的。当Prototype团队知道原生的document.getElementsByClassName()即将到来,所以他们增加了一些防守性的代码,如下:
if (!document.getElementsByClassName) { document.getElementsByClassName = function (classes) { // 非原生实现 }; }
故Prototype只是在document.getElementsByClassName()不存在的时候定义它。这看上去好像问题就此解决了,但还有一个重要的事实是:HTML5的document.getElementsByClassName()不返回一个数组,所以each()方法根本不存在。原生的DOM方法使用了一个特殊化的集合类型称为NodeList。document.getElementsByClassName()返回一个NodeList来匹配其他的DOM方法的调用。
如果浏览器中原生实现了document.getElementsByClassName()方法,那么由于NodeList没有each()方法,无论是原生的或是Prototype增加的each()方法,在执行时都将引发一个JS错误。最后的结局是Prototype的用户不得不既要升级类库代码还要修改他们自己的代码,真是一场维护的噩梦。
从Prototype的错误中可以学到,你不可能精确预测JS将来会如何变化。标准已经进化了,它们经常会从诸如Prototype这样的库代码中获得一些线索来决定下一代标准的新功能。事实上,原生的Array.prototype.forEach()方法在ECMAScript5有定义,它与Prototype的each()方法行为非常类似。问题是你不知道官方的功能与原生会有什么样的不同,甚至是微小的区别也将导致很大的问题。
大多数JS库代码有一个插件机制,允许为代码库安全地新增一些功能。如果想修改,最佳最可维护的方式是创建一个插件。
不删除方法
删除JS方法和新增方法一样简单。当然,覆盖一个方法也是删除已存在的方法的一种方式。最简单的删除一个方法的方式就是给对应的名字赋值为null。
// 不好的写法 - 删除了DOM方法document.getElementById = null;
将一个方法设置为null,不管它以前是怎么定义的,现在它已经不能被调用到了。如果方法是在对象的实例上定义的(相对于对象的原型而言),也可以使用delete操作符来删除。
var person = { name: 'Nicholas'};delete person.name;console.log(person.name); // undefined
上例中,从person对象中删除了name属性。delete操作符只能对实例的属性和方法起作用。如果在prototype的属性或方法上使用delete是不起作用的。例如:
// 不影响delete document.getElementById;console.log(document.getElementById('myelement')); // 仍然能工作
因为document.getElementById()是原型上的一个方法,使用delete是无法删除的。但是,仍然可以用对其赋值为null的方式来阻止被调用。
无需赘述,删除一个已存在对象的方法是糟糕的实践。不仅有依赖那个方法的开发者存在,而且使用该方法的代码有可能已经存在了。删除一个在用的方法会导致运行时错误。如果你的团队不应该使用某个方法,将其标识为“废弃”,可以用文档或者用静态代码分析器。删除一个方法绝对应该是最后的选择。
反之,不删除你拥有对象的方法实际上是比较好的实践。从库代码或原生对象上删除方法是非常难的事情,因为第三方代码正依赖于这些功能。在很多案例中,库代码和浏览器都会将有bug或不完整的方法保留很长一段时间,因为删除它们以后会在数不胜数的网站上导致错误。
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
以上就是维护JS代码的三种方法的详细内容,更多请关注其它相关文章!