1. 程式人生 > >Zepto源碼分析之一(代碼結構及初始化)

Zepto源碼分析之一(代碼結構及初始化)

剔除 col 函數賦值 mage 引用 define init方法 undefined 需要

關於讀源碼,讀jQuery自然是不錯,但太過於龐大不易解讀,對於小白,最好從Zepto,Lodash這樣的小庫入手。

這裏使用的是zepto1.1.6版本為例。

自執行函數

在閱讀之前,先弄清楚閉包和自執行函數

兩種方式: (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的值,只能在函數體內部返回,然後將函數賦值給一個全局變量

1 let temp= (function () {
2   let val = ‘123‘;
3   return val;     // 必須return, 否則獲取不到值
4 })()
5 
6 window.temp = temp;
7 
8 console.log(temp)   // 123

而關於閉包,簡單理解就是在自執行函數內部聲明變量或方法,在自執行函數內就形成了一個閉合的上下文環境,外部是無法直接訪問的。

源碼結構

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了。

Zepto源碼分析之一(代碼結構及初始化)