理论上如果浏览器的库功能足够强大,是完全可可以代替第三方的库。事实上各家浏览器都在不断增强自家浏览器的特性一些群众呼声较高的功能或者 API 已经早早加入到浏览器标准规范中,直接调用即可。问题是,许多客户因为种种原因没有升级它们的浏览器,造成开发者面对此类的问题时候,只能采取第三方库或“打补丁”的方式兼容,所谓”Polyfill“ 正是如此。笔者较倾向于“打补丁”的方式,也就是编写补丁的时候,其接口尽量与标准规范的一致。这样的好处当然是兼容了新的通用方式,API 更显一致。
首先是调试用的方法 console.log()/dir()/warn()。代码如下。
if(!this.console){ // 用 this 代替 window 更通用,可在一些非浏览器的 js 环境中使用,例如 ASP in JScript this.console = { log : alert } } if(!console.dir){ console.dir = console.log; }
不使用 new 创建对象,就应该使用 Object.create()。该方法最早由 D.C 提出,然后被纳入 ECMAScript 语言标准,这也是笔者所极力推荐的方法。
if (!Object.create){ Object.create = function (o) { if (arguments.length > 1) { throw new Error('Object.create implementation only accepts the first parameter.'); } function F() {} F.prototype = o; return new F(); }; }参见本博客系列文章 《OO思想(js)》。
无须多讲,这方法很常用。但奇怪的是,好像没有被纳入语言 API 的迹象。
String.prototype.format = function () { var str = this; if(typeof(arguments[0]) == 'string'|| typeof(arguments[0]) == 'number'){ for(var i = 0, j = arguments.length; i < j; i++){ str = str.replace(new RegExp('\\{' + i +'\\}', 'g'), arguments[i]); } }else{ for(var i in arguments[0]){ str = str.replace(new RegExp('\\{' + i +'\\}', 'g'), arguments[0][i]); // 大小写敏感 } } return str; }
视各场合而用。
参见本博客文章《JavaScript自定义日期格式化函数》。
Function.prototype.bind 很实用。笔者的方案依赖于 delegate,除了指定作用域外还能指定函数的参数。
if(!Function.prototype.bind){ Function.prototype.bind = function (scope) { this.scope = scope; return this.delegate(); } }delegate 参见源码: http://naturaljs.googlecode.com/svn/trunk/src/dhtml/prototype.js。
参见本博客文章《我对 Javascript 原型的扩展函数》。
IE8+ 很及时地对 JSON 支持,这点很好。
if(!window.JSON)window.JSON = { // 加入 JSON 包 };
IE7或以下的必须依赖其他包
<!--[if lte IE 7]> <script src=""<%=bigfootUrl%>libs/json2.js"></script> <![endif]-->
<!--[if lte IE 8]> <script src="<%=bigfootUrl%>libs/html5shim.js"></script> <![endif]-->
// for IE8- if(!window.addEventListener){ /** * @param {String} eventName 事件名称,不要带 on 开头 * @param {Function} eventHandler 事件处理器 */ window.addEventListener = Element.prototype.addEventListener = function(eventName, eventHandler){ this.attachEvent('on' + eventName, eventHandler); } } // for IE8- if(!window.XMLHttpRequest); // see request.js // for IE7- 不报错 if(!window.Element)window.Element = function(){};
/** * 一般来讲我们不通过 style.xxx 来获取元素的样式。IE8 不支持,IE9开始支持。 * http://www.w3help.org/zh-cn/causes/BT9008 */ if (!window.getComputedStyle) { window.getComputedStyle = function (el) { return el.currentStyle; } } if(!window.postMessage){ console.warn('不支持 postMessage'); window.postMessage = function(event){ var e = { data : event }; render(e); } }
如浏览器不支持 el.classList 属性,设置一个,例如这样:
;(function(){ var el = document.createElement('div'); if(!el.classList){ Element.prototype.classList = { }; } })();
参见本博客文章《浏览器已经支持 className 的一些方法了 》。
如浏览器不支持 HTML5 data-* 属性,也可以设置一个。但这种方法有入侵原生方法之嫌。
源码详见:https://code.google.com/p/naturaljs/source/browse/trunk/src/webview/patch.js
参见本博客文章《自动获取HTML5 的 data-* 属性》。
参见本博客文章《记一次 JS 模拟 CSS active 效果的过程 》。
源码详见:https://code.google.com/p/naturaljs/source/browse/trunk/src/webview/patch.js
这个方法事件是个事件。在早期的时候,DOMReady 很受用。但是笔者现在的习惯就是,先写 HTML 再写 Script,让这个顺序固定好,DOMReady 使用的机会就不多了。当然不好就是 Script 都写成一块一块的较分散,不能写在头部的 <head> 中。
/* * 注册浏览器的 DOMContentLoaded 事件,@todo 应有自我卸载的功能。 * @param {Function} onready [必填]在DOMContentLoaded事件触发时需要执行的函数 */ document.onReady = function(f){ if(/in/.test(document.readyState)){ window.setTimeout(arguments.callee, 9, f); }else{ f(); } }
见过其他的实现,代码行数较多。这个就很短小,原理较巧妙(当然也要注意 document.readyState 浏览器是否支持),值得推荐。最后 f 是对 arguemnts.callee 的传参,应用到了 setTimeout/ setInterval 的新写法。同样这种新写法也有补丁。参见下面。
曾几何时,对 setTimeout/ setInterval 如何传递参数是童鞋们一大关心的问题。如果了解 JS 函数和事件队列,那么这个问题迎刃而解。不过,话又说回来,如果 API 直接提供传参功能,岂不是很好?恰恰就是这种想法,新版的浏览器提供了。要写旧补丁的话,可参见如下的。
/*\ |*| IE-specific polyfill which enables the passage of arbitrary arguments to the |*| callback functions of javascript timers (HTML5 standard syntax). |*| https://developer.mozilla.org/en-US/docs/DOM/window.setInterval |*| Syntax: |*| var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]); |*| var timeoutID = window.setTimeout(code, delay); |*| var intervalID = window.setInterval(func, delay[, param1, param2, ...]); |*| var intervalID = window.setInterval(code, delay); \*/ ;(function(){ if (document.all && !window.setTimeout.isPolyfill) { var __nativeST__ = window.setTimeout; window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { var aArgs = Array.prototype.slice.call(arguments, 2); return __nativeST__(vCallback instanceof Function ? function () { vCallback.apply(null, aArgs); } : vCallback, nDelay); }; window.setTimeout.isPolyfill = true; } if (document.all && !window.setInterval.isPolyfill) { var __nativeSI__ = window.setInterval; window.setInterval = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { var aArgs = Array.prototype.slice.call(arguments, 2); return __nativeSI__(vCallback instanceof Function ? function () { vCallback.apply(null, aArgs); } : vCallback, nDelay); }; window.setInterval.isPolyfill = true; } })();
附:《兼容的浏览器原生功能支持》http://goojs.com/polyfill.html