前言
上次我們看了zepto的選擇器方面的東西,其實zepto簡單很大程度是因為他用了最新的檢索器querySelectorAll,
今天我們來學習下zepto的一些零碎點的地方吧,主要根據zepto官方文檔順序來
contains (parent, node)
該方法用于檢測父節點是否包含給定的dom節點,如果兩者相同則傳回false
contains 為javascript的基本文法,首先在ie中,最後其它浏覽器也擴充了,如果a包含b則傳回true
1 p = document.getElementById('parent'),
2 c = document.getElementById('child');
3 log(p.contains(c)); //true
4 log(c.contains(p));//false
這個是zepto的實作,與其說實作不如說是封裝
1 $.contains = function (parent, node) {
2 return parent !== node && parent.contains(node)
3 }
each
each這個方法我們平時用的比較多,并且感覺很好用,很多朋友使用for可能會導緻這樣那樣的問題,但是使用each卻變好了
是因為each封裝了一個閉包,是以可以解決一些初學朋友的BUG,到時隐藏的BUG總會爆發,先遇到也不是什麼壞事
zepto的實作如下:
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 elements
11 }
如果我們的回調有一個傳回了false,那麼就會跳出整個循環,我曾經看到有人在裡面寫break,break對js有點不靠譜的
我們這裡提一個可能發生的問題,代碼可能沒有實際意義,大概可以表達意思:
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);
這個例子是我昨天一個問題思考出來的,答案非常經典,因為本來我是想要說明閉包的問題,卻不自主的引入了另外一個神器
settimeout
這樣寫的話,無論如何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();
這樣阻止冒泡,但是如果我們代碼寫成這樣的話:
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 = null
16 touch.el.trigger && touch.el.trigger('singleTap')
17 touch = {}
18 }, 250)
19 }
20 }, 0)
21 } else {
22 touch = {}
23 }
24 deltaX = deltaY = 0
25 })
這裡觸發了tap事件(touch.el.trigger(event) ),但是在這個位置執行什麼阻止冒泡等操作毫無意義,原因就是外層的settimeout(function(){}, 0)
好了,這裡扯得有點遠,我們繼續剛剛的閉包問題,如果我們将最後列印改成這樣,答案依舊難辨,因為我們看着題目容易臆測,而忽略實際問題:
2 console.log(sum1);
3 console.log(sum2);
4 console.log(sum3);
5 }, 1)
這裡sum1的值居然是NaN,因為我以為他會是0undefined,是以不能臆測啊!
這裡說回來大家都會知道産生了閉包,而each解決了閉包問題,而for最後i的值是2,而我們的arr自然取不到值
PS:不知道這個例子可以說明each的用途沒有......
$.extend(target, [source, [source2, ...]])
該方法比較有用,用于通過源對象擴充目标對象屬性,源對象屬性将覆寫目标對象屬性,預設為淺指派,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]
這個代碼很清晰,不是深度擴充時候僅僅是單純的複制,由此可能可以回答一些可能被問到的問題:
源對象和複制對象有相同的屬性會被覆寫嗎?答案是會的
然後看這個深度拷貝,就會遞歸的将複制對象的對象給複制過去
PS:深度克隆與淺克隆的差別是複制對象變了源對象相關屬性不會跟着改變,這就是差別(在java就是引用與值的差別)
$.grep(items, function(item){ ... })
這個方法是用于篩選數組的,新數組隻包含回調函數中傳回 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 this
16 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)
$.parseJSON(string)
這個方法在最新的javascript文法出來時非常有用,我們原來一般是這樣幹的:
var json = eval('(' + str + ')');
後面我們就這樣幹了:
if (window.JSON) $.parseJSON = JSON.parse
是以這個方法,我們暫時不必關注了,因為zepto面向的是高版本浏覽器,是以他基本也不關注這個問題
好了,我們看到這裡有幾個方法比較重要了!
add(selector, [context])
添加元素到比對的元素集合。如果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) == idx
8 })
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
好了,我們來看看我們的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 !== undefined
14 if (value === undefined) return svg ? klass.baseVal : klass
15 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) : arg
5 }
這個第二個參數可以是一個函數,如果是就執行,并使用本身作為作用域,如果不是就自己傳回,我們現在的做法就直接傳回class名了
下面的代碼就比較簡單了,就是拼接字元串,組成新的class,然後賦給dom就結束了,是以addClass也就結束了,removeClass我們就不管了
append
這個方法非常龐大,雖然隻有這麼一個方法,但是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 this
12 return this.each(function (_, target) {
13 parent = inside ? target : target.parentNode
14 //通過改變target将after,prepend,append操作轉成before操作,insertBefore的第二個參數為null時等于appendChild操作
15 target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null
16 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 => insertAfter
27 // prepend => prependTo
28 // before => insertBefore
29 // append => appendTo
30 $.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function (html) {
31 $(html)[operator](this)
32 return this
33 }
34 })
這裡inside隻會取到0,1兩種值,然後就開始初始化方法了,比如:
.fn.append=function();因為我們調用一般是這樣幹的:.fn.append=function();因為我們調用一般是這樣幹的:('id′).append(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 } else
11 //如果是對象
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 this
5 //相當于:
6 $.fn.insertAfter = function(html) {
7 $(html).prepend(this);
8 retrun;
其中,this指的是包裝的dom集合,html為我們傳入的dom對象或者dom字元串,下面的方法就是我們上面定義的
attr(name, value)
該方法,比較常用,我們一般用他來為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取不到,得到的結果是null
10 //比如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) return
14 //如果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)
css(property, value)
這個方法也比較常用,很大情況下雨attr比較類似,看源碼前我們先思考下,為什麼沒有removeCss
為什麼沒有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 += ';' + css
26 })
27 }
camelize是将aa-bb這種明明改為aaBb這種駝峰命名,首先比較簡單,會從style裡面擷取style的值,不行就看樣式表
具代碼,是對象情況還要做其它處理,其中好像給css設定為null時候可以取消樣式,我們來試試(IE不管)
最後試驗證明是不靠譜的,是以我們不要向去removeCss了吧:
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>
width/height
這個與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,offsetHeight
11 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 })
ready
該方法也比較常用,在頁面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 this
7 },
結語
今天暫時到這,我們下次看看zepto事件相關的實作
本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/3447767.html,如需轉載請自行聯系原作者