任意一段重要的代码都须要关注无数的开发问题。可是,其中对可复用JavaScript代码挑战最大的五项问题如图14.2所示。javascript
图14.2 对可复用JavaScript代码挑战最大的五项问题html
五大开发问题以下。java
咱们须要权衡解决这些问题所花费的时间与获得的收益。这些是不得不回答的问题。你分析潜在受众、开发资源、开发排期等,这些都是决定性因素。web
当试图开发可复用的JavaScript代码,咱们须要考虑全部的因素,还须要考虑目前最流行的浏览器,由于这些浏览器是咱们的目标受众最可能使用的浏览器。其余不那么流行的浏览器,咱们至少保证代码能够优雅降级。例如,若是一个浏览器不支持某API,咱们应该当心咱们的代码不会抛出任何异常,这样剩下的代码仍然能够顺利执行。正则表达式
在接下来的小节中,咱们将讲解这些问题,以便更好地理解咱们面对的挑战以及如何应对。chrome
当咱们开发可复用性JavaScript代码时,须要考虑解决的问题之一是处理咱们肯定须要兼容的多种浏览器bug以及API的差别。尽管浏览器愈来愈标准化,可是代码仍是必须得彻底符合浏览器提供的特性。windows
实现这一目标的方法很直接:咱们须要完整的测试工具,足以覆盖代码经常使用的和不经常使用的用例。充分测试以后,在知道开发的代码将在支持的浏览器中工做后,咱们会感到安全。假设浏览器没有后续变化,不会打破向后兼容性,我有一个模糊的预感,代码甚至会在将来版本的浏览器中工做。在14.3节中,咱们会观察特定的策略来处理浏览器bug和差别。api
复杂的地方是,当前浏览器bug会在将来的浏览器版本中被修复。数组
浏览器永远存在特定的错误是很愚蠢的——大部分浏览器bug最终都会修复,把但愿寄托在浏览器bug上是很危险的开发策略。最佳方式是使用14.3节中的技术,使用不会过期的变通方案。promise
在编写一个可重用的JavaScript代码时,咱们但愿它能够持续运行很长时间。编写任何方面的网站(CSS、HTML等),浏览器发布新版本后,咱们不但愿再回去修复代码。
假设浏览器bug引发常见的网站问题:为解决浏览器bug使用特殊技巧,未来浏览器发布新版本修复了bug,就会出现问题。
处理浏览器漏洞的问题是双重的:
最近刚好发生了第2种状况的有趣的事例,关于scrollTop的bug(https://dev.opera.com/article...)。
当处理HTML DOM时,可使用scrollTop和scrollLeft属性,修改当前元素的滚动位置。可是当咱们对根元素使用这些属性时,根据规范,将会返回滚动的位置,IE11与Firefox浏览器严格遵循了这则规范。而Safari、Chrome和Opera并无遵照。若是试图修改根元素的滚动位置时,不会发生任何事情。为了实现相同的效果,咱们只能在body元素上使用scrollTop和scrollLeft属性。
当面对浏览器的不一致性时,Web开发者们经常检测当前浏览器的名字(经过用户代理字符串,后续会详细介绍),而后在IE11和Firefox上对HTML元素使用scrollTop和scrollLeft属性,而在Safari、Chrome和Opera上则对body元素使用scrollTop和scrollLeft属性。规避这类问题将会形成灾难性后果。由于许多网页明确编码指定在Safari、Chrome或Opera上使用body元素,这些浏览器没法真正修复这个bug,由于一旦修复,许多网页都没法运行。
这引出了另外一个关于bug的观念:在肯定某一功能是不是潜在的错误时,使用规范进行验证!
浏览器的bug不一样于未指明的API。参考浏览器规范很是重要,由于规范提供了确切的标准,浏览器使用这些标准进行开发和完善代码。相比之下,一个未指明的API的实现可能会在任什么时候候发生改变(特别是试图成为标准化的实现)。在未指明的API不一致的状况下,你应该对预期输出进行测试。警戒这些API将来可能发生的变化。
另外,bug修复和API的变化是有区别的。bug修复是很容易预见的——浏览器最终将修复bug,即便要花很长的时间,API变化更难发现。标准API不太可能改变,尽管不是彻底闻所未闻,变化更有可能出现未指明的API中。
幸运的是,大多数Web应用程序出问题的状况不多发生。万一出现问题,有效地提早预知是无效的办法(除非咱们逐一测试相关的API——可是这样一个过程的开销是可怕的)。这种API的变化应该作回归处理。
下一个须要关心的问题是,没有人是一座孤岛,咱们的代码也不是,让咱们研究代码的影响范围。
任何可重用代码必须与围绕它的代码共存。咱们但愿代码运行在本身编写的网站或是他人开发的网站上,咱们都须要确保代码能够与其余代码共存。
这是一把双刃剑:咱们的代码不只必须可以经受住可能写得很遭的外部代码,还必须得克服环境对代码的不利影响。
咱们须要警戒的程度很大程度上取决于所使用的代码对环境的关注。例如,若是咱们仅为单个或有限个网站编写可重用的代码,在某种程度上能够控制,能够少一些担忧,由于咱们知道代码的运行对外部代码的影响程序,并且一旦有问题,咱们能够自行修复。
{注意 }这个问题的重要程度足以用一本书来阐述。若是你想更深刻地探究,咱们强烈推荐Ben Vinegar 和 Anton Kovalyov 编写的《第三方JavaScript》一书(Manning, 2013, https:// www.manning.com/books/third-party-javascript)。
若是开发代码将普遍用于未知环境(不可控的)中,则咱们须要双重确认代码的健壮性。接下来讨论一些实现代码健壮性的策略。
为了不咱们的代码影响页面上的其余代码,最佳实践是使用封装。一般来讲,封装指代码(如同)存放在容器里。从广义上来讲,是一种限制访问其余对象组件的语言机制。Aunt Mathilda也许会总结为“各人自扫门前雪,莫管他人瓦上霜”。
在页面上引入咱们的代码时,尽量少地影响全局代码,将会使Aunt Mathilda很是开心。事实上,尽量少地使用全局变量,甚至最好仅限一个,是很容易的。
第12章中的jQuery,它是最流行的客户端JavaScript库,也是最好的范例。jQuery引入一个名为jQuery的全局变量(一个函数),别名为$,它甚至容许其余网页为$设置别名避免冲突。
jQuery中几乎全部的操做都经过jQuery函数完成。其余函数(工具函数)被定义为jQuery的属性(第3章介绍如何将函数定义为另外一个函数的属性),使用jQuery做为命名空间。咱们可使用相同的策略。假设咱们须要定义一组函数,咱们将其定义在命名空间ninja下。
与jQuery相似,咱们能够定义名为ninja()的全局函数以操做传入的变量。例如:
var ninja = function(){ /* implementation code goes here */ }
使用咱们设定好的命名空间定义工具函数:
ninja.hitsuke = function(){ /* code to distract guards with fire here */ }
若是咱们不须要ninja做为函数,仅做为一个命名空间便可,咱们可使用以下定义方式:
var ninja = {};
建立空对象,随后在该对象上定义属性或方法便可。为了保证代码的封装,须要避免其余操做,如修改已经存在的变量、函数原型甚至DOM元素。修改咱们本身代码以外的任何内容,均可能引发潜在的冲突和混淆。另外,尽管咱们当心翼翼地严格遵照最佳实践封装代码,但咱们仍然没法保证代码的行为。
有一个老笑话Grace Hopper在Cretaceous时期为接替人员清除蛀虫时说:“你最不恶心的代码就是你本身写的代码。”看起来很讽刺,可是当咱们的代码与不可控的代码同时运行时,为了安全起见,咱们须要假设最糟的状况。
尽管一些代码编写工整,但也有可能潜在地作一些出乎意料的事,例如修改函数属性、对象属性和DOM元素的方法。这些均可能设有陷阱。
在这种状况下,咱们的代码只能作一些无伤大雅的事,例如使用JavaScript数组,通常状况下JavaScript数组只能是JavaScript数组。可是,若是一些页面上修改了数组的行为,咱们的代码将没法运行,固然不是咱们自身的缘由。
遗憾的是,处理这种问题没有固定的原则标准,可是咱们能够采起一些措施。咱们将在后续小节中介绍保护性方法。
大部分浏览器具备一些反特性(咱们不能称之为bug,由于这些特性是有意而为之),这些特性会使得代码不可预期地落入陷阱从而运行失败。这些特性使得原始元素与添加在元素上的id或name属性产生关联。可是当id或name属性与元素上已经存在的部分属性产生冲突时,就会发生一些意料以外的状况。
查看如下HTML代码片断,观察id属性的滥用:
<form id="form" action="/conceal"> <input type="text" id="action"/> <input type="submit" id="submit"/> </form>
如今,在浏览器中能够这样调用:
var what = document.getElementById('form').action;
咱们指望返回合理的form的action属性。大部分状况下是能够返回的。可是当检查值的时候你会发现,返回的倒是input#action元素。为何?让咱们试试其余元素:
document.getElementById('form').submit();
这条语句本应引发form提交,可是却返回script错误:
Uncaught TypeError: Property 'submit' of object #<HTMLFormElement> is not a function
发生了什么呢?
浏览器将<form>元素内全部input元素都做为表单form的属性。这一特性开始看起来很便利,添加到form属性的名称是input元素的id或name属性。若是input元素的id或name属性刚好使用了form元素的属性,例如action或submit,这些form元素的初始属性就被替换为新的属性值,一般被错误地指向DOM。所以,在input#submit元素建立以前,form.action的引用应指向<form>的action属性。在input#action元素建立以后,form.action的引用指向input#action元素。form.submit属性也发生了相同的状况。
这是为了兼容过去的浏览器的处理方法,老式浏览器不具有获取DOM元素的方法。浏览器厂商添加这种特性是为了方便获取form元素。现现在咱们能够轻松地获取DOM元素,可是仍然留下了反作用。不管如何,浏览器这种特殊的“特性”可能引发代码中大量扑朔迷离的问题,在调试时须要谨记于心。当咱们遇到属性被意外地转变成非预期的内容时,罪魁祸首有多是DOM滥用。幸亏咱们能够在本身的代码中避免这种问题,避免编写有可能与标准属性发生冲突的过于简单的id或name属性,并能够推荐其余开发者使用相似的策略。开发过程当中尤为须要避免submit值,以避免形成使人沮丧和困惑的bug行为。
一般咱们指望CSS规则在代码执行时已经可用。确保在样式代码中定义的CSS规则在JavaScript代码执行时已经可用的最佳方式之一是,将外部样式表单放置在外部脚本文件以前。若是不这样作,可能引发意料以外的结果,由于脚本可能试图访问未定义的样式信息。遗憾的是,这种问题没法经过JavaScript脚本进行矫正,只能经过手动修改用户文件解决。
后续几节中会介绍一些关于外部代码对于代码运行的影响的基础示例。当其余用户试图将咱们的代码集成进他们的网站时,会暴露出一些问题,那么应该如何诊断这些问题,如何设计合适的测试用例来解决这些问题呢?有时,当咱们试图将其余人的代码集成进本身的页面时,会发现相似的问题,但愿本节介绍的建议有助于解决这些问题。糟糕的是,对于解决代码集成问题,除了采用一些聪明的方式来编写防护性代码,没有其余更好的方式。接下来继续关注下一个问题。
回归是在编写可复用、可维护性JavaScript代码时,遇到的最难的问题之一。由于浏览器的bug或不向后兼容的API发生变化(一般是未详细说明的API)致使代码不可预期地中断了。
{注意 }这里咱们使用术语回归的经典定义:过去使用的特性再也不运行了。这一般是无心的,也有多是仔细考虑后的结果。
一些API发生的可预见性的变化,咱们能够提早检测并处理,如代码清单14.1所示。例如,Microsoft在 IE 9引入对DOM 2 的事件处理机制(使用addEventListener方法绑定事件),而过去的IE版本使用IE内置的attachEvent方法。对于 IE 9以前的代码,使用简单的特性检测能够处理这种变化。
清单14.1 预期即将发生的API变化
function bindEvent(element, type, handle) { if (element.addEventListener) { element.addEventListener(type, handle, false); ⇽--- 使用标准API绑定 } else if (element.attachEvent) { element.attachEvent("on" + type, handle); ⇽--- 使用专有API } }
在本例中,不会过期的代码提早预知Microsoft将在IE浏览器中引入DOM标准。使用特性检测来判断浏览器是否支持标准API,若支持,则使用addEventListener方法。若是不支持,则检测是否支持attachEvent方法。
大部分将来的API变化是不容易预测到的,而且没法预测将来的bug。这是本书强调测试的最重要缘由之一。面对不可预期的变化对咱们代码的影响,最佳实践是在浏览器发行的版本中模拟测试,以快速发现问题。
使用优秀的测试套件并密切关注即将发行的浏览器版本是处理将来的退化问题的最佳方式。不是在开发周期中,而是在平常测试中进行。在新发行的浏览器版本中运行的这些测试,应该分解到开发周期中进行。
从如下网站能够获取即将发行的浏览器信息。
勤奋很重要。由于咱们没法彻底预测浏览器将来可能产生的bug,可行的最佳方式就是对将来可能发生的状况时刻保持警戒。
浏览器厂商为了不回归问题的发生,作了不少事情。浏览器一般将JavaScript库的测试套件集成进浏览器测试套件中,确保将来的回归不会直接影响这些库。虽然没法覆盖全部的问题(确定没法彻底覆盖),但这是一个很好的开端,代表浏览器厂商在尽量地避免发生那样的状况。
在本节中,咱们介绍了开发可复用性代码时面对的4种主要问题:浏览器bug、浏览器bug修复、外部代码、浏览器回归。
本文摘自:JavaScript忍者秘籍(第2版)
[美] John Resig(莱西格),Bear Bibeault(贝奥特),Josip Maras(马瑞斯) 著
《JavaScript 忍者秘籍(第2版)》使用实际的案例清晰地诠释每个核心概念和技术。本书向读者介绍了如何掌握 JavaScript 核心的概念,诸如函数、闭包、对象、原型和 promise,同时还介绍了 JavaScript API, 包括 DOM、事件和计时器。你将学会测试、跨浏览器开发,全部这些都是高级JavaScript开发者应该掌握的技能。
本书包含如下内容:
在本节中,咱们介绍了开发可复用性代码时面对的4种主要问题:浏览器bug、浏览器bug修复、外部代码、浏览器回归。