迷你MVVM框架avalon在兼容旧式IE做的努力

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

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操作上解放出来,研究设计模式,算法,分层架构等具有更高附加值的东西。从码农到工程师到架构师的道路迈进!

你可能感兴趣的:(val)