當前位置:
首頁 > 最新 > 讀Zepto源碼之操作DOM

讀Zepto源碼之操作DOM

這篇依然是跟 相關的方法,側重點是操作 的方法。

讀Zepto源碼系列文章已經放到了github上,歡迎star:reading-zepto

源碼版本

本文閱讀的源碼為zepto1.2.0

.remove()remove: function() { return this.each(function() { if (this.parentNode != null) this.parentNode.removeChild(this) }) },

刪除當前集合中的元素。

如果父節點存在時,則用父節點的 方法來刪掉當前的元素。

相似方法生成器

中 、 、 、 、 、 、 和 都是通過這個相似方法生成器生成的。

定義容器adjacencyOperators = [ after , prepend , before , append ]

首先,定義了一個相似操作的數組,注意數組裡面只有 、 、 、 這幾個方法名,後面會看到,在生成這幾個方法後, 、 、 和 會分別調用前面生成的幾個方法。

輔助方法traverseNodefunction traverseNode(node, fun) { fun(node) for (var i = 0, len = node.childNodes.length; i

這個方法遞歸遍歷 的子節點,將節點交由回調函數 處理。這個輔助方法在後面會用到。

核心源碼adjacencyOperators.forEach(function(operator, operatorIndex) { var inside = operatorIndex % 2 //=> prepend, append $.fn[operator] = function() { // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings var argType, nodes = $.map(arguments, function(arg) { var arr = [] argType = type(arg) if (argType == "array") { arg.forEach(function(el) { if (el.nodeType !== undefined) return arr.push(el) else if ($.zepto.isZ(el)) return arr = arr.concat(el.get()) arr = arr.concat(zepto.fragment(el)) }) return arr } return argType == "object" || arg == null ? arg : zepto.fragment(arg) }), parent, copyByClone = this.length > 1 if (nodes.length

在分析之前,先看看這幾個方法的用法:

after(content) prepend(content) before(content) append(content)

參數 可以為 字元串, 節點,或者節點組成的數組。 是在每個集合元素後插入 , 正好相反,在每個集合元素前插入 , 是在每個集合元素的初始位置插入 , 是在每個集合元素的末尾插入 。 和 插入的 在元素的外部,而 和 插入的 在元素的內部,這是需要注意的。

將參數 轉換成 節點數組var inside = operatorIndex % 2 //=> prepend, append

遍歷 ,得到對應的方法名 和方法名在數組中的索引 。

定義了一個 變數,當 為偶數時, 的值為 ,也就是 的值為 或 時, 的值為 。這個可以用來區分 是插入到元素內部還是外部的方法。

即為 對象設置對應的屬性值(方法名)。

var argType, nodes = $.map(arguments, function(arg) { var arr = [] argType = type(arg) if (argType == "array") { arg.forEach(function(el) { if (el.nodeType !== undefined) return arr.push(el) else if ($.zepto.isZ(el)) return arr = arr.concat(el.get()) arr = arr.concat(zepto.fragment(el)) }) return arr } return argType == "object" || arg == null ? arg : zepto.fragment(arg) }),

變數 用來保存變數變數的類型,也即 的類型。 是根據 轉換後的 節點數組。

這裡用了 的方式來獲取參數 ,這裡只有一個參數,這什麼不用 來獲取呢?這是因為 可以將數組進行展平,具體的實現看這裡《讀zepto源碼之工具函數》。

首先用內部函數 來獲取參數的類型,關於 的實現,在《讀Zepto源碼之內部方法》 已經作過分析。

如果參數 ,也即 的類型為數組時,遍歷 ,如果數組中的元素存在 屬性,則斷定為 節點,就將其 進容器 中;如果數組中的元素為 對象(用 判斷,該方法已經在《讀Zepto源碼之神奇的$》有過分析),不傳參調用 方法,返回的是一個數組,然後調用數組的 方法合并數組, 方法在《讀Zepto源碼之集合操作》有過分析;否則,為 字元串,調用 處理,並將返回的數組合并,` 在《讀Zepto源碼之神奇的$》中有過分析。

如果參數類型為 (即為 對象)或者 ,則直接返回。

否則為 字元串,調用 處理。

parent, copyByClone = this.length > 1 if (nodes.length

這裡還定義了 變數,用來保存 插入的父節點;當集合中元素的數量大於 時,變數 的值為 ,這個變數的作用後面再說。

如果 的數量比 小,也即需要插入的節點為空時,不再作後續的處理,返回 ,以便可以進行鏈式操作。

用 來模擬所有操作return this.each(function(_, target) { parent = inside ? target : target.parentNode // convert all methods to a "before" operation target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null var parentInDocument = $.contains(document.documentElement, parent) ... })

對集合進行 遍歷

parent = inside ? target : target.parentNode

如果 節點需要插入目標元素 的內部,則 設置為目標元素 ,否則設置為當前元素的父元素。

target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null

這段是將所有的操作都用 原生方法 來模擬。 如果 即為 時, 節點應該插入到目標元素 的後面,即 的下一個兄弟元素的前面;當 即為 時, 節點應該插入到目標元素的開頭,即 的第一個子元素的前面;當 即為 時, 剛好與之對應,即為元素本身。當 的第二個參數為 時, 會將 插入到子節點的末尾,剛好與 對應。具體見文檔:Node.insertBefore()

var parentInDocument = $.contains(document.documentElement, parent)

調用 方法,檢測父節點 是否在 中。 方法在《讀zepto源碼之工具函數》中已有過分析。

將 節點數組插入到元素中nodes.forEach(function(node) { if (copyByClone) node = node.cloneNode(true) else if (!parent) return $(node).remove() parent.insertBefore(node, target) ... })

如果需要複製節點時(即集合元素的數量大於 時),用 節點方法 來複制節點,參數 表示要將節點的子節點和屬性等信息也一起複制。為什麼集合元素大於 時需要複製節點呢?因為 插入的是節點的引用,對集合中所有元素的遍歷操作,如果不克隆節點,每個元素所插入的引用都是一樣的,最後只會將節點插入到最後一個元素中。

如果父節點不存在,則將 刪除,不再進行後續操作。

將節點用 方法插入到元素中。

如果父元素在 內,則調用 來處理 節點及 節點的所有子節點。主要是檢測 節點或其子節點是否為不指向外部腳本的 標籤。

el.nodeName != null && el.nodeName.toUpperCase() === SCRIPT

這段用來判斷是否為 標籤,通過 的 屬性是否為 來判斷。

!el.type || el.type === text/javascript

不存在 屬性,或者 屬性為 。這裡表示只處理 ,因為 屬性不一定指定為 ,只有指定為 或者為空時,才會按照 來處理。見MDN文檔script

並且不存在外部腳本。

var target = el.ownerDocument ? el.ownerDocument.defaultView : window

是否存在 屬性, 返回的是元素的根節點,也即 對象, 對象的 屬性返回的是 對象所關聯的 對象,這裡主要是處理 里的 ,因為在 中有獨立的 對象。如果不存在該屬性,則默認使用當前的 對象。

target[ eval ].call(target, el.innerHTML)

最後調用 的 方法,執行 中的腳本,腳本用 取得。

為什麼要對 元素單獨進行這樣的處理呢?因為出於安全的考慮,腳本通過 的方法插入到 中時,是不會執行腳本的,所以需要使用 來進行處理。

生成 、 、 和 方法

先來看看這幾個方法的調用方式

insertAfter(target) insertBefore(target) appendTo(target) prependTo(target)

這幾個方法都是將集合中的元素插入到目標元素 中,跟 、 、 和 剛好是相反的操作。

他們的對應關係如下:

after => insertAfter prepend => prependTo before => insertBefore append => appendTo

因此可以調用相應的方法來生成這些方法。

$.fn[inside ? operator + To : insert + (operatorIndex ? Before : After )] = function(html) { $(html)[operator](this) return this }inside ? operator + To : insert + (operatorIndex ? Before : After )

這段其實是生成方法名,如果是 或 ,則在後面拼接 ,如果是 或 ,則在前面拼接 。

$(html)[operator](this)

簡單地反向調用對應的方法,就可以了。

到此,這個相似方法生成器生成了 、 、 、 、 、 、 和 等八個方法,相當高效。

.empty()empty: function() { return this.each(function() { this.innerHTML = }) },

的作用是將所有集合元素的內容清空,調用的是 的 屬性設置為空。

.replaceWith()replaceWith: function(newContent) { return this.before(newContent).remove() },

將所有集合元素替換為指定的內容 , 的類型跟 的參數類型一樣。

首先調用 將 插入到對應元素的前面,再將元素刪除,這樣就達到了替換的上的。

.wrapAll()wrapAll: function(structure) { if (this[0]) { $(this[0]).before(structure = $(structure)) var children // drill down to the inmost element while ((children = structure.children()).length) structure = children.first() $(structure).append(this) } return this },

將集合中所有的元素都包裹進指定的結構 中。

如果集合元素存在,即 存在,則進行後續操作,否則返回 ,以進行鏈式操作。

調用 方法,將指定結構插入到第一個集合元素的前面,也即所有集合元素的前面

while ((children = structure.children()).length) structure = children.first()

查找 的子元素,如果子元素存在,則將 賦值為 的第一個子元素,直找到 最深層的第一個子元素為止。

將集合中所有的元素都插入到 的末尾,如果 存在子元素,則插入到最深層的第一個子元素的末尾。這樣就將集合中的所有元素都包裹到 內了。

.wrap()wrap: function(structure) { var func = isFunction(structure) if (this[0] && !func) var dom = $(structure).get(0), clone = dom.parentNode || this.length > 1 return this.each(function(index) { $(this).wrapAll( func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom ) }) },

為集合中每個元素都包裹上指定的結構 , 可以為單獨元素或者嵌套元素,也可以為 元素或者 節點,還可以為回調函數,回調函數接收當前元素和當前元素在集合中的索引兩個參數,返回符合條件的包裹結構。

var func = isFunction(structure)

判斷 是否為函數

if (this[0] && !func) var dom = $(structure).get(0), clone = dom.parentNode || this.length > 1

如果集合不為空,並且 不為函數,則將 轉換為 節點,通過 來轉換,並賦給變數 。如果 的 存在或者集合的數量大於 ,則 的值為 。

return this.each(function(index) { $(this).wrapAll( func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom ) })

對集合進行遍歷,調用 方法,如果 為函數,則將回調函數返回的結果作為參數傳給 ;

否則,如果 為 ,則將 也即包裹元素的副本傳給 ,否則直接將 傳給 。這裡傳遞副本的的原因跟生成器中的一樣,也是避免對 節點的引用。如果 的 存在時,表明 本來就從屬於某個節點,如果直接使用 ,會破壞原來的結構。

.wrapInner()wrapInner: function(structure) { var func = isFunction(structure) return this.each(function(index) { var self = $(this), contents = self.contents(), dom = func ? structure.call(this, index) : structure contents.length ? contents.wrapAll(dom) : self.append(dom) }) },

將集合中每個元素的內容都用指定的結構 包裹。 的參數類型跟 一樣。

對集合進行遍歷,調用 方法,獲取元素的內容, 方法在《讀Zepto源碼之集合元素查找》有過分析。

如果 為函數,則將函數返回的結果賦值給 ,否則將直接將 賦值給 。

如果 存在,即元素不為空元素,調用 方法,將元素的內容包裹在 中;如果為空元素,則直接將 插入到元素的末尾,也實現了將 包裹在元素的內部了。

.unwrap()unwrap: function() { this.parent().each(function() { $(this).replaceWith($(this).children()) }) return this },

當集合中的所有元素的包裹層去掉,也即將父元素去掉,但是保留父元素的子元素。

實現的方法也很簡單,就是遍歷當前元素的父元素,將父元素替換為父元素的子元素。

.clone()clone: function() { return this.map(function() { return this.cloneNode(true) }) },

每集合中每個元素都創建一個副本,並將副本集合返回。

遍曆元素集合,調用 的原生方法 創建副本。要注意, 不會將元素原來的數據和事件處理程序複製到副本中。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

用這4個超實用的方法,做出有價值的功能性動效!
平面設計的前景好嗎?工資待遇又是如何?
告別酷熱體驗,這位「涼風搬運工」能迅速撫平你的盛夏綜合症
內容社交產品中的關鍵數據——獲得良好反饋的用戶比例
創業失敗三次他做共享雨傘 100把鋪設上海不知去向 擬估值1億

TAG:推酷 |

您可能感興趣

MFC TabSheet 源碼
PHP源碼分析之parse
JDK 源碼閱讀 Reference
JDK源碼閱讀:InterruptibleChannel 與可中斷 IO
讀 SnapKit和Masonry 自動布局框架源碼
JDK 源碼閱讀 :ByteBuffer
JDK 源碼閱讀 : DirectByteBuffer
Vue 源碼分析之 Observer
TinyHttpd源碼分析
Rasa Core源碼之Policy訓練
從JDK源碼看StringBuffer
Drill-on-YARN之源碼解析
PyalgoTrade源碼閱讀完結篇
HashMap源碼分析
Storm源碼分析之Trident源碼分析
PopupWindow源碼分析
《跟隨霄,LAMMPS源碼學習06》Atom:grow
Android-IMX6Q源碼編譯
APT組織Carbanak源碼泄露
MapReduce中源碼分析(map端的過程)