前言
我們移動端基本使用zepto了,而我也從一個小白變成稍微靠譜一點的前端了,最近居然經常要改到zepto源碼
但是,我對zepto不太熟悉,其實前端水准還是不夠,所以便私下偷偷學習下吧,別被發現了
核心方法$()
我們使用這個方法一般有幾個用途(我這里只說我自己用到過的),這里根據使用度排個序:
① 選擇器/$(selector)
將返回一個包裝過的dom集合對象(有很多選擇器)
② html字符串/$(domStr)
仍然返回一個包裝過的dom對象,他會將字符串初始化為我們的dom結構
PS:新增了一個方法可以直接賦予dom結構屬性,我們這里不關注
③ 函數/$(function(){})
我基本沒這么用過,他實在文檔加載結束后執行
暫時不管這些東西吧。我們這里來一個個看看他們的實現:
1 $('div') 2 //=> all DIV elements on the page 3 $('#foo') 4 //=> element with ID "foo" 5 6 // create element: 7 $("<p>Hello</p>") 8 //=> the new P element 9 // create element with attributes: 10 $("<p />", 11 { 12 text: "Hello", 13 id: "greeting", 14 css: { color: 'darkblue' } 15 }) 16 //=> <p id=greeting style="color:darkblue">Hello</p> 17 // execute callback when the page is ready: 18 Zepto(function ($) { 19 alert('Ready to Zepto!') 20 })
入口$()
只如初見
首先我們來看看選擇器,在zepto代碼中有這么一行代碼:
1 $ = function (selector, context) { 2 return zepto.init(selector, context) 3 }
這里其實前面就定義了$這個變量:
var undefined, key, $, classList, emptyArray = [],
這里初始化了$這個變量為一個函數,最后並將這個變量公開化(現在再匿名還是中,外面是訪問不到的),他具體是這樣干的
1 var Zepto = function () { 2 var zepto = {}, $; 3 zepto.init = function (selector, context) { 4 }; 5 $ = function (selector, context) { 6 return zepto.init(selector, context); 7 }; 8 $.zepto = zepto; 9 return $; 10 }; 11 window.$ = Zepto;
於是我們在頁面中使用這樣的代碼:
1 var el = $('#id'); 2 //實際上是 3 var el = zepto.init('#id');
具體里面的邏輯就是我們的重點,於是我們跟進去慢慢看吧
一查到底
我們詳細看看zepto.init這個方法
1 zepto.init = function (selector, context) { 2 if (!selector) return zepto.Z() 3 else if (isFunction(selector)) return $(document).ready(selector) 4 else if (zepto.isZ(selector)) return selector 5 else { 6 var dom 7 if (isArray(selector)) dom = compact(selector) 8 else if (isObject(selector)) 9 dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null 10 else if (fragmentRE.test(selector)) 11 dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null 12 else if (context !== undefined) return $(context).find(selector) 13 else dom = zepto.qsa(document, selector) 14 return zepto.Z(dom, selector) 15 } 16 }
第一步,如果選擇器selector不存在的話,就調用默認的方法,說白了就是返回一個集合長度為0的包裝對象
1 //通過給dom設置__proto__屬性指向$.fn來達到繼承$.fn上所有方法的目的 2 //ie自然是不支持的,zepto也基本不理睬ie 3 zepto.Z = function (dom, selector) { 4 dom = dom || [] 5 dom.__proto__ = $.fn 6 dom.selector = selector || '' 7 return dom 8 }
第二步,處理傳入函數情況,如果傳入是函數的話就在文檔加載結束后執行
第三步,處理傳入zepto對象,如果已經是zepto包裝對象了就直接返回
1 zepto.isZ = function (object) { 2 return object instanceof zepto.Z 3 }
第四步,開始了復雜的邏輯計算了,我們這里單獨提出來
selector處理
① 數組項
如果對象是一個數組,則去掉其中無意義的數組項
1 //清除給定的參數中的null或undefined,注意0==null,'' == null為false 2 function compact(array) { 3 return filter.call(array, function (item) { 4 return item != null 5 }) 6 }
filter
其中filter是javascript數組的一個新的方法,用以篩選滿足條件的數組項
1 var arr = [5, "element", 10, "the", true]; 2 var result = arr.filter( 3 function (value) { 4 return (typeof value === 'string'); 5 } 6 ); 7 document.write(result);//Output: element, the
最后返回數組項,我們這里認為其中每個數組項都是一個dom結構
② 傳入對象
因為typeof dom也是object,所以zepto這里做了一點擴展
1 function isObject(obj) { 2 return type(obj) == "object" 3 }
這個代碼存在的意義,老夫也不知道了......if里面的代碼比較關鍵:
如果傳入的是對象(比如{}),就將selector拷貝到一個對象,如果是dom結構就直接放入數組
這里有兩個方法需要注意,一個是extend,一個是isPlainObject
extend
extend用於為對象擴展對象
1 function extend(target, source, deep) { 2 for (key in source) 3 //如果深度擴展 4 if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { 5 //如果要擴展的數據是對象且target相對應的key不是對象 6 if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {} 7 //如果要擴展的數據是數組且target相對應的key不是數組 8 if (isArray(source[key]) && !isArray(target[key])) target[key] = [] 9 extend(target[key], source[key], deep) 10 } else if (source[key] !== undefined) target[key] = source[key] 11 }
$.extend事實上也是調用的上面的方法,我們這里先不管他
1 $.extend = function (target) { 2 var deep, args = slice.call(arguments, 1) 3 if (typeof target == 'boolean') { //當第一個參數為boolean類型的值時,表示是否深度擴展 4 deep = target 5 target = args.shift() //target取第二個參數 6 } 7 //遍歷后面的參數,全部擴展到target上 8 args.forEach(function (arg) { 9 extend(target, arg, deep) 10 }) 11 return target 12 }
isPlainObject
這個方法有所不同,通過字面量定義的對象和new Object的對象返回true,new Object時傳參數的返回false(測試對象是否純粹的對象)
1 function isPlainObject(obj) { 2 return isObject(obj) && !isWindow(obj) && obj.__proto__ == Object.prototype 3 } 4 $.isPlainObject({})// => true 5 $.isPlainObject(new Object) // => true 6 $.isPlainObject(new Date) // => false 7 $.isPlainObject(window) // => false
至於這個數組到底是不是dom,代碼這里先不關注,完了這里也結束了
③ 傳入html字符串
如果傳入的是html字符串,我們這里就要負責創建dom的工作了,整個這個東西其實比較復雜:
1 //HTML代碼片斷的正則 2 fragmentRE = /^\s*<(\w+|!)[^>]*>/,
PS:我正則不行,暫時就不嘗試去解釋這個了
字符串=>dom
1 zepto.fragment = function (html, name, properties) { 2 //將類似<div class="test"/>替換成<div class="test"></div>,算是一種修復吧 3 if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>") 4 //給name取標簽名 5 if (name === undefined) name = fragmentRE.test(html) && RegExp.$1 6 //設置容器標簽名,如果不是tr,tbody,thead,tfoot,td,th,則容器標簽名為div 7 if (!(name in containers)) name = '*' 8 var nodes, dom, container = containers[name] //創建容器 9 container.innerHTML = '' + html //將html代碼片斷放入容器 10 //取容器的子節點,這樣就直接把字符串轉成DOM節點了 11 dom = $.each(slice.call(container.childNodes), function () { 12 container.removeChild(this) //逐個刪除 13 }) 14 //如果properties是對象, 則將其當作屬性來給添加進來的節點進行設置 15 if (isPlainObject(properties)) { 16 nodes = $(dom) //將dom轉成zepto對象,為了方便下面調用zepto上的方法 17 //遍歷對象,設置屬性 18 $.each(properties, function (key, value) { 19 //如果設置的是'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset',則調用zepto上相對應的方法 20 if (methodAttributes.indexOf(key) > -1) nodes[key](value) 21 else nodes.attr(key, value) 22 }) 23 } 24 //返回將字符串轉成的DOM節點后的數組,比如'<li></li><li></li><li></li>'轉成[li,li,li] 25 return dom 26 }
這個方法比較高深,各位慢慢消化,我稍后也再去消化下
以上完了后,就返回了創建后的dom結構了......
④ 上下文
如果帶有上下文,就得使用上下文查找,沒有就在document中查詢
1 zepto.qsa = function (element, selector) { 2 var found 3 return (isDocument(element) && idSelectorRE.test(selector)) ? 4 ((found = element.getElementById(RegExp.$1)) ? [found] : []) : 5 (element.nodeType !== 1 && element.nodeType !== 9) ? [] : 6 slice.call( 7 classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) : 8 tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) : 9 element.querySelectorAll(selector) 10 ) 11 }
這個函數與上面函數一樣重要,各位下去消化吧
⑤ 結束
最后的最后便返回我們的包裝集合了,這里依舊使用zepto.Z(dom, selector)進行封裝
深入zepto.fragment
待續......
深入zepto.qsa
待續......
結語
今天的學習暫時到這里,我們下次繼續。