對於操做 DOM
來講,jQuery
是很是方便的一個庫,雖然現在隨着 React
, Vue
之類框架的流行,jQuery
用得愈來愈少了,可是其中不少思想仍是很是值得咱們學習的,這篇文章將介紹如何從零開始實現一個簡化版 jQuery
。node
在這裏,我把這個庫命名爲 Clus(class 的諧音),下面以 $
符號代替。git
首先須要聲明一個構造函數並作一些初始化操做:github
function $(selector) { return new $.fn.init(selector); } $.fn = $.prototype = { contructor: $, init, };
能夠看到,該構造函數返回一個 $.fn.init
的實例,這樣作的好處就是在使用的時候不要每次都 new
一個構造函數就能夠建立一個新的實例了,能夠看出來,整個核心都在 init
函數上了:數組
function init(selector) { const fragmentRE = /^\s*<(\w+|!)[^>]*>/; const selectorType = $.type(selector); const elementTypes = [1, 9, 11]; let dom; if (!selector) { dom = [], dom.selector = selector; } else if (elementTypes.indexOf(selector.nodeType) !== -1 || selector === window) { dom = [selector], selector = null; } else if (selectorType === 'function') { return $(document).ready(selector); } else if (selectorType === 'array') { dom = selector; } else if (selectorType === 'object') { dom = [selector], selector = null; } else if (selectorType === 'string') { if (selector[0] === '<' && fragmentRE.test(selector)) { dom = $.parseHTML(selector), selector = null; } else { dom = [].slice.call(document.querySelectorAll(selector)); } } dom = dom || []; $.extend(dom, $.fn); dom.selector = selector; return dom; }
能夠很清楚的看到,根據傳入的參數類型的不一樣進行一些不一樣的操做,好比傳入的是函數的話,則該函數裏的操做的都是 DOM Ready
以後的操做了;再好比傳入的是字符串的話,而且若是是標籤的話,則會把這段標籤字符串解析成 DOM Fragment
,若是是普通字符串,則會調用 document.querySelectorAll()
方法來查找 DOM。app
相信你們都能很容易的看明白上面的代碼,不過有一點值得一提的是 $.extend(dom, $.fn);
這段代碼,其含義是把實例上的全部方法都添加到 dom
這個數組對象中,這樣作的目的就是爲了能夠直接鏈式調用某個實例的方法,好比 $('.clus').addClass('hello')
,這個 addClass()
方法就是在 $.fn
上實現的。所以全部在 $.fn
實現的方法均可以經過 $(selector).method()
這種方式來調用了。框架
至於 extend()
方法我認爲是除了 init()
方法之外,整個庫中最核心的一個方法了,代碼以下:dom
export default function extend() { let options, name, clone, copy, source, copyIsArray, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; if (typeof target === 'boolean') { deep = target; target = arguments[i] || {}; i++; } if (typeof target !== 'object' && $.type(target) !== 'function') { target = {}; } if (i === length) { target = this; i--; } for (; i < length; i++) { // if ((options = arguments[i]) !== null) { // for in source object for (name in options) { source = target[name]; copy = options[name]; if (target == copy) { continue; } // deep clone if (deep && copy && ($.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { // if copy is array if (copyIsArray) { copyIsArray = false; // if is not array, set it to array clone = source && Array.isArray(source) ? source : []; } else { // if copy is not a object, set it to object clone = source && $.isPlainObject(source) ? source : {}; } target[name] = extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } } } return target; }
能夠看到,和 jQuery
的實現一毛同樣,沒錯就是從那兒 copy 過來的固然同樣。函數
下面以 addClass()
方法爲例介紹如何操做 DOM 的:學習
function addClass(cls) { let classes, clazz, el, cur, curValue, finalValue, j, i = 0; if (typeof cls === 'string' && cls) { classes = cls.match(rnotwhite) || []; while((el = this[i++])) { curValue = getClass(el); cur = (el.nodeType === 1) && ` ${curValue} `.replace(rclass, ' '); if (cur) { j = 0; while((clazz = classes[j++])) { // to determine whether the class that to add has already existed if (cur.indexOf(` ${clazz} `) == -1) { cur += clazz + ' '; } finalValue = $.trim(cur); if ( curValue !== finalValue ) { el.setAttribute('class', finalValue); } } } } } return this; } $.fn.addClass = addClass;
值得一提的就是在實例方法中,this
關鍵字能夠訪問到根據選擇器所查詢到的全部元素的集合,在這裏是經過 while
循環來對每一個元素進行操做。要實現相似 $(selector).addClass().removeClass()
這樣的鏈式操做,只須要在每一個實例方法中返回一個 this
便可。要實現其餘實例方法好比 hasClass()
之類的也是相似的方法。this
其實每一個實例方法都是經過 this
關鍵字來獲取查詢到的元素,而後遍歷這些元素來針對每一個元素進行具體的操做,在舉一個栗子:
function append(DOMString) { let el, i = 0, fregmentCollection = $.parseHTML(DOMString), fregments = Array.prototype.slice.apply(fregmentCollection); while((el = this[i++])) { fregments.map(fregment => { el.appendChild(fregment); }); } return this; } $.fn.append = append;
上面是 append()
的實現,首先先解析 DOMString
爲 fregment
,而後就是遍歷查詢到的元素(經過 this
關鍵字)並針對每一個元素去進行 appendChild()
的操做,從而把 DOM
插入到匹配到的全部元素中。
其餘實例方法也是經過相似的方式實現的,這裏就不一一細說了,想更詳細的查看其餘方法的實現能夠直接到 Clus 中查看源碼。