摘要:輔助方法這個方法遞歸遍歷的子節(jié)點,將節(jié)點交由回調(diào)函數(shù)處理。對集合進行遍歷,調(diào)用方法,如果為函數(shù),則將回調(diào)函數(shù)返回的結果作為參數(shù)傳給否則,如果為,則將也即包裹元素的副本傳給,否則直接將傳給。
這篇依然是跟 dom 相關的方法,側重點是操作 dom 的方法。
讀Zepto源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
.remove()remove: function() { return this.each(function() { if (this.parentNode != null) this.parentNode.removeChild(this) }) },
刪除當前集合中的元素。
如果父節(jié)點存在時,則用父節(jié)點的 removeChild 方法來刪掉當前的元素。
相似方法生成器zepto 中 after、 prepend、 before、 append、insertAfter、 insertBefore、 appendTo 和 prependTo 都是通過這個相似方法生成器生成的。
定義容器adjacencyOperators = ["after", "prepend", "before", "append"]
首先,定義了一個相似操作的數(shù)組,注意數(shù)組里面只有 after、 prepend、 before、 append 這幾個方法名,后面會看到,在生成這幾個方法后,insertAfter、 insertBefore、 appendTo 和 prependTo 會分別調(diào)用前面生成的幾個方法。
輔助方法traverseNodefunction traverseNode(node, fun) { fun(node) for (var i = 0, len = node.childNodes.length; i < len; i++) traverseNode(node.childNodes[i], fun) }
這個方法遞歸遍歷 node 的子節(jié)點,將節(jié)點交由回調(diào)函數(shù) fun 處理。這個輔助方法在后面會用到。
核心源碼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 < 1) return this 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) nodes.forEach(function(node) { if (copyByClone) node = node.cloneNode(true) else if (!parent) return $(node).remove() parent.insertBefore(node, target) if (parentInDocument) traverseNode(node, function(el) { if (el.nodeName != null && el.nodeName.toUpperCase() === "SCRIPT" && (!el.type || el.type === "text/javascript") && !el.src) { var target = el.ownerDocument ? el.ownerDocument.defaultView : window target["eval"].call(target, el.innerHTML) } }) }) }) }調(diào)用方式
在分析之前,先看看這幾個方法的用法:
after(content) prepend(content) before(content) append(content)
參數(shù) content 可以為 html 字符串,dom 節(jié)點,或者節(jié)點組成的數(shù)組。after 是在每個集合元素后插入 content , before 正好相反,在每個集合元素前插入 content,prepend 是在每個集合元素的初始位置插入 content, append 是在每個集合元素的末尾插入 content。before 和 after 插入的 content 在元素的外部,而 prepend 和 append 插入的 content 在元素的內(nèi)部,這是需要注意的。
將參數(shù) content 轉換成 node 節(jié)點數(shù)組var inside = operatorIndex % 2 //=> prepend, append
遍歷 adjacencyOperators,得到對應的方法名 operator 和方法名在數(shù)組中的索引 operatorIndex。
定義了一個 inside 變量,當 operatorIndex 為偶數(shù)時,inside 的值為 true,也就是 operator 的值為 prepend 或 append 時,inside 的值為 true 。這個可以用來區(qū)分 content 是插入到元素內(nèi)部還是外部的方法。
$.fn[operator] 即為 $.fn 對象設置對應的屬性值(方法名)。
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) }),
變量 argType 用來保存變量變量的類型,也即 content 的類型。nodes 是根據(jù) content 轉換后的 node 節(jié)點數(shù)組。
這里用了 $.map arguments 的方式來獲取參數(shù) content ,這里只有一個參數(shù),這什么不用 arguments[0] 來獲取呢?這是因為 $.map 可以將數(shù)組進行展平,具體的實現(xiàn)看這里《讀zepto源碼之工具函數(shù)》。
首先用內(nèi)部函數(shù) type 來獲取參數(shù)的類型,關于 type 的實現(xiàn),在《讀Zepto源碼之內(nèi)部方法》 已經(jīng)作過分析。
如果參數(shù) content ,也即 arg 的類型為數(shù)組時,遍歷 arg ,如果數(shù)組中的元素存在 nodeType 屬性,則斷定為 node 節(jié)點,就將其 push 進容器 arr 中;如果數(shù)組中的元素為 zepto 對象(用 $.zepto.isZ 判斷,該方法已經(jīng)在《讀Zepto源碼之神奇的$》有過分析),不傳參調(diào)用 get 方法,返回的是一個數(shù)組,然后調(diào)用數(shù)組的 concat 方法合并數(shù)組,get 方法在《讀Zepto源碼之集合操作》有過分析;否則,為 html 字符串,調(diào)用 zepto.fragment 處理,并將返回的數(shù)組合并,`zepto.fragment 在《讀Zepto源碼之神奇的$》中有過分析。
如果參數(shù)類型為 object (即為 zepto 對象)或者 null ,則直接返回。
否則為 html 字符串,調(diào)用 zepto.fragment 處理。
parent, copyByClone = this.length > 1 if (nodes.length < 1) return this
這里還定義了 parent 變量,用來保存 content 插入的父節(jié)點;當集合中元素的數(shù)量大于 1 時,變量 copyByClone 的值為 true ,這個變量的作用后面再說。
如果 nodes 的數(shù)量比 1 小,也即需要插入的節(jié)點為空時,不再作后續(xù)的處理,返回 this ,以便可以進行鏈式操作。
用 insertBefore 來模擬所有操作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) ... })
對集合進行 each 遍歷
parent = inside ? target : target.parentNode
如果 node 節(jié)點需要插入目標元素 target 的內(nèi)部,則 parent 設置為目標元素 target,否則設置為當前元素的父元素。
target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null
這段是將所有的操作都用 dom 原生方法 insertBefore 來模擬。 如果 operatorIndex == 0 即為 after 時,node 節(jié)點應該插入到目標元素 target 的后面,即 target 的下一個兄弟元素的前面;當 operatorIndex == 1 即為 prepend 時,node 節(jié)點應該插入到目標元素的開頭,即 target 的第一個子元素的前面;當 operatorIndex == 2 即為 before 時,insertBefore 剛好與之對應,即為元素本身。當 insertBefore 的第二個參數(shù)為 null 時,insertBefore 會將 node 插入到子節(jié)點的末尾,剛好與 append 對應。具體見文檔:Node.insertBefore()
var parentInDocument = $.contains(document.documentElement, parent)
調(diào)用 $.contains 方法,檢測父節(jié)點 parent 是否在 document 中。$.contains 方法在《讀zepto源碼之工具函數(shù)》中已有過分析。
將 node 節(jié)點數(shù)組插入到元素中nodes.forEach(function(node) { if (copyByClone) node = node.cloneNode(true) else if (!parent) return $(node).remove() parent.insertBefore(node, target) ... })
如果需要復制節(jié)點時(即集合元素的數(shù)量大于 1 時),用 node 節(jié)點方法 cloneNode 來復制節(jié)點,參數(shù) true 表示要將節(jié)點的子節(jié)點和屬性等信息也一起復制。為什么集合元素大于 1 時需要復制節(jié)點呢?因為 insertBefore 插入的是節(jié)點的引用,對集合中所有元素的遍歷操作,如果不克隆節(jié)點,每個元素所插入的引用都是一樣的,最后只會將節(jié)點插入到最后一個元素中。
如果父節(jié)點不存在,則將 node 刪除,不再進行后續(xù)操作。
將節(jié)點用 insertBefore 方法插入到元素中。
處理 script 標簽內(nèi)的腳本if (parentInDocument) traverseNode(node, function(el) { if (el.nodeName != null && el.nodeName.toUpperCase() === "SCRIPT" && (!el.type || el.type === "text/javascript") && !el.src) { var target = el.ownerDocument ? el.ownerDocument.defaultView : window target["eval"].call(target, el.innerHTML) } })
如果父元素在 document 內(nèi),則調(diào)用 traverseNode 來處理 node 節(jié)點及 node 節(jié)點的所有子節(jié)點。主要是檢測 node 節(jié)點或其子節(jié)點是否為不指向外部腳本的 script 標簽。
el.nodeName != null && el.nodeName.toUpperCase() === "SCRIPT"
這段用來判斷是否為 script 標簽,通過 node 的 nodeName 屬性是否為 script 來判斷。
!el.type || el.type === "text/javascript"
不存在 type 屬性,或者 type 屬性為 "text/javascript"。這里表示只處理 javascript,因為 type 屬性不一定指定為 text/javascript ,只有指定為 test/javascript 或者為空時,才會按照 javascript 來處理。見MDN文檔
!el.src
并且不存在外部腳本。
var target = el.ownerDocument ? el.ownerDocument.defaultView : window
是否存在 ownerDocument 屬性,ownerDocument 返回的是元素的根節(jié)點,也即 document 對象,document 對象的 defaultView 屬性返回的是 document 對象所關聯(lián)的 window 對象,這里主要是處理 iframe 里的 script,因為在 iframe 中有獨立的 window 對象。如果不存在該屬性,則默認使用當前的 window 對象。
target["eval"].call(target, el.innerHTML)
最后調(diào)用 window 的 eval 方法,執(zhí)行 script 中的腳本,腳本用 el.innerHTML 取得。
為什么要對 script 元素多帶帶進行這樣的處理呢?因為出于安全的考慮,腳本通過 insertBefore 的方法插入到 dom 中時,是不會執(zhí)行腳本的,所以需要使用 eval 來進行處理。
生成 insertAfter、prependTo、insertBefore 和 appendTo 方法先來看看這幾個方法的調(diào)用方式
insertAfter(target) insertBefore(target) appendTo(target) prependTo(target)
這幾個方法都是將集合中的元素插入到目標元素 target 中,跟 after、before、append 和 prepend 剛好是相反的操作。
他們的對應關系如下:
after => insertAfter prepend => prependTo before => insertBefore append => appendTo
因此可以調(diào)用相應的方法來生成這些方法。
$.fn[inside ? operator + "To" : "insert" + (operatorIndex ? "Before" : "After")] = function(html) { $(html)[operator](this) return this }
inside ? operator + "To" : "insert" + (operatorIndex ? "Before" : "After")
這段其實是生成方法名,如果是 prepend 或 append ,則在后面拼接 To ,如果是 Before 或 After,則在前面拼接 insert。
$(html)[operator](this)
簡單地反向調(diào)用對應的方法,就可以了。
到此,這個相似方法生成器生成了after、 prepend、 before、 append、insertAfter、 insertBefore、 appendTo 和 prependTo 等八個方法,相當高效。
.empty()empty: function() { return this.each(function() { this.innerHTML = "" }) },
empty 的作用是將所有集合元素的內(nèi)容清空,調(diào)用的是 node 的 innerHTML 屬性設置為空。
.replaceWith()replaceWith: function(newContent) { return this.before(newContent).remove() },
將所有集合元素替換為指定的內(nèi)容 newContent , newContent 的類型跟 before 的參數(shù)類型一樣。
replaceWidth 首先調(diào)用 before 將 newContent 插入到對應元素的前面,再將元素刪除,這樣就達到了替換的上的。
.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 },
將集合中所有的元素都包裹進指定的結構 structure 中。
如果集合元素存在,即 this[0] 存在,則進行后續(xù)操作,否則返回 this ,以進行鏈式操作。
調(diào)用 before 方法,將指定結構插入到第一個集合元素的前面,也即所有集合元素的前面
while ((children = structure.children()).length) structure = children.first()
查找 structure 的子元素,如果子元素存在,則將 structure 賦值為 structure 的第一個子元素,直找到 structrue 最深層的第一個子元素為止。
將集合中所有的元素都插入到 structure 的末尾,如果 structure 存在子元素,則插入到最深層的第一個子元素的末尾。這樣就將集合中的所有元素都包裹到 structure 內(nèi)了。
.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 ) }) },
為集合中每個元素都包裹上指定的結構 structure,structure 可以為多帶帶元素或者嵌套元素,也可以為 html 元素或者 dom 節(jié)點,還可以為回調(diào)函數(shù),回調(diào)函數(shù)接收當前元素和當前元素在集合中的索引兩個參數(shù),返回符合條件的包裹結構。
var func = isFunction(structure)
判斷 structure 是否為函數(shù)
if (this[0] && !func) var dom = $(structure).get(0), clone = dom.parentNode || this.length > 1
如果集合不為空,并且 structure 不為函數(shù),則將 structure 轉換為 node 節(jié)點,通過 $(structure).get(0) 來轉換,并賦給變量 dom。如果 dom 的 parentNode 存在或者集合的數(shù)量大于 1 ,則 clone 的值為 true。
return this.each(function(index) { $(this).wrapAll( func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom ) })
對集合進行遍歷,調(diào)用 wrapAll 方法,如果 structure 為函數(shù),則將回調(diào)函數(shù)返回的結果作為參數(shù)傳給 wrapAll ;
否則,如果 clone 為 true ,則將 dom 也即包裹元素的副本傳給 wrapAll ,否則直接將 dom 傳給 wrapAll。這里傳遞副本的的原因跟生成器中的一樣,也是避免對 dom 節(jié)點的引用。如果 dom 的 parentNode 存在時,表明 dom 本來就從屬于某個節(jié)點,如果直接使用 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) }) },
將集合中每個元素的內(nèi)容都用指定的結構 structure 包裹。 structure 的參數(shù)類型跟 wrap 一樣。
對集合進行遍歷,調(diào)用 contents 方法,獲取元素的內(nèi)容,contents 方法在《讀Zepto源碼之集合元素查找》有過分析。
如果 structure 為函數(shù),則將函數(shù)返回的結果賦值給 dom ,否則將直接將 structure 賦值給 dom。
如果 contents.length 存在,即元素不為空元素,調(diào)用 wrapAll 方法,將元素的內(nèi)容包裹在 dom 中;如果為空元素,則直接將 dom 插入到元素的末尾,也實現(xiàn)了將 dom 包裹在元素的內(nèi)部了。
.unwrap()unwrap: function() { this.parent().each(function() { $(this).replaceWith($(this).children()) }) return this },
當集合中的所有元素的包裹層去掉,也即將父元素去掉,但是保留父元素的子元素。
實現(xiàn)的方法也很簡單,就是遍歷當前元素的父元素,將父元素替換為父元素的子元素。
.clone()clone: function() { return this.map(function() { return this.cloneNode(true) }) },
每集合中每個元素都創(chuàng)建一個副本,并將副本集合返回。
遍歷元素集合,調(diào)用 node 的原生方法 cloneNode 創(chuàng)建副本。要注意,cloneNode 不會將元素原來的數(shù)據(jù)和事件處理程序復制到副本中。
系列文章讀Zepto源碼之代碼結構
讀 Zepto 源碼之內(nèi)部方法
讀Zepto源碼之工具函數(shù)
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
參考Node.insertBefore()
Node.cloneNode()
Zepto源碼分析-zepto模塊
MDN文檔
Node.ownerDocument
Document.defaultView
License最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83308.html
摘要:的模塊用來獲取節(jié)點中的屬性的數(shù)據(jù),和儲存跟相關的數(shù)據(jù)。獲取節(jié)點指定的緩存值。如果存在,則刪除指定的數(shù)據(jù),否則將緩存的數(shù)據(jù)全部刪除。為所有下級節(jié)點,如果為方法,則節(jié)點自身也是要被移除的,所以需要將自身也加入到節(jié)點中。 Zepto 的 Data 模塊用來獲取 DOM 節(jié)點中的 data-* 屬性的數(shù)據(jù),和儲存跟 DOM 相關的數(shù)據(jù)。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā)事件,提交表單。最終返回的結果是一個數(shù)組,每個數(shù)組項為包含和屬性的對象。否則手動綁定事件,如果沒有阻止瀏覽器的默認事件,則在第一個表單上觸發(fā),提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提交表單。 讀 Zepto 源碼系列文章已...
摘要:源碼結構整體結構如果在編輯器中將的源碼折疊起來,看到的就跟上面的代碼一樣。參考源碼分析代碼結構對象思想與源碼分析設計和源碼分析源碼中關于的問題最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關注歡迎提意見 雖然最近工作中沒有怎么用 zepto ,但是據(jù)說 zepto 的源碼比較簡單,而且網(wǎng)上的資料也比較多,所以我就挑了 zepto 下手,希望能為以后閱讀其他框架的源碼打下基礎吧。 源碼版...
摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調(diào)用的時候,會為返回的結果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊是為解決移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了這篇文章作為附文怎樣處理移動端對圖片資源的限制,更詳細地解釋了這個模塊的應用場景。 assets 模塊是為解決 Safari 移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了《How to...
閱讀 867·2021-10-25 09:45
閱讀 3284·2021-09-22 14:58
閱讀 3844·2021-08-31 09:43
閱讀 914·2019-08-30 15:55
閱讀 917·2019-08-29 13:51
閱讀 1225·2019-08-29 13:02
閱讀 3483·2019-08-29 12:52
閱讀 1961·2019-08-26 13:27