迷你MVVM框架avalon在兼容旧式IE做的努力
很多时候,写代码就像砌砖头,只要我们不关心盖楼的原因、建筑的原理、土木工程基础和工程经验,就算我们砌了100栋高楼,我们也就只是一个砌砖工人,永远也成为不了一个工程师,更别说建筑师了。而那些包工头也只会把我们当成劳动力罢了。——左耳朵耗子
avalon在兼容旧式IE上做了大量工作,从而让它更接地气,完美地运行于国内的各种奇葩浏览器中。
首先是Object.defineProperties的模拟,正因为有这东西,才能让avalon是纯事件驱动地同步视图,而不用脏检测,从而获得更高的性能。
//IE6-8使用VBScript类的set get语句实现
if (!defineProperties && window.VBArray) {
window.execScript([
"Function parseVB(code)",
" ExecuteGlobal(code)",
"End Function"
].join("
"), "VBScript")
function VBMediator(accessingProperties, name, value) {
var accessor = accessingProperties[name]
if (arguments.length === 3) {
accessor(value)
} else {
return accessor()
}
}
defineProperties = function(name, accessingProperties, normalProperties) {
var className = "VBClass" + setTimeout("1"),
buffer = []
buffer.push(
"Class " + className,
" Private [__data__], [__proxy__]",
" Public Default Function [__const__](d, p)",
" Set [__data__] = d: set [__proxy__] = p",
" Set [__const__] = Me", //链式调用
" End Function")
//添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好
for (name in normalProperties) {
buffer.push(" Public [" + name + "]")
}
buffer.push(" Public [" + 'hasOwnProperty' + "]")
//添加访问器属性
for (name in accessingProperties) {
if (!(name in normalProperties)) { //防止重复定义
buffer.push(
//由于不知对方会传入什么,因此set, let都用上
" Public Property Let [" + name + "](val" + expose + ")", //setter
" Call [__proxy__]([__data__], "" + name + "", val" + expose + ")",
" End Property",
" Public Property Set [" + name + "](val" + expose + ")", //setter
" Call [__proxy__]([__data__], "" + name + "", val" + expose + ")",
" End Property",
" Public Property Get [" + name + "]", //getter
" On Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回
" Set[" + name + "] = [__proxy__]([__data__],"" + name + "")",
" If Err.Number 0 Then",
" [" + name + "] = [__proxy__]([__data__],"" + name + "")",
" End If",
" On Error Goto 0",
" End Property")
}
}
buffer.push("End Class") //类定义完毕
buffer.push(
"Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数
" Dim o",
" Set o = (New " + className + ")(a, b)",
" Set " + className + "Factory = o",
"End Function")
window.parseVB(buffer.join("
")) //先创建一个VB类工厂
return window[className + "Factory"](accessingProperties, VBMediator) //得到其产品
}
option元素的value值的提取。在规范中,如果用户没有显式定义value,则会对其innerHTML进行两边对空白操作,作为value值。但如何判定用户是否显示定义value值呢,IE67是没有hasAttribute方法,此外还有其他兼容问题,而jQuery的做法太罗索。看avalon的实现:
var roption = /^<option(?:s+w+(?:s*=s*(?:"[^"]*"|'[^']*'|[^s>]+))?)*s+value[s=]/i
var valHooks = {
"option:get": function(node) {
//在IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作)
if (node.hasAttribute) {
return node.hasAttribute("value") ? node.value : node.text
}
//specified并不可靠,因此通过分析outerHTML判定用户有没有显示定义value
return roption.test(node.outerHTML) ? node.value : node.text
},
//.....
}
旧式IE下高性能获对所有绑定属性:
//IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支,
//但如果我们去掉scanAttr中的attr.specified检测,一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面
if (!"1" [0]) {
var cacheAttr = createCache(512)
var rattrs = /s+(ms-[^=s]+)(?:=("[^"]*"|'[^']*'|[^s>]+))?/g,
rquote = /^['"]/,
rtag = /