编写自己的跨浏览器polyfill

我相信,作为设计师和开发人员,我们有责任倡导最佳实践,并鼓励其他人在现代网络中使用现代功能。与此同时,我们需要尽最大努力避免让用户使用旧浏览器。Polyfills- 由Remy Sharp创造的一个术语,用于描述复制新浏览器本机功能中的标准API的JavaScript填充程序,以帮助我们实现这一目标。

在我看来,我们不应该决定不使用功能提供的功能,因为它在某些浏览器中不是本机支持的。我们应该以此为基础考虑其他因素,例如价值和绩效。 Polyfills意味着无论您定位的是哪种浏览器,都可以自由选择使用功能的正确理由。

在这篇文章中,我将重述我创建跨浏览器polyfill的经验以及沿途的经验教训。我还将为您提供一些技巧,说明如何创建自己的polyfill并避免开发人员在第一次编码时经常遇到的一些麻烦。

为什么开发人员会写polyfill?

编写polyfill是一个很好的机会,可以更多地了解浏览器中功能实现之间的细微差别,但我们大多数人编写它们的真正原因是因为我们有真正的需求并希望知道它是否可以完成。

我们希望能够说'这是真正令人惊叹的新功能,但我们现在已经可以在生产中使用它,而不必担心跨浏览器的兼容性'。网络上的包容性对我们很重要。

我不能代表所有的开发人员,但如果我不得不猜测我会想象大多数polyfill作者更喜欢能够使用当前的功能,而不是等待一两年的规范最终确定。这就是这种思维方式所带来的Html5shivCSS3Pie以及为更大规模的现代网络技术铺平道路的其他解决方案。

有时,polyfill开发人员只需要真正原生支持的行为的一部分,而在其他人我们可能希望填充整个功能集。我们总是可以尽力实现后者,但正确地将其正确地提高到100%通常具有挑战性,因为许多开发人员都会证明这一点。

因为喜欢GitHub上谷歌代码,这很少是一个问题。感谢一个充满活力的开发社区,如果我们最终创建一个广泛使用的polyfill,其他贡献者总是有机会帮助它达到polyfill和native之间的差异可以忽略不计的程度。

入门指南

如上所述,大多数选择编写polyfill的开发人员都是这样做的,因为它满足了个人需求。但是,如果您发现自己有足够的空闲时间并希望向社区提供填充物,那么您可以遵循一些有用的指导方针来开始使用。

首先,选择当前仅在现代浏览器中支持的功能,而不是旧版浏览器,并查看是否已存在填充。

保罗爱尔兰保持一个活跃的wiki在Modernizr回购中,目前的polyfill和垫片,我强烈建议通过。对于当前稳定版本的Chrome,Opera,Safari和Firefox中的许多功能,可能存在适用于旧版浏览器的常用polyfill。但是,在新的边缘浏览器中可用的功能要少得多Opera.next铬夜莺

在一天结束时,即使确实存在针对特定功能的polyfill,如果您觉得您在实施解决方案方面做得比可用的更好,那么请不要犹豫不决。

清单上的下一个重要事项应该是查看您选择的功能的官方规格。您应该这样做有很多原因,包括更好地理解该功能应该如何工作,更重要的是,官方“原生”API描述的提供方法和属性。除非您觉得有必要从这些规范中大大抽象,否则您将希望尽可能地遵循此模型。

还有许多其他问题需要考虑,如性能,价值,加载机制等,但我们很快就会涉及这些问题。

将这些想法付诸实践

我决定写一个功能填充工具因为是'页面可见性API”。 API是W3C页面可见性的实验性实现草案该功能提供了一些特别有用的功能。如果开发人员希望他们的站点在用户决定从当前活动选项卡(您的站点)切换到另一个活动选项卡时做出反应或表现不同,则API会使可见性更改非常直接地进行检测。

我们在处理需要为两种不同类型的浏览器创建解决方案的情况方面经验丰富:

  1. 现代网络浏览器和
  2. 旧版浏览器。

使用Page Visibility API,当时只有两个浏览器本身支持它,并且它们实际上并不属于上述任何一种情况。第一个(Chromium 13+)是谷歌Chrome的beta频道版本,而第二个(IE10PP2)仍在平台预览短语中。这意味着我实际上还需要迎合第三种情况:3)最前沿的浏览器。

此类别超越了现代浏览器,因为通常只有少数开发人员积极使用它们。但是,这并没有让我无法使用可在所有浏览器中使用的Page Visibility API的polyfill。因为3)已经对该功能有本机支持,所以通过polyfill来满足对它的调用是相对简单的。

然而,如果您决定在将来编写自己的polyfill,那么在其他浏览器中对该功能的回填功能确实会突出显示一些有用的警告和课程。其中一些提示可能相当简单,而其他提示恰恰相反!

特征检测

前缀

当浏览器供应商决定实施尚未最终确定的功能或标准时,他们通常会使用特定于其浏览器的前缀来发布它们。 Chrome和Safari依赖于WebKit渲染引擎,使用'webkit',Firefox使用'moz',Opera'o',Microsft'ms',还应该考虑非前缀的情况(即功能最终确定时)标准机构,并实施这样的)。根据您尝试编写polyfill的内容,您可能会找到一个前缀测试器,例如我在下面帮助编写的前缀测试器。

/ ** vendorPrefix.js  - 版权所有(c)Addy Osmani 2011. * http://github.com/addyosmani*测试特定上下文中浏览器属性的本机支持*如果支持,将返回一个值。* / function getPrefix(prop,context){var vendorPrefixes = ['moz','webkit','khtml','o','ms'],upper = prop.charAt(0).toUpperCase()+ prop.slice( 1),pref,len = vendorPrefixes.length,q = null; while(len--){q = vendorPrefixes [len]; if(context.toString()。indexOf('style')){q = q.charAt(0).toUpperCase()+ q.slice(1); } if((q + upper)in context){pref =(q); if(prop in context){pref = prop; } if(pref){return' - '+ pref.toLowerCase()+' - '; } return'';} // LocalStorage testconsole.log(getPrefix('localStorage',window)); //页面可见性APIconsole.log(getPrefix('hidden',document)); // CSS3 transformsconsole.log(getPrefix( 'transform',document.createElement('div')。style)); // CSS3 transitionsconsole.log(getPrefix('transition',document.createElement('div')。style)); //文件API测试(非常基本测试,理想情况下也要检查'文件')console.log(getPrefix('FileReader',window));

有两种不同的方法可以针对特定功能进行前缀测试。

如果您希望在其他浏览器供应商提前实现功能的情况下为您的polyfill提供面向未来的证明,则可以针对上述所有前缀进行测试。但是,如果您打算长期维护您的polyfill,您可以测试当前已知支持您正在填充的功能的浏览器的前缀(在我的例子中是WebKit / Chrome和IE)。它实际上是一种微观优化,以避免不必要的测试,但是在一天结束时,您完全可以选择选择哪种。

支持

然而,建立供应商前缀通常只是检测未终结特征的特征的第一步。在本节中,我将讨论功能检测最终确定和未确定功能,因为这可能会带来更大的好处。可以采用多种方法来检测是否支持某项功能,并非所有功能都适用于所有情况:

这些包括测试:

  • 如果当前窗口中存在(即支持)要素的属性
  • 如果当前文档中存在该要素的属性
  • 如果要素的属性或其实例可以创建为当前文档中的新元素
  • 如果特征的属性或属性存在于特定元素中(例如“占位符”属性)。

这绝不是一份全面的清单。但是,如果您想了解有关功能测试的更多信息,我建议您同时查看Modernizr和Has.js功能检测测试,因为它们充满了有关如何正确接近这些测试的有趣示例。

标准组织最终确定的浏览器功能通常会导致属性,属性和方法名称不附加任何供应商前缀。在上面的前缀测试中,这就是为什么我还包括一个测试,以查看文档中是否存在没有前缀的测试的直接属性。

正如我们所见,未定义的特征通常会在其属性之前出现前缀。要使用前缀测试边缘要素,在某些情况下,必须使用供应商已实现该功能的相应浏览器前缀“前缀”属性或事件的名称。

在大多数情况下,这只是意味着在我们之前检测到的前缀之前。但是,检查任何可用的供应商文档以确定是否需要记住任何警告非常重要。例如,在某些情况下,最终特征的命名是elem.something,而这可能是因为elem.mozSomething等早期实现的驼峰。

除此之外,唯一需要记住的是,您的功能测试绝对需要跨浏览器工作。考虑到上述测试的例子,这似乎是一个微不足道的观点。但是,通过某些功能,这些功能会增加复杂性,而您最不希望看到的是功能检测过程。

JavaScript:古怪的部分

polyfill开发人员面临的最大挑战通常涉及规范浏览器之间功能实现的差异。这也适用于语言实现级别的差异,特别是JavaScript。必须记住,EcmaScript 4和5的实现经常在不仅是浏览器之间存在差异,而且在这些浏览器的版本之间也存在差异(这里最糟糕的攻击者当然是Internet Explorer)。

幸运的是,如果您在特定于供应商的JavaScript问题上有点生疏,可以使用许多资源作为参考点。该MDN,官方规格,QuirksMode.orgDottoro当我在我的polyfill工作时特别有用。堆栈溢出当然是另一个很好的资源,但请记住不要将任何列出的建议视为理所当然:社区提出的测试建议或假设,以确保它们实际上是正确的。

我并不羞于说我最终重构了我的polyfill总共八到九次。在许多情况下,这是我忘记的非常小的事情,因为多年来我已经习惯了使用Dojo,jQuery和其他库。在幕后,这些库和框架规范了IE的大部分怪癖,因此开发人员可以屏蔽它们。但是,我们仍然必须了解可能影响实施解决方案所需时间的限制。

编写polyfill有时候相对微不足道,而在其他情况下,你会痛苦地想起为什么重要的是我们要达到所有浏览器在所有基础上共享或多或少相同级别的标准合规性的重要性。

例如,您是否知道在撰写本文时,Chrome 13+和Firefox 4+是唯一完全符合ES5标准的两种主要浏览器? (一个支持表总结兼容性可供参考)。这意味着如果开发人员希望在他们的应用程序中使用ES5功能,他们将需要包括垫片,如ES5垫片这为非功能完整的浏览器提供了相同的功能(对于传统浏览器而言,这比那些不支持的浏览器更严重,'严格'模式)。

对我来说,最大的问题是它直接影响了我的实现规模,所以最后我选择坚持使用ES4方法来解决我的问题。如果这是一个成熟的应用程序,我可能不会那么担心,但是polyfill需要尽量不要给任何人的页面添加不必要的重量。

来自战壕的故事

具体到我的polyfill,有一个特别的故事可能是有趣的。我们都知道addEventListener()方法允许您在浏览器(如文档或窗口)中的单个目标上注册事件侦听器。它相当容易使用。虽然在Firefox,Chrome,Safari和Opera中完全支持,但它仅适用于IE9及更高版本。这意味着必须使用替代(Microsoft的attachEvent())方法来代替旧版本的IE。这通常只意味着一些额外的代码行来测试使用哪个选项。

当您想要触发可以跨浏览器监听的事件时,会出现真正的问题,特别是如果解决方案中存在动态元素。如果您希望激活诸如“onPageVisible”之类的自定义事件,那么使用createEvent(),initEvent()和dispatchEvent()方法的大多数浏览器都表现良好。但是,IE要求你选择createEventObject()和fireEvent():这引起了我各种麻烦,因为我想让开发人员以跨浏览器的方式轻松地监听可见性事件*而不需要实现一层抽象来规范化IE的怪癖。由于时间限制,这个想法消失了。

如果您正在实施相对复杂的行为,那么获得一个简洁,一致的解决方案可能会遇到挑战,该解决方案与Microsoft的同等解决方案同样适用。

出于这个原因,请尽量花一些时间更好地规划出更好的解决方案,因为从长远来看,这将为您节省大量的调试时间。

性能

作为网络的经验丰富的拥护者,我们都知道优化网站性能的一些基本规则:使用更少的HTTP请求,缩小脚本和样式表等等。遗憾的是,开发人员和设计人员没有那么多的经验来测试他们的JavaScript代码。由于大多数polyfill严重依赖于JavaScript,因此我们必须对代码进行压力测试,以避免将慢速,不完整的例程引入其他人的页面,因为这可能会抵消我们的polyfill所带来的好处。

现在,随着在线工具的可用性,测量脚本的性能比以往任何时候都要简单得多JsPerf.com(创作Mathias BynensJD道尔顿),这是由Benchmark.jsBrowserScope。 JsPerf允许开发人员为他们的JavaScript代码段创建测试,然后可以由任何拥有Web浏览器的人共享和运行。然后汇总这些测试的结果,以全面了解片段的执行情况。

jsPerf执行重复创建的每个测试,直到达到测量百分比不确定度百分比所需的最小时间。创建测试时,您将看到一个表和一个列下面带有“ops / second”标签的列:这是指测试在一秒钟内执行的次数。使用jsPerf时可能遇到的迭代次数差别很大,但测试通常运行至少五秒或五次运行,两者都可以配置。数字越大越好,最快的片段通常会显示为绿色。

那么,我们如何对polyfill进行性能测试呢?

这实际上取决于我们如何构建代码,但通常可以为已编写的每个主要函数或方法创建测试,并测试以确定实现是否存在明显的问题,例如浏览器之间的性能统计数据存在显着差异。

请记住,具有原生支持的浏览器的数字可能并且可能会高于已填充的浏览器的数字,因此最好将重点放在现代和旧版浏览器的基准测试上。

如果在GitHub上已经存在polyfill的替代品,你可能会发现比较两者的性能是有用的,以确定是否有一个明显的区域,你的实现可以使用一些工作,反之亦然。在一天结束时,我们希望创建能够提供价值并且表现良好的解决方案,jsPerf可以为您提供帮助。

考虑价值

在决定编写polyfill时,我们并不总是明白的一个考虑因素是它提供的价值是否值得注意:

  • 我们实施和测试解决方案的时间(以及其他人的时间)
  • 其他开发者将其添加到他们的网站
  • 维护解决方案直到大多数用于访问Web的浏览器都是现代的并支持您本机填充的功能(如果您选择这样做)

值得我们花时间吗?

关于这些要点中的第一点,如果您实施polyfill的原因是因为您需要在生产级别(无论是在工作中还是在个人项目中),那么您可能需要证明所花费的时间是合理的。它是否会提供可以改善用户体验的功能。

如果您希望使用的功能已经存在polyfill,您可能希望考虑解决方案是否满足您的需求,提供过多(可能会影响解决方案的大小)或者您是否只是喜欢不同的方法。
请记住,您始终可以选择分叉现有解决方案并根据需要对其进行修剪。

在我的情况下,我知道(通过Modernizr Polyfill页面)已经存在Page Visibility API的polyfill。然而,在对其进行审核之后,我得出结论,我只需要它提供的内容的一部分,并且可能为我的需求设置一个解决方案(以我自己喜欢的方式),减少约50%的代码行。我写这种填充剂的理由既是为了学习,也是为了在生产水平上使用,如果它被证明足够稳定的话。

它对其他开发者来说足够好用吗?

确定您的polyfill是否值得其他开发人员使用是一个非常有趣的两难选择。作为好奇的开发人员,我们经常为了实验而创建随机片段或GitHub要点,但理想情况下应该是可靠的polyfill:

  • 为开发人员提供优于他们可以在短时间内编码自己的解决方案(即价值)
  • 以最佳方式执行(参见性能测试部分)
  • 不消耗相当大的空间:如果总体增值值可以忽略不计,很少有开发人员会使用100KB的polyfill
  • 有合理的记录
  • 理想情况下,自带单元测试

我见过很多广泛使用的polyfill,它们不一定能解决最后两点。但是,如果开发人员没有足够的带宽来记录他们的解决方案或为其编写单元测试,开发人员至少应该确保他们的代码易于阅读。

我们真的可以维持吗?

在发布开源解决方案时,这被视为一个重要因素。但是,我建议您将这些内容记录在一般的网络上。

每周,我(和许多其他人一样)收到许多希望使用我过去编写过的插件或脚本的开发人员的电子邮件。在大多数情况下,他们也在寻找特定的,定制的或全新的东西。遗憾的是,我并不总是有空闲时间来协助大多数这些请求。

我犯的错误是我没有考虑如果我写的东西变得“流行”会发生什么。我想在这里分享的教训是,如果您觉得自己没有足够的带宽来解决与代码相关的未来问题或请求,请务必明确说明您可以获得的维护和支持级别。项目页面或回购自述文件。

这将使开发人员知道,他们更有意义分叉您的解决方案或提交新功能的拉取请求,而不是留下可能没有被追赶的评论或设置他们可能能够处理新功能的期望任何时候。这只是一种礼貌。

尽管我已经强调了最后一点,但不要以任何方式阻止你编写自己的polyfill。即使您无法长期为其提供支持,这也非常值得学习。

加载机制

有许多有效的机制可以加载polyfill脚本以供使用。但是,考虑如何通常加载它们以免影响实现可能是有益的。我个人用Yepnope.js(一个条件脚本加载器)用于加载我的,虽然有很多选择。

在其核心,yepnope提供了一种简单的方法来定义条件的测试,必须满足这些条件才能加载特定的脚本。如果测试结果为真(即'yep'),则可能会加载'natively-supported.js',而false('nope')可能会加载'polyfill.js'。

请记住,在许多情况下,自封装的polyfill通常会有自己的测试来确定某个功能是否是本机支持的,或者需要使用polyfill例程。如果您选择拦截API的本机API调用并在API周围提供通用的抽象层,例如,为了提供具有略微不同的方法名称的跨浏览器功能集,您实际上已经创建了一个垫片而不是polyfill。我已经看到开发人员经常交换这些术语,所以尽量不要过多地使用命名约定。

yepnope从性能角度来看很吸引人,因为它提供了在polyfill /垫片本身被加载之前在链中更高的位置执行特征检测的机会。这使得开发人员决定:a)解决方案是否应包含自己的功能检测测试?b)是否应依赖用户定义自己的功能?或者c)它应该依靠Modernizr之类的东西进行测试吗?

在我看来,松散地构建您的polyfill以支持所有上述是最好的选择。我还没有机会用我的解决方案这样做。然而,雷米夏普存储polyfill是如何以宽松的方式定义功能测试的一个很好的例子:https://gist.github.com/350433(请注意,这个要点中的特征检测可以改进,因为规范状态错误可能只是简单地访问window.localStorage;但它仍然是一个很好的参考点)。如果你有机会对它进行检查,你会发现他的测试包围了polyfill,这意味着它们可以很容易地被删除并更换为更高的yepnope条件测试。

如果特征检测与您的polyfill实现非常相关(例如,事件和属性名称也需要前缀检测等),那么进行自己的测试没有任何问题。但是请注意,如果开发人员选择像yepnope这样的解决方案,他们最终可能会执行两次相同的测试(一次作为条件,再次在您的polyfill中)。有关如何最好地加载polyfill的指导原则的良好文档通常可以帮助避免许多与此相关的小问题。

文档的重要性

一旦我们发现自己受到时间限制,文档通常是首先脱离项目范围的事情之一。这适用于polyfill和任何开源项目一样多,但是如果其他设计人员或开发人员开始使用您的实现,那么尽量使文档比二等公民更重要。

许多polyfill和垫片是相对较短的解决方案,因此您可以做的第一件事就是确保您的代码易于阅读。优先考虑过度简洁代码的可读性,尽可能注释您的代码,这样无论开发人员的技能水平如何,他们都可以进行微小的调整而无需太多帮助。

您认为重要的文件是什么?希望使用您的polyfill的开发人员将非常有兴趣了解它遵循官方规范的程度。它是否提供规范定义的API的1:1表示?如果没有,它真正揭示了哪些方法或特征?该解决方案是否有任何警告?您是否提供任何可能使您的解决方案更适合自己编写的解决方案?

理想情况下,文档应该尝试回答用户可能对您的实施提出的最常见问题,如果做得好,可能会限制您可能收到的有关X或Y如何工作的问题数量。如果广泛使用并且应用于旧浏览器中的大量元素的填充物实际上会导致阻塞。不要让用户亲自发现这一点,而是充分测试您的解决方案,并对用户期望看到的性能类型保持开放态度。他们会非常感谢beild告诉你实施的局限性。

虽然与文档没有直接关联,但在您的回购或正式版本中还包括您的解决方案的缩小版本。这将允许开发人员立即发现文件大小的成本,将您的实现包含在他们的页面中(我在查看Modernizr Polyfill列表时经常发现自己正在检查)。

同样,您希望让开发人员尽可能轻松地决定您的解决方案是否值得使用。从长远来看,他们会感谢你的!

不可测试的

需要注意的一个有用信息是,有许多浏览器功能无法(或非常难以)检测到。这些包括HTML5 readyState和webforms UI datepicker。 Modernizr实际上维持着维基关于untestables的页面,我建议你查看。

这些功能难以可靠地检测,因为对它们的测试依赖于UA嗅探,浏览器推断和其他不太准确的方法。如果您正在接受挑战,您可以尝试使用跨浏览器方法来解决不可测试问题(如果是这样,请用您的调查结果更新维基),但如果您了解它们,请务必注意它们发现自己试图填充已被标记为此类的功能。 。

Polyfill测试

单元测试

总结一下,我将简要介绍一下从单元测试的重要性开始的polyfill测试。单元测试是一种测试脚本或应用程序的最小可测试部分的方法,并确保隔离的方法或功能按预期运行。您的polyfills的单元测试(无论您是否选择使用茉莉花QUnit或者他们的另一个测试框架)应该降低风险,易于运行,并且随着polyfill的发展或变化而易于维护。

但是,它们可靠地运行起来很棘手。例如,开发人员可能想知道他们是否应该尝试为应该能够在Chrome中运行的IE回退创建测试:在我看来,答案是否定的。如果您无法准确模拟在IE中直接正确匹配测试所需的事件,只需将测试分为边缘,现代和旧版浏览器,并确保每个测试集在这些浏览器中按预期运行。同样重要的是,您也可以正确访问并正在测试所有浏览器。

跨浏览器测试

我们假设所有开发人员都以相同的可靠方式跨浏览器测试他们的代码,但通常情况并非如此。

例如,我最近遇到了许多开发人员,他们认为IETester或IE的文档模式提供了在Windows上使用IE的专用版本时可能获得的相同的1:1渲染和脚本体验。遗憾的是,这很不正确。我过去曾对IETester和IE文档/浏览器模式进行了测试,结果与原始浏览器相比,两者的布局完全不同。

出于这个原因,我不建议使用任何一种来测试您的polyfill是否有效。您需要更可靠的东西来避免误报的风险。

那么,我认为理想的测试设置是什么?我个人用VirtualBox的适用于Mac的Windows 7图像适用于IE 6,8,9,10和所有其他现代浏览器。 IE9和10PP2目前可以独立运行,没有任何问题,但我使用的是6和8这些独立的IE可执行文件。你会注意到我从上面的列表中省略了IE7。对于IE7测试,您需要一个WindowsXP映像和原始IE7安装的副本(您可以通过Google相对轻松地找到它)。

virtualbox with IE10

听到以上所有设置可能听起来很痛苦。但是,一旦它们出现,您可以轻松地让VirtualBox在后台运行,随时为您提供服务。

结论

虽然有很多其他项目你可以贡献你的时间,如果polyfills感兴趣你,我肯定鼓励尝试写一个围绕浏览器引入的新功能。社区一直在寻找新的有用的解决方案,帮助打破边界以提供可访问性,正如我所说,这是一个很好的学习练习。

作为结束语,我要感谢Mathias Bynens和Remy Sharp对这篇文章的技术评论以及Paul Irish的许多资源,这些资源有助于简化我自己编写polyfill的经验。



翻译字数超限