【zepto學習筆記01】核心方法$()

前言

咱們移動端基本使用zepto了,而我也從一個小白變成稍微靠譜一點的前端了,最近竟然常常要改到zepto源碼
可是,我對zepto不太熟悉,其實前端水準仍是不夠,因此便私下偷偷學習下吧,別被發現了javascript

核心方法$()

咱們使用這個方法通常有幾個用途(我這裏只說我本身用到過的),這裏根據使用度排個序:css

① 選擇器/$(selector)

將返回一個包裝過的dom集合對象(有不少選擇器)html

② html字符串/$(domStr)

仍然返回一個包裝過的dom對象,他會將字符串初始化爲咱們的dom結構
PS:新增了一個方法能夠直接賦予dom結構屬性,咱們這裏不關注前端

③ 函數/$(function(){})

我基本沒這麼用過,他實在文檔加載結束後執行java

暫時無論這些東西吧。咱們這裏來一個個看看他們的實現:node

 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 }

這裏其實前面就定義了$這個變量:dom

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

待續......

結語

今天的學習暫時到這裏,咱們下次繼續。

相關文章
相關標籤/搜索