【zepto学习笔记02】零碎点

前言

上次咱们看了zepto的选择器方面的东西,其实zepto简单很大程度是由于他用了最新的检索器querySelectorAll,
今天咱们来学习下zepto的一些零碎点的地方吧,主要根据zepto官方文档顺序来javascript

contains (parent, node)

该方法用于检测父节点是否包含给定的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

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);

复制代码

这个例子是我昨天一个问题思考出来的,答案很是经典,由于原本我是想要说明闭包的问题,却不自主的引入了另一个神器数组

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();
这样阻止冒泡,可是若是咱们代码写成这样的话: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的用途没有......

$.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]11 }

复制代码

这个代码很清晰,不是深度扩展时候仅仅是单纯的复制,由此可能能够回答一些可能被问到的问题:
源对象和复制对象有相同的属性会被覆盖吗?答案是会的

而后看这个深度拷贝,就会递归的将复制对象的对象给复制过去

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 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 }

$.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) == 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

好了,咱们来看看咱们的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咱们就无论了

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 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字符串,下面的方法就是咱们上面定义的

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取不到,获得的结果是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)

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 += ';' + 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>

复制代码

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,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 })

复制代码

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 this7 },

复制代码

结语

今天暂时到这,咱们下次看看zepto事件相关的实现

相关文章
相关标签/搜索