我是怎麼寫JavaScript框架的(一)

前言

之前大三的時候寫的一個小框架,仿jquery鏈式結構,純屬練手,大牛勿噴。當時還把JQ的源碼所有打印下來(由於在電腦上看不方便作筆記),7600+行代碼,300+頁A4紙,對照着看別人是怎麼寫的。還好一個長我一屆的學長當時比較閒,我說我想讀JQ代碼,他也沒讀過,就找來上兩屆的學長之前練手寫的小框架進行學習。惋惜只找到了壓縮混淆後的代碼,對於一個渣渣來講,閱讀起來很困難。在他的指點下,我大體瞭解了原理,並開始着手本身寫。如今陸續有學弟找我要這個代碼。雖然如今以爲寫得一塌糊塗,可是,可能對於和我當時同樣摸不着邊的人來講,可能會有幫助。我就打算慢慢解析我是怎麼寫的,並穿插講解我對JQ(2.0版)寫法的理解。其實源代碼裏我都寫有註釋,我也是靠這些註釋回憶我當時是怎麼想的。可能會有不少很差的地方,輕噴~ 罒ω罒node

我給庫起名Oct.js,是由於這個是在企鵝實習生應聘失敗後爲10月校招而寫。記念那個稚嫩的我。jquery

所有代碼,看這裏看這裏~( ̄▽ ̄)~* 
配合使用文檔看代碼更容易理解git

clipboard.png

閉包結構

爲了防止和別的庫的衝突,用閉包把整個框架安全地保護好。咱們待會的代碼都寫在裏面。這裏建立一個全局變量"window.O",就是在window對象里加個O,它等價於 "Oct",至關於jquery、Jquery這樣的別稱,意味着之後用Oct()或O()來進行操做。把window.O寫成window.$,那就和JQ的$調用用法同樣。window.O返回一個Octobj對象,這很重要。github

(function() {
    // 建立一個全局變量"window.O"
    window.O = Oct = function(selector, root_id, tag) { 
        return new Octobj(selector, root_id, tag); 
    }; 

    // 帥氣地定義一個版本
    Oct.version = "1.0"; 

})();

鏈式結構原理

JQ的流行,我想是由於它很方便吧,一個選擇器就能夠接着寫一連串的方法。鏈式結構就是在每一個方法結束時返回一個對象進行下一個方法的操做。後面會經過代碼說明segmentfault

選擇器

選擇器很是重要。JQ特別把選擇器部分剝離出一個庫sizzle.js。如今的JQ內部也是調用JQ裏面的sizzle。「選擇器」三個字寫得簡單,代碼寫起來就頗有學問,sizzle.js這種高大上的我就不說了。我這裏簡單實現一些功能:選擇class/id/標籤。瀏覽器

首先,把上面的Octobj對象骨架搭建起來。包含selector, root_id, tag三個參數。selector就是你想要找的東西,root_id是你想要找的東西的上級的id,tag是你要指定返回某種標籤。安全

var Octobj = function(selector, root_id, tag) {

};

定義一些參數閉包

// args: 存儲root_id的子標籤 
// type: 類型標記,id("#"), class(".") 或者 tag("&。 tag也加標記是爲了代碼方便
// eles: 臨時的,存儲`selector`變量裏"# . &"標記後面的字符串
// selector_exp: 用來匹配標籤的正則規則
var agrs, type, eles; 
var selector_exp = /^(?:#(\w-_)+|\.(\w-_)+|(\w)+)$/; 

// this.elements: 存儲函數結束後返回對象
this.elements = [];

// 防止如下狀況: $(""), $(null), $(undefined), $(false)
if (!selector) {
    return this;
}

接下來處理有沒有指定root_id、tag的狀況,框架

if (root_id) {
    root_id = typeof root_id == "string" ? document.getElementById(root_id) : root_id;
} else {
    root_id = document.body;
}
tag = tag || "*";
if (tag !== "*") {
    tag = tag.slice(1);
}

進入核心部分。分兩種狀況,能支持querySelector()方法的「高級」瀏覽器和不能支持的「低端」瀏覽器。先是能支持querySelector的,就很簡單了:函數

if (document.querySelectorAll) {
    // 由於我用'&'符號來標記標籤 ,因此要用"replace()"去掉'&' 
    var node_list = document.querySelectorAll(selector.replace("&", ""))
    for (var i in node_list) {
        if (node_list[i].tagName !== undefined) {
            // 把符合要求的元素存入`this.elements`
            this.elements.push(node_list[i]);
        }
    }
}

不能支持querySelector的,先來設置一些參數:

// 處理相似JQ $('.a .b')的狀況
selector = selector.replace(/^\s+/, "").split(/\s+/);

// 若是不指明 "root_id" 和 "tag", "args" 就存儲全部的標籤
args = root_id.getElementsByTagName(tag);

// 經過符號標記判斷是class/id/tag
type = selector[0].charAt(0);

// 所要選取的目標的字符串
eles = selector[0].slice(1);

處理選擇class的狀況。有文章指出for in的效率不高,我原本是用for處理的,可是出現問題,由於會把方法一塊兒選進來。JQ用了for in,感受有了標杆,我就所有改用for in了。

if (type === ".") {
    // 查找每一個標籤中有className,提取處理進行匹配
    for (var i in args) {
        if(args[i].className) {

            // className 可能不止一個, 經過空格進行分割
            var r = args[i].className.split(/\s+/);
            for (var j in r) {
                if (r[j] === eles) {
                    this.elements.push(args[i]);
                }
            }
        }
    }
}

處理選擇id的狀況:

else if (type === "#") {
    for (var i in args) {
        if(args[i].id) {
            var r = args[i].id.split(/\s+/);
            for (var j in r) {
                if (r[j] === eles) {
                    this.elements.push(args[i]);
                }
            }
        }
    }
}

處理選擇標籤的狀況:

else if (type === "&") {
    for (var i in args) {
        // 你能夠"console.log(args[i]);" 能夠看到最後一個是"length" ,沒有"tagName",因此要排除這個狀況,不然會報錯
        if (i !== "length" && typeof args[i] !== "function") {

            // "args[i].tagName" 在瀏覽器的屬性裏是用大寫的,爲了符合個人習慣,改用小寫進行判斷
            if (args[i].tagName.toLowerCase() === eles.toLowerCase()) {
                this.elements.push(args[i]);
            }
        }
    }

}

基礎選擇器部分完成了,別忘了最後一句,返回找到的元素,給下一個方法進行操做:

return this;

所有代碼

用法舉例

1.經過 id 選擇.

O("#buddy")

2.經過 class 選擇.

Oct(".buddy")

3.選擇全部 div.

O("&div") // also O("&Div"), O("&DIV")

4.選擇 .buddy 裏的 <p>.

O(".buddy", null, "&p")

5.選擇#dad下的.buddy

O(".buddy", "#dad")

Laker's blog

相關文章
相關標籤/搜索