上次咱們看了zepto的選擇器方面的東西,其實zepto簡單很大程度是由於他用了最新的檢索器querySelectorAll,
今天咱們來學習下zepto的一些零碎點的地方吧,主要根據zepto官方文檔順序來javascript
該方法用於檢測父節點是否包含給定的dom節點,若是二者相同則返回false
contains 爲javascript的基本語法,首先在ie中,最後其它瀏覽器也擴展了,若是a包含b則返回truecss
1 p = document.getElementById('parent'),2 c = document.getElementById('child');3 log(p.contains(c)); //true4 log(c.contains(p));//false
這個是zepto的實現,與其說實現不如說是封裝html
1 $.contains = function (parent, node) {2 return parent !== node && parent.contains(node)3 }
each這個方法咱們平時用的比較多,而且感受很好用,不少朋友使用for可能會致使這樣那樣的問題,可是使用each卻變好了
是由於each封裝了一個閉包,因此能夠解決一些初學朋友的BUG,到時隱藏的BUG總會爆發,先遇到也不是什麼壞事
zepto的實現以下:java
1 $.each = function (elements, callback) { 2 var i, key 3 if (likeArray(elements)) { 4 for (i = 0; i < elements.length; i++) 5 if (callback.call(elements[i], i, elements[i]) === false) return elements 6 } else { 7 for (key in elements) 8 if (callback.call(elements[key], key, elements[key]) === false) return elements 9 }10 return elements11 }
若是咱們的回調有一個返回了false,那麼就會跳出整個循環,我曾經看到有人在裏面寫break,break對js有點不靠譜的node
咱們這裏提一個可能發生的問題,代碼可能沒有實際意義,大概能夠表達意思:json
1 var sum1 = 0, sum2 = 0, sum3 = 0; len = 2; 2 var arr = []; 3 for (var i = 0; i < len; i++) { 4 arr.push(i) 5 } 6 for (var i = 0; i < len; i++) { 7 setTimeout(function () { 8 sum1 += arr[i]; 9 }, 0);10 }11 $.each(arr, function (i, v) {12 setTimeout(function () {13 sum2 += v;14 }, 0);15 });16 for (var i = 0; i < len; i++) {17 sum3++;18 }19 //sum3無論,答出len=2與len=200000時,sum1,sum2的值20 console.log(sum1);21 console.log(sum2);22 console.log(sum3);
這個例子是我昨天一個問題思考出來的,答案很是經典,由於原本我是想要說明閉包的問題,卻不自主的引入了另一個神器數組
這樣寫的話,不管如何sum1與sum2都是0,就算把len改的很大,明明已通過了1s了答案依舊是0
由此各位能夠意識到settimeout不是我最初想象那麼簡單了,並非多少秒後就會執行,而是徹底從主幹流程脫離出來
主幹若是進行復雜的代碼運算,甚至耗費幾秒,咱們的settimeout也不會執行瀏覽器
好比咱們說下一個例子:閉包
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 <style> 7 #list { display: block; position: absolute; top: 100px; left: 10px; width: 200px; height: 100px; } 8 div { display: block; border: 1px solid black; height: 500px; width: 100%; } 9 #input { width: 80px; height: 200px; display: block; }10 </style>11 <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>12 </head>13 <body>14 <input type="button" value="獲取焦點" id="bt" />15 <div id="divBt" style="width: 100px; height: 40px; background-color: Red;">16 div按鈕</div>17 <br />18 <div id="d">19 <input type="text" id="input" />20 <div id="list">21 </div>22 </div>23 </body>24 <script type="text/javascript">25 var list = $('#list');26 var d = $('#d');27 var input = $('#input');28 input.tap(function (e) {29 e.stopPropagation();30 e.preventDefault();31 input.val(new Date().getTime());32 return false;33 });34 list.tap(function (e) {35 $('body').css("pointer-events", "none");36 list.hide();37 console.log(e);38 setTimeout(function () {39 e.stopPropagation();40 console.log(e);41 }, 0)42 setTimeout(function () {43 list.show();44 }, 1250);45 setTimeout(function () {46 $('body').css("pointer-events", "auto");47 }, 50);48 });49 d.tap(function () {50 d.append($('<p>div tap</p>'));51 });52 $('#bt').tap(function () {53 var s = input.val() + new Date().getTime();54 input.val(s)55 input.focus();56 });57 $('#divBt').tap(function () {58 input.focus();59 });60 </script>61 </html>
這個例子是我最近遇到的一個問題,我的認爲比較經典,咱們點擊裏面的div會冒泡執行外面的div事件,可是咱們能夠 e.stopPropagation();
這樣阻止冒泡,可是若是咱們代碼寫成這樣的話:app
1 setTimeout(function () {2 list.show();3 }, 1250);
那麼對不起,阻止冒泡是不起做用的
這個問題影響比較深遠好比zepto的touch源碼最後關鍵部分:
1 on('touchend MSPointerUp', function (e) { 2 // ...... 3 else if ('last' in touch) 4 if (deltaX < 30 && deltaY < 30) { 5 tapTimeout = setTimeout(function () { 6 var event = $.Event('tap') 7 event.cancelTouch = cancelAll 8 touch.el.trigger && touch.el.trigger(event) 9 if (touch.isDoubleTap) {10 touch.el.trigger && touch.el.trigger('doubleTap')11 touch = {}12 }13 else {14 touchTimeout = setTimeout(function () {15 touchTimeout = null16 touch.el.trigger && touch.el.trigger('singleTap')17 touch = {}18 }, 250)19 }20 }, 0)21 } else {22 touch = {}23 }24 deltaX = deltaY = 025 })
這裏觸發了tap事件(touch.el.trigger(event) ),可是在這個位置執行什麼阻止冒泡等操做毫無心義,緣由就是外層的settimeout(function(){}, 0)
好了,這裏扯得有點遠,咱們繼續剛剛的閉包問題,若是咱們將最後打印改爲這樣,答案依舊難辨,由於咱們看着題目容易臆測,而忽略實際問題:
1 setTimeout(function () {2 console.log(sum1);3 console.log(sum2);4 console.log(sum3);5 }, 1)
這裏sum1的值竟然是NaN,由於我覺得他會是0undefined,因此不能臆測啊!
這裏說回來你們都會知道產生了閉包,而each解決了閉包問題,而for最後i的值是2,而咱們的arr天然取不到值
PS:不知道這個例子能夠說明each的用途沒有......
該方法比較有用,用於經過源對象擴展目標對象屬性,源對象屬性將覆蓋目標對象屬性,默認爲淺賦值,true的話便會連餘下對象一塊兒複製
1 var target 2 = { one: 3 'patridge' 4 }, 5 source = { 6 two: 'turtle doves' 7 } 8 $.extend(target, source) 9 //=> { one: 'patridge',10 // two: 'turtle doves' }
咱們來看看源碼實現:
1 function extend(target, source, deep) { 2 for (key in source) 3 //若是深度擴展 4 if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { 5 //若是要擴展的數據是對象且target相對應的key不是對象 6 if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {} 7 //若是要擴展的數據是數組且target相對應的key不是數組 8 if (isArray(source[key]) && !isArray(target[key])) target[key] = [] 9 extend(target[key], source[key], deep)10 } else if (source[key] !== undefined) target[key] = source[key]11 }
這個代碼很清晰,不是深度擴展時候僅僅是單純的複製,由此可能能夠回答一些可能被問到的問題:
源對象和複製對象有相同的屬性會被覆蓋嗎?答案是會的
而後看這個深度拷貝,就會遞歸的將複製對象的對象給複製過去
PS:深度克隆與淺克隆的區別是複製對象變了源對象相關屬性不會跟着改變,這就是區別(在java就是引用與值的區別)
這個方法是用於篩選數組的,新數組只包含回調函數中返回 ture 的數組項,這個代碼就不關注了,他底層仍是調用的javascript數組的方法
1 if (!Array.prototype.filter) 2 { 3 Array.prototype.filter = function(fun /*, thisp*/) 4 { 5 var len = this.length; 6 if (typeof fun != "function") 7 throw new TypeError(); 8 9 var res = new Array();10 var thisp = arguments[1];11 for (var i = 0; i < len; i++)12 {13 if (i in this)14 {15 var val = this[i]; // in case fun mutates this16 if (fun.call(thisp, val, i, this))17 res.push(val);18 }19 }20 return res;21 };22 }23 24 $.inArray(element, array, [fromIndex])
搜索數組中指定值並返回它的索引(若是沒有找到則返回-1)。[fromIndex] 參數可選,表示從哪一個索引值開始向後查找。
這個函數底層依舊是調用javascript數組原生的方法:
1 $.inArray = function (elem, array, i) {2 return emptyArray.indexOf.call(array, elem, i)3 }
這個方法在最新的javascript語法出來時很是有用,咱們原來通常是這樣乾的:
var json = eval('(' + str + ')');
後面咱們就這樣幹了:
if (window.JSON) $.parseJSON = JSON.parse
因此這個方法,咱們暫時沒必要關注了,由於zepto面向的是高版本瀏覽器,因此他基本也不關注這個問題
好了,咱們看到這裏有幾個方法比較重要了!
添加元素到匹配的元素集合。若是content參數存在,只在content中進行查找,不然在document中查找。
1 <ul> 2 <li>list item 1</li> 3 <li>list item 2</li> 4 <li>list item 3</li> 5 </ul> 6 <p>a paragraph</p> 7 8 <script type="text/javascript"> 9 $('li').add('p').css('background-color', 'red');10 </script>
1 add: function (selector, context) {2 return $(uniq(this.concat($(selector, context)))) //追加並去重3 },4 5 uniq = function (array) {6 return filter.call(array, function (item, idx) {7 return array.indexOf(item) == idx8 })9 }
PS:concat是數組自己的方法
咱們這裏來一點點搞下這個代碼邏輯:
① $(selector, context)
該方法爲一個dom選擇器,根據咱們上次的研究,他會返回咱們的封裝後的dom集合
② this.concat(el)
咱們知道this當前指向就是被包裝的dom數組對象,因此這裏就將兩個方法鏈接起來了
如今無論真實頁面結構渲染是否變化,反正包裝的dom結構被鏈接了
③ uniq(el)
稍後,這個代碼讀了後我整我的迷糊了!!!咱們來看個例子
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script> 5 <script src="../../zepto.js" type="text/javascript"></script> 6 </head> 7 <body> 8 <ul> 9 <li>list item 1</li>10 <li>list item 2</li>11 <li>list item 3</li>12 </ul>13 <p>14 a paragraph</p>15 </body>16 <script type="text/javascript">17 $('ul').add('p').css('background-color', 'red');18 </script>19 </html>
按照他的意思,咱們ul就應該加到p後面,可是運行結構並非這樣的......因此該方法暫時忽略......
好了,咱們來看看咱們的addClass是幹什麼的
1 addClass: function (name) { 2 return this.each(function (idx) { 3 classList = [] 4 var cls = className(this), 5 newName = funcArg(this, name, idx, cls) 6 //處理同時多個類的狀況,用空格分開 7 newName.split(/\s+/g).forEach(function (klass) { 8 if (!$(this).hasClass(klass)) classList.push(klass) 9 }, this)10 classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))11 })12 },
這個each是能夠理解的,就是操做每個dom結構,因此咱們將代碼當作這個樣子:
1 classList = [] 2 var cls = className(this), 3 newName = funcArg(this, name, idx, cls) 4 //處理同時多個類的狀況,用空格分開 5 newName.split(/\s+/g).forEach(function (klass) { 6 if (!$(this).hasClass(klass)) classList.push(klass) 7 }, this) 8 classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) 9 10 這裏有使用了className方法,因此咱們來看看:11 12 function className(node, value) {13 var klass = node.className, svg = klass && klass.baseVal !== undefined14 if (value === undefined) return svg ? klass.baseVal : klass15 svg ? (klass.baseVal = value) : (node.className = value)16 }
多餘的東西也無論,意思就是沒有value就是獲取值,有就是設置,這裏是原生的dom操做
而後是funcArg方法
1 //這個函數在整個庫中取着很得要的做用,處理arg爲函數或者值的狀況2 //下面不少設置元素屬性時的函數都有用到3 function funcArg(context, arg, idx, payload) {4 return isFunction(arg) ? arg.call(context, idx, payload) : arg5 }
這個第二個參數能夠是一個函數,若是是就執行,並使用自己做爲做用域,若是不是就本身返回,咱們如今的作法就直接返回class名了
下面的代碼就比較簡單了,就是拼接字符串,組成新的class,而後賦給dom就結束了,因此addClass也就結束了,removeClass咱們就無論了
這個方法很是龐大,雖然只有這麼一個方法,可是before、after等都在這裏實現了
1 adjacencyOperators = ['after', 'prepend', 'before', 'append'] 2 adjacencyOperators.forEach(function (operator, operatorIndex) { 3 var inside = operatorIndex % 2 //=> prepend, append 4 $.fn[operator] = function () { 5 // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings 6 var argType, nodes = $.map(arguments, function (arg) { 7 argType = type(arg) 8 return argType == "object" || argType == "array" || arg == null ? arg : zepto.fragment(arg) 9 }),10 parent, copyByClone = this.length > 1 //若是集合的長度大於集,則須要clone被插入的節點11 if (nodes.length < 1) return this12 return this.each(function (_, target) {13 parent = inside ? target : target.parentNode14 //經過改變target將after,prepend,append操做轉成before操做,insertBefore的第二個參數爲null時等於appendChild操做15 target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null16 nodes.forEach(function (node) {17 if (copyByClone) node = node.cloneNode(true)18 else if (!parent) return $(node).remove()19 //插入節點後,若是被插入的節點是SCRIPT,則執行裏面的內容並將window設爲上下文20 traverseNode(parent.insertBefore(node, target), function (el) {21 if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && (!el.type || el.type === 'text/javascript') && !el.src) window['eval'].call(window, el.innerHTML)22 })23 })24 })25 }26 // after => insertAfter27 // prepend => prependTo28 // before => insertBefore29 // append => appendTo30 $.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function (html) {31 $(html)[operator](this)32 return this33 }34 })
這裏inside只會取到0,1兩種值,而後就開始初始化方法了,好比:
$.fn.append = function() {};
由於咱們調用通常是這樣乾的:
$('$id').append($('dom'))
這裏的this就是前面的dom集合,由於可能不止一個節點,因此每一個節點都會被插入新節點,可是咱們通常只給一個節點插東西
這裏用到了map方法,咱們來看看:
1 //遍歷elements,將每條記錄放入callback裏進憲處理,保存處理函數返回值不爲null或undefined的結果 2 //注意這裏沒有統一的用for in,是爲了不遍歷數據默認屬性的狀況,如數組的toString,valueOf 3 $.map = function (elements, callback) { 4 var value, values = [], 5 i, key 6 //若是被遍歷的數據是數組或者nodeList 7 if (likeArray(elements)) for (i = 0; i < elements.length; i++) { 8 value = callback(elements[i], i) 9 if (value != null) values.push(value)10 } else11 //若是是對象12 for (key in elements) {13 value = callback(elements[key], key)14 if (value != null) values.push(value)15 }16 return flatten(values)17 }
咱們從代碼看來,這個方法是用於數組過濾,與filter有點相似
因此,這裏的使用map函數保證了nodes是比較靠譜的dom節點集合,若是長度爲0 就直接返回了
而後下面開始遍歷咱們的this dom節點,依次作操做,這裏有個須要注意的地方,若是this包含的節點不止一個,那麼每一個節點都會被插入
因此他這裏提供了一個克隆的功能,可能出來節點,由於dom上只有一個節點,被不斷的append也只是移動位置
cloneNode是javascript dom 自己的一個方法,直接使用便可,可是要注意id不要重複
而後根據inside不一樣而選取不一樣的parentNode,這應該與插入點有關係了,由於原生javascript只支持appendChild與insertBefore
這裏調用traverseNode方法前,就將dom操做結束了,
這裏還區分了是否是script標籤,這裏又有一個較關鍵的方法:traverseNode,他會執行咱們的javascript
1 function traverseNode(node, fun) {2 fun(node)3 for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)4 }
這裏有兩個循環,外層each裏層forEach,因此節點就所有插入了......至此這個方法也基本結束
值得一提的是最後這個代碼段:
1 $.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function (html) {2 $(html)[operator](this)3 return this4 }5 //至關於:6 $.fn.insertAfter = function(html) {7 $(html).prepend(this);8 retrun;9 }
其中,this指的是包裝的dom集合,html爲咱們傳入的dom對象或者dom字符串,下面的方法就是咱們上面定義的
該方法,比較經常使用,咱們通常用他來爲dom元素設置屬性,獲取屬性,可是他還能夠傳入函數哦......
1 attr = function (name, value) { 2 var result 3 //當只有name且爲字符串時,表示獲取第一條記錄的屬性 4 return (typeof name == 'string' && value === undefined) ? 5 //集合沒有記錄或者集合的元素不是node類型,返回undefined 6 (this.length == 0 || this[0].nodeType !== 1 ? undefined : 7 //若是取的是input的value 8 (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() : 9 //注意直接定義在node上的屬性,在標準瀏覽器和ie9,10中用getAttribute取不到,獲得的結果是null10 //好比div.aa = 10,用div.getAttribute('aa')獲得的是null,須要用div.aa或者div['aa']這樣來取11 (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result) :12 this.each(function (idx) {13 if (this.nodeType !== 1) return14 //若是name是一個對象,如{'id':'test','value':11},則給數據設置屬性15 if (isObject(name)) for (key in name) setAttribute(this, key, name[key])16 //若是name只是一個普通的屬性字符串,用funcArg來處理value是值或者function的狀況最終返回一個屬性值17 //若是funcArg函數返回的是undefined或者null,則至關於刪除元素的屬性18 else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))19 })20 }
獲取代碼比較簡單,值得注意的是,他只會獲取咱們第一個dom屬性的值
this[0].getAttribute(name)
設置值的時候固然又是一個循環了(setAttribute)
這個方法也比較經常使用,很大狀況下雨attr比較相似,看源碼前咱們先思考下,爲何沒有removeCss
固然是由於,咱們樣式表問題,因此removeCss就沒有意義了
好了,如今咱們來看看源碼:
1 css: function (property, value) { 2 //獲取指定的樣式 3 if (arguments.length < 2 && typeof property == 'string') return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property)) 4 //設置樣式 5 var css = '' 6 if (type(property) == 'string') { 7 if (!value && value !== 0) //當value的值爲非零的能夠轉成false的值時如(null,undefined),刪掉property樣式 8 this.each(function () { 9 //style.removeProperty 移除指定的CSS樣式名(IE不支持DOM的style方法)10 this.style.removeProperty(dasherize(property))11 })12 else css = dasherize(property) + ":" + maybeAddPx(property, value)13 } else {14 //當property是對象時15 for (key in property)16 if (!property[key] && property[key] !== 0)17 //當property[key]的值爲非零的能夠轉成false的值時,刪掉key樣式18 this.each(function () {19 this.style.removeProperty(dasherize(key))20 })21 else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'22 }23 //設置24 return this.each(function () {25 this.style.cssText += ';' + css26 })27 }
camelize是將aa-bb這種明明改成aaBb這種駝峯命名,首先比較簡單,會從style裏面獲取style的值,不行就看樣式表
具代碼,是對象狀況還要作其它處理,其中好像給css設置爲null時候能夠取消樣式,咱們來試試(IE無論)
最後試驗證實是不靠譜的,因此咱們不要向去removeCss了吧:
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 5 <style> 6 #list { display: block; position: absolute; top: 100px; left: 10px; width: 200px; height: 100px; background-color: black; } 7 </style> 8 <script src="../../zepto.js" type="text/javascript"></script> 9 </head>10 <body>11 <div id="list">12 </div>13 </body>14 <script type="text/javascript">15 $('#list').css('background-color', false);16 alert($('#list').css('background-color'))17 </script>18 </html>
這個與el.css('width')相似:
1 ['width', 'height'].forEach(function (dimension) { 2 $.fn[dimension] = function (value) { 3 var offset, el = this[0], 4 //將width,hegiht轉成Width,Height,用於取window或者document的width和height 5 Dimension = dimension.replace(/./, function (m) { 6 return m[0].toUpperCase() 7 }) 8 //沒有參數爲獲取,獲取window的width和height用innerWidth,innerHeight 9 if (value === undefined) return isWindow(el) ? el['inner' + Dimension] :10 //獲取document的width和height時,用offsetWidth,offsetHeight11 isDocument(el) ? el.documentElement['offset' + Dimension] : (offset = this.offset()) && offset[dimension]12 else return this.each(function (idx) {13 el = $(this)14 el.css(dimension, funcArg(this, value, idx, el[dimension]()))15 })16 }17 })
該方法也比較經常使用,在頁面dom加載結束後執行裏面的方法
DOMContentLoaded事件是文檔加載結束後執行,老瀏覽器不支持就是load
1 ready: function (callback) {2 if (readyRE.test(document.readyState)) callback($)3 else document.addEventListener('DOMContentLoaded', function () {4 callback($)5 }, false)6 return this7 },
今天暫時到這,咱們下次看看zepto事件相關的實現