JavaScript 框架設計(二)


JavaScript 高級框架設計 (二)

上一篇,JavaScript高級框架設計(一)我們 實現了對tag標簽的選擇

下來我們實現對id的選擇,即id選擇器.

我們將上一篇的get命名為getTag(),然后再編寫一個getId(),和getClass()

然后在總的get方法中調用,這樣做的好處就是模塊化,便於維護.
我所有的代碼都會托管到github上.

01.js

var getId = function (id, result) {
    result = result || [];
    //  由於獲取的Id是一個元素,所以這里使用call.
    result.push.call(result,document.getElementById(id));
    return result;
}

var getClass = function (className, result) {
    result = result || [];
    result.push.apply(result, document.getElementsByClassName(className));
    return result;
}

好了,現在分別實現了三個get方法,但是怎么根據選擇器表達式來調用者三個方法呢?


下面引出一個關鍵正則:

/^(?:#([\w-]+)|\.([\w-]+)|([\w]+)|(\*))$/

這段正則目的就是匹配id,類和標簽選擇器,並且通過exec可以拿到匹配的內容.

那么,

var m = expr.exec(selector);

m[1] 就表示id選擇器,m[2]就表示類選擇器,m[3]表示標簽選擇器,m[4]表示通用選擇器

下面的代碼就順理成章了.

01.js

var get = function (selector, result) {
    result = result || [];
    var rquickExpr = /^(?:#([\w-]+)|\.([\w-]+)|([\w]+)|(\*))$/,
        m = rquickExpr.exec(selector);
    if (!m) {
        return result;
    }

     if (m[1]) {
         result = getId(selector);
     } else if (m[2]) {
         result = getClass(selector);
     } else if (m[3]) {
         result = getTag(selector);
     } else if (m[4]) {
         result = getAll(selector);
     }
    return result;
};

如果沒有匹配到,就直接返回result,如果有匹配到,那么就一定是選擇器中的一種,通過分組進行判斷.

在對每個分組的判斷可以優化,

 if (m[1]) {
      result = getId(m[1], result);
  } else if (m[2]) {
      result = getClass(m[2], result);
  } else {
      result = getTag(m[3] || '*', result)
  }

現在這樣就已經可以完成對單獨選擇器的匹配了.

我們來進行測試一遍.

01.html

<body>
    <div>1</div>
    <div id="k">2</div>
    <div class="pawn">3</div>
    <p></p>
    <p></p>
</body>
<script src="01.js"></script>

01.js

onload = function(){
    each(get("p"),function(){
        this.style.backgroundColor = "green";
    })
    each(get("#k"),function(){
        this.style.backgroundColor = "red";
    });
    each(get(".pawn"),function () {
        this.style.backgroundColor = "pink";
    })
}

下面是結果,是不是歐了?

單獨的選擇器似乎已經完成,但是一個問題出現了? 有考慮過兼容性嗎?

document.getElementsByClassName() ie8以下是不兼容的!

下面引入兩個問題:

  • 怎么判斷兼容性?
  • 怎么解決不兼容?

怎么判斷兼容性?

我們通過chrome watch寫上document

然后查看getElementsByClassName() 方法.

經過不斷在原型鏈中搜索,

我們發現其嵌套很深,每次調用都在原型鏈中搜索.

當我們這樣的代碼在IE8 以下運行這樣的代碼時,

if(docuemnt.getElementsByClassName(className)){
  //
}

它會一直向上尋找,直到Object對象,效率很低.

如果按照上面的代碼,那么每調用一次就要就行一次能力檢測,效率肯定太低了.

怎么辦呢?

方法就是只做一次能力檢測,並把它記錄下來.

其中,jQuery中就是這么做的,它擁有一個專門的功能檢測模塊,$.support,里面的屬性包含存在兼容問題的方法或屬性,並用布爾值來記錄.

所以在我們這里,在全局作用域中(最終都要用成沙箱或者閉包)提供一個support對象,里面提供所有的以方法名相同的屬性,值均為布爾值,在瀏覽器加載js的開始的時候,就進行能力判斷,凡是涉及到能力檢測的時候就直接檢測support即可.

所以我們在代碼開頭加入

var support = {};
support.getElementsByClassName = !!document.getElementsByClassName;

那么以后再需要能力檢測的時候,可以這么寫:

if(support.getElementsByClassName){
    //...
}

似乎已經可以了.

但是,有一種攻擊叫做注入漏洞攻擊,我在文件開頭嵌入這樣的代碼(chrome是允許內嵌js代碼的!!!):

document.getElementsByClassName = 'pawn';

那么上面的檢測還有用嗎?

看看jQuery怎么做的,不僅要判斷它是否存在,還要判斷其能力是否符合要求.

看下面的代碼

var support = {};
support.getElementsByClassName = function () {
    if (!document.getElementsByClassName) {
        return false;
    }
    var div = document.createElement('div'),
        pWithClass = document.createElement('p');
    pWithClass.className = 'pawn-pawn-pawn';
    div.appendChild(pWithClass);
    var res = div.getElementsByClassName('pawn-pawn-pawn');
    return res[0] === pWithClass;
} ();

然后我們在chrome控制台輸入

support.getElementsByClassName

在ie8里,

可以看到,這樣很有用.

現在查看jQuery源代碼

看它的方法,實在是太簡潔了,jQuery留給我們的,就是敬仰!!

現在檢查已經完了,怎么解決不兼容問題呢?


解決兼容

怎么解決呢? 首先進行能力檢測,如果有,就直接調用,如果沒有,實現了一個自己的myGetClassName()方法.

原理很簡單,通過通用選擇器查找到所有的元素,然后看它有木有這個類名,如果有就push.

05.js

var getClass = function (className, result) {
    result = result || [];
    // 首先判斷我們的docoument.getElementsByClassName() 有沒有這個功能
    var res;
    
    if (support.getElementsByClassName) {
        res = document.getElementsByClassName(className);
    } else {
        // 自己實現getElementByClassName
        // 思路 : 首先獲得所有元素,然后再在所有元素中獲得帶有這個類的元素
        res = myGetByClassName(className.document);
    }
    result.push.apply(result,res);
    return result;
}

var myGetByClassName = function (className, context) {
    var elements = context.getElementsByTagName("*"),
        res = [];
    // 循環判斷是否符合要求
    each(elements, function () {
        // 這個細節非常重要 !!!!!
        if ((" " + this.className + " ").indexOf(" " + className + " ") != -1) {
            res.push(this);
        }
    })
    return res;
}

這個細節非常重要!!

 if ((" " + this.className + " ").indexOf(" " + className + " ") != -1) {
            res.push(this);
  }

好了,我們已經添加了自己的getClassName() 方法,現在來測試一遍.

05.html

<body>
    <div class="dv1">1</div>
    <div class="dv1">2</div>
    <div>3</div>
    <p clss='p1'>4</p>
    <p clss='p1'>5</p>
    <p>6</p>
<script src="05.js"></script>
</body>
<script>
    onload = function () {

    each(get('dv1'), function () {
        this.style.backgroundColor = "red";
    });
    each(get('p1'), function () {
        this.style.backgroundColor = "green";
    });
};
</script>

chrome下效果:

ie8下效果:

關於getElementsByClassName()的兼容問題就討論到這里,在下一篇,JavaScript高級框架設計(三),我會開始介紹push的兼容問題和 組合選擇器.

最后特別感謝恩師蔣坤老師(jk)對我的知識學習和人生引導的極大幫助,非常感謝他.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM