關於讀源碼,讀jQuery天然是不錯,但太過於龐大不易解讀,對於小白,最好從Zepto,Lodash這樣的小庫入手。css
這裏使用的是zepto1.1.6版本爲例。數組
自執行函數閉包
在閱讀以前,先弄清楚閉包和自執行函數dom
兩種方式: (function() {})() 和 (function() {}())函數
1 (function() { 2 console.log('這裏直接執行') 3 })()
1 (function () { 2 console.log('這裏直接執行') 3 }())
自執行函數的優點在於,避免了全局變量的污染,即在自執行函數體內聲明的變量,外部是沒法訪問到的。測試
1 (function () { 2 let val = '123' 3 })() 4 5 console.log(val) // val is not defined
如須要獲取變量val的值,只能在函數體內部返回,而後將函數賦值給一個全局變量spa
1 let temp= (function () { 2 let val = '123'; 3 return val; // 必須return, 不然獲取不到值 4 })() 5 6 window.temp = temp; 7 8 console.log(temp) // 123
而關於閉包,簡單理解就是在自執行函數內部聲明變量或方法,在自執行函數內就造成了一個閉合的上下文環境,外部是沒法直接訪問的。code
源碼結構cdn
1 ;let Zepto = (function () { 2 let $, zepto = {}; 3 4 return $; // 最終返回值 5 })() 6 7 window.Zepto = Zepto; 8 window.$ === undefined && (window.$ = Zepto);
首先聲明一個字支持函數並賦給變量Zepto, 再將變量Zepto賦給全局變量window.Zepto對象
若是$未被聲明,則也將Zepto賦給window.$全局變量
此時,外部訪問Zepto或者$均可以返回自執行函數內部的$(即 return $)
再看$究竟是什麼
$返回一個函數,傳入的參數是selector選擇器(即tagName,id,或className),context暫爲空。
1 ;let Zepto = (function () { 2 let $, zepto = {}; 3 $ = function(selector, context) { 4 console.log('獲取節點'); 5 return zepto.init(selector, context); 6 }; 7 return $; 8 })(); 9 10 window.Zepto = Zepto; 11 window.$ === undefined && (window.$ = Zepto);
此時,若是訪問節點, 會輸出'獲取節點'
1 <div> 2 <span>測試</span> 3 <span>測試</span> 4 <span>測試</span> 5 </div>
1 let span = $('span'); 2 console.log(span)
這裏報錯是由於init方法還未定義;
繼續建立 init方法
init函數是綁定在zepto對象上的方法(以前已經聲明一個zepto空對象)
1 zepto.init = function(selector, context) { 2 let dom;
// 處理dom的代碼 3 return zepto.Z(dom, selector); 4 };
init處理dom部分,分爲幾種狀況
1 . 當不傳參數 $(), 直接返回zepto.Z()不傳參
1 if (!selector) return zepto.Z()
2 . 當傳入字符串參數,又分爲幾種
1 else if (typeof selector == 'string') { 2 selector = selector.trim() 3 4 if (selector[0] == '<' && fragmentRE.test(selector)) 5 dom = zepto.fragment(selector, RegExp.$1, context), selector = null 6 7 else if (context !== undefined) return $(context).find(selector) 8 9 else dom = zepto.qsa(document, selector) //這裏的qsa其實就是document.querySelectorAll() 10 }
(1) $(' span ')
首先將清除字符串先後空格 selector.trim()
(2) $('<div></div>')
若是第一個字符是<,而且正則fragmentRE成立,
則使用fragment方法處理dom節點
1 if (selector[0] == '<' && fragmentRE.test(selector)) 2 dom = zepto.fragment(selector, RegExp.$1, context), selector = null
(3) $('span', 'div')
若是傳入第二個參數,即以前的context不等於undefined,則至關於執行$('div').find('span')
1 else if (context !== undefined) return $(context).find(selector)
若div中存在span則返回span,不然返回空數組。
(4)如以上三種不知足則執行最後一步,zepto.qsa()
3 . 若是傳入函數,用document調用一個zepto對象,而後$.ready()方法, ready方法和上面的find方法同樣在後面建立。
1 else if (isFunction(selector)) return $(document).ready(selector);
4 . 若已是一個zepto對象,則直接返回該對象,沒必要再調用$(), 即 $($('span')) === $('span')
1 else if (zepto.isZ(selector)) return selector
1 zepto.isZ = function(object) { 2 return object instanceof zepto.Z 3 }
5 . 如果引用類型
1 else { 2 // 如參數是數組,則調用compact方法, 會將null或undefined剔除 3 if (isArray(selector)) dom = compact(selector) 4 // 如是DOM節點,則數組化 5 else if (isObject(selector)) 6 dom = [selector], selector = null 7 // 如下和上面字符串參數的重複,何意未知。 8 else if (fragmentRE.test(selector)) 9 dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null 10 // 11 // 12 else if (context !== undefined) return $(context).find(selector) 13 // 14 else dom = zepto.qsa(document, selector) 15 }
init是一個初始化方法,調用init時去執行zepto.Z()函數
1 zepto.Z = function(dom, selector) { 2 dom = dom || [] 3 dom.__proto__ = $.fn; 4 dom.selector = selector || ''; 5 console.log(dom) 6 console.log(selector) 7 return dom; 8 };
關於Z()
1 . 傳入dom,若是未傳則返回[]空數組
若是不傳DOM節點 $(), 則返回一個空值。
2 . 若是有dom $('span'),則
3 . 同時在該數組的原型對象上建立$.fn對象
在未定義$.fn以前,dom.__proto__原型對象上的方法是Array構造函數的內置方法
而這裏使用了dom.__proto__ = $.fn 從新定義了原型對象上的方法
1 $.fn = { 2 get: function () { 3 console.log('自定義get方法') 4 }, 5 on: function () { 6 console.log('自定義on方法') 7 } 8 }
能夠看出,經過原型對象上建立新方法後,原來的內置方法都沒有了。
若是隻是想追加方法,則應該在原型對象上添加屬性。
dom.__proto__.get = function() {}
dom.__proto__.on = function() {}
4 . 最後返回該DOM節點對象。此時$('span')對象就能夠調用方法get和on了。