迷你MVVM框架avalon在兼容旧式IE做的努力
很多时候,写代码就像砌砖头,只要我们不关心盖楼的原因、建筑的原理、土木工程基础和工程经验,就算我们砌了100栋高楼,我们也就只是一个砌砖工人,永远也成为不了一个工程师,更别说建筑师了。而那些包工头也只会把我们当成劳动力罢了。——左耳朵耗子

avalon在兼容旧式IE上做了大量工作,从而让它更接地气,完美地运行于国内的各种奇葩浏览器中。
首先是Object.defineProperties的模拟,正因为有这东西,才能让avalon是纯事件驱动地同步视图,而不用脏检测,从而获得更高的性能。
//IE6-8使用VBScript类的set get语句实现 if (!defineProperties && window.VBArray) { window.execScript([ "Function parseVB(code)" , "\tExecuteGlobal(code)" , "End Function" ].join( "\n" ), "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, "\tPrivate [__data__], [__proxy__]" , "\tPublic Default Function [__const__](d, p)" , "\t\tSet [__data__] = d: set [__proxy__] = p" , "\t\tSet [__const__] = Me" , //链式调用 "\tEnd Function" ) //添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好 for (name in normalProperties) { buffer.push( "\tPublic [" + name + "]" ) } buffer.push( "\tPublic [" + 'hasOwnProperty' + "]" ) //添加访问器属性 for (name in accessingProperties) { if (!(name in normalProperties)) { //防止重复定义 buffer.push( //由于不知对方会传入什么,因此set, let都用上 "\tPublic Property Let [" + name + "](val" + expose + ")" , //setter "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")" , "\tEnd Property" , "\tPublic Property Set [" + name + "](val" + expose + ")" , //setter "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")" , "\tEnd Property" , "\tPublic Property Get [" + name + "]" , //getter "\tOn Error Resume Next" , //必须优先使用set语句,否则它会误将数组当字符串返回 "\t\tSet[" + name + "] = [__proxy__]([__data__],\"" + name + "\")" , "\tIf Err.Number <> 0 Then" , "\t\t[" + name + "] = [__proxy__]([__data__],\"" + name + "\")" , "\tEnd If" , "\tOn Error Goto 0" , "\tEnd Property" ) } } buffer.push( "End Class" ) //类定义完毕 buffer.push( "Function " + className + "Factory(a, b)" , //创建实例并传入两个关键的参数 "\tDim o" , "\tSet o = (New " + className + ")(a, b)" , "\tSet " + className + "Factory = o" , "End Function" ) window.parseVB(buffer.join( "\r\n" )) //先创建一个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 = /<\w+\b(?:([ "'])[^" ]*?(\1)|[^>])*>/i var getAttributes = function (elem) { if (elem.outerHTML.slice(0, 2) == "</" ) { //处理旧式IE模拟HTML5新元素带来的伪标签 return [] } var str = elem.outerHTML.match(rtag)[0] var attributes = [], match, k, v; if (cacheAttr[str]) { return cacheAttr[str] } while (k = rattrs.exec(str)) { v = k[2] var name = k[1].toLowerCase() match = name.match(rmsAttr) var binding = { name: name, specified: true , value: v ? rquote.test(v) ? v.slice(1, -1) : v : "" } attributes.push(binding) } return cacheAttr(str, attributes) } } |
avalon允许使用script, noscript, textaea作为子模块的容器,但script节点需要修改type属性,textarea要手动display:none,noscript无疑是最好的选择,但noscript在IE78中竟然抽风了,在chrome下也有坑。avalon被逼又正则一番了……
var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML var xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以 xhr.open( "GET" , location, false ) //谢谢Nodejs 乱炖群 深圳-纯属虚构 xhr.send( null ) //http://bbs.csdn.net/topics/390349046?page=1#post-393492653 var noscripts = DOC.getElementsByTagName( "noscript" ) var array = (xhr.responseText || "" ).match(rnoscripts) || [] var n = array.length for ( var i = 0; i < n; i++) { var tag = noscripts[i] if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的 tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug tag.fixIE78 = (array[i].match(rnoscriptText) || [ "" , " " ])[1] } } } |
A,IMG标签的src, href路径的转义,这个真是够隐秘啊!
if (!W3C && (method === "src" || method === "href" )) { val = val.replace(/&/g, "&" ) //处理IE67自动转义的问题 } |
oninput事件在IE6-9的兼容问题:
if (W3C) { //先执行W3C element.addEventListener( "input" , updateVModel) data.rollback = function () { element.removeEventListener( "input" , updateVModel) } } else { removeFn = function (e) { if (e.propertyName === "value" ) { updateVModel() } } element.attachEvent( "onpropertychange" , removeFn) data.rollback = function () { element.detachEvent( "onpropertychange" , removeFn) } } if (DOC.documentMode === 9) { // IE9 无法在切剪中同步VM var selectionchange = function (e) { if (e.type === "focus" ) { DOC.addEventListener( "selectionchange" , updateVModel) } else { DOC.removeEventListener( "selectionchange" , updateVModel) } } element.addEventListener( "focus" , selectionchange) element.addEventListener( "blur" , selectionchange) var rollback = data.rollback data.rollback = function () { rollback() element.removeEventListener( "focus" , selectionchange) element.removeEventListener( "blur" , selectionchange) } } } |
此外还有许多许多,但都是见诸于jQuery源码的常见问题,我就不便贴出来了,它们的实现也与jQuery的相差无几。可见兼容旧式IE是多么头痛纠结的一件事。但由于OA的要求,甲方的要求,公司上头的要求,我们总是奔于疲命。jQuery帮我们搞定了浏览器的兼容问题,但业务上的复杂性,让我们的代码在DOM与业务逻辑上两头跳。用了avalon后,我们就能从搬砖似的DOM操作上解放出来,研究设计模式,算法,分层架构等具有更高附加值的东西。从码农到工程师到架构师的道路迈进!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端