迷你MVVM框架avalon在兼容舊式IE作的努力

不少時候,寫代碼就像砌磚頭,只要咱們不關心蓋樓的緣由、建築的原理、土木工程基礎和工程經驗,就算咱們砌了100棟高樓,咱們也就只是一個砌磚工人,永遠也成爲不了一個工程師,更別說建築師了。而那些包工頭也只會把咱們當成勞動力罷了。——左耳朵耗子

avalon在兼容舊式IE上作了大量工做,從而讓它更接地氣,完美地運行於國內的各類奇葩瀏覽器中。css

首先是Object.defineProperties的模擬,正由於有這東西,才能讓avalon是純事件驅動地同步視圖,而不用髒檢測,從而得到更高的性能。node

 //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的實現:git

    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下高性能獲對全部綁定屬性:github

    //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路徑的轉義,這個真是夠隱祕啊!chrome

  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操做上解放出來,研究設計模式,算法,分層架構等具備更高附加值的東西。從碼農到工程師到架構師的道路邁進!數組

相關文章
相關標籤/搜索