-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzepto.js
1732 lines (1476 loc) · 70 KB
/
zepto.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Zepto v1.1.6 - zepto event ajax form ie - zeptojs.com/license */
var Zepto = (function() {
var undefined, key, $, classList,
// 获取数组的slice 和 filter(返回数组中的满足回调函数中指定的条件的元素)方法
emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
document = window.document,
elementDisplay = {}, classCache = {},
cssNumber = {
'column-count': 1,
'columns': 1,
'font-weight': 1,
'line-height': 1,
'opacity': 1,
'z-index': 1,
'zoom': 1
},
// 取出html代码中第一个html标签(或注释),如取出 <p>123</p><h1>345</h1> 中的 <p>
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
// 匹配 <img /> <p></p> 不匹配 <img src=""/> <p>123</p>
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
// 单标签
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
// body html
rootNodeRE = /^(?:body|html)$/i,
// 大写字母
capitalRE = /([A-Z])/g,
// special attributes that should be get/set via method calls
// 应该通过方法调用来设置/获取的特殊属性
methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
table = document.createElement('table'),
tableRow = document.createElement('tr'),
// 指定特殊元素的 容器
containers = {
'tr': document.createElement('tbody'),
'tbody': table,
'thead': table,
'tfoot': table,
'td': tableRow,
'th': tableRow,
// 除了上面指定的,其他所有元素的容器都是 div
'*': document.createElement('div')
},
// interactive ???
readyRE = /complete|loaded|interactive/,
// 匹配一个包括(字母、数组、下划线、-)的字符串
simpleSelectorRE = /^[\w-]*$/,
class2type = {},
toString = class2type.toString,
zepto = {},
camelize, uniq,
tempParent = document.createElement('div'),
// 属性转换为 camalCase 格式。
// $.fn.prop 方法用到了
propMap = {
'tabindex': 'tabIndex',
'readonly': 'readOnly',
'for': 'htmlFor',
'class': 'className',
'maxlength': 'maxLength',
'cellspacing': 'cellSpacing',
'cellpadding': 'cellPadding',
'rowspan': 'rowSpan',
'colspan': 'colSpan',
'usemap': 'useMap',
'frameborder': 'frameBorder',
'contenteditable': 'contentEditable'
},
// 判断是否是arr的函数
isArray = Array.isArray || function(object){ return object instanceof Array }
// 上文定义 zepto = {}
// 判断 element 是否符合 selector 的选择要求
zepto.matches = function(element, selector) {
// selector有值,element有值,element是普通DOM节点
if (!selector || !element || element.nodeType !== 1) return false
// elem.matchesSelector('.item')
// 判断当前的 elem 是否符合传入的 selector 的要求
var matchesSelector = element.webkitMatchesSelector ||
element.mozMatchesSelector ||
element.oMatchesSelector ||
element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
// 浏览器不支持 matchesSelector
// fall back to performing a selector:
var match,
parent = element.parentNode,
temp = !parent
// 上文定义 tempParent = document.createElement('div'),
// 如果没有parent,parent赋值为一个div,然后将当前元素加入到这个div中
if (temp) {
parent = tempParent;
tempParent.appendChild(element);
// (parent = tempParent).appendChild(element); 这种写法不易读
}
// 通过 qsa 获取匹配的元素,判断其中有没有 element
match = ~zepto.qsa(parent, selector).indexOf(element)
if (temp) {
// 如果没有parent时,之前执行过 tempParent.appendChild(element);
// 此时要移除子元素
tempParent.removeChild(element);
}
// temp && tempParent.removeChild(element) // 这种写法不易读
// 返回最终的匹配结果,经过 qsa 判断的结果
return match
}
function type(obj) {
return obj == null ?
String(obj) : // null undefined
class2type[toString.call(obj)] || "object"
// 下文定义:
// // Populate the class2type map
// $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
// class2type[ "[object " + name + "]" ] = name.toLowerCase()
// })
}
function isFunction(value) { return type(value) == "function" }
// window的特点:window.window === window
function isWindow(obj) { return obj != null && obj == obj.window }
// document.nodeType === 9
// elem.DOCUMENT_NODE 也等于 9 (这里直接判断是不是9也行???)
function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
function isObject(obj) { return type(obj) == "object" }
// 判断是否是最基本的object:Object.getPrototypeOf(obj) == Object.prototype
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
// 数组或者对象数组
function likeArray(obj) { return typeof obj.length == 'number' }
// 筛选数组,踢出 null undefined 元素
function compact(array) { return filter.call(array, function(item){ return item != null }) }
// 下文定义:
// $.fn = {
// concat: emptyArray.concat,
// $.fn.concat.apply([], array) —— 无论 array 是不是数组,都将返回一个数组,
// 例如 $.fn.concat.call([], 'abc') 返回的是 ['abc']
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
// camelize 已在上文定义
// 用于 css 的 camalCase 转换,例如 background-color 转换为 backgroundColor
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
// 将 lineHeight 转换为 line-height 格式
function dasherize(str) {
return str.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/_/g, '-')
.toLowerCase()
}
// uniq变量已经在前面定义
// 用来将 [1,1,2,2,3,3] 替换为 [1,2,3]
uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
// 上文定义 classCache = {}
function classRE(name) {
return name in classCache ?
classCache[name] :
(classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
// classCache 存储的数据是这样的:
// {
// abc: /(^|\s)abc(\s|$)/, // 能匹配 'abc' 或 ' abc ' 或 ' abc' 或 'abc '
// xyz: /(^|\s)abc(\s|$)/,
// ...
// }
}
// 传入一个 css 的 name 和 value,判断这个 value 是否需要增加 'px'
function maybeAddPx(name, value) {
// dasherize(name) 将 lineHeight 转换为 line-height 格式
// !cssNumber[dasherize(name)] 判断转换出来的 css name 是否再这个数组之外
return (typeof value == "number" && !cssNumber[dasherize(name)]) ?
// 如果 value 是数字,并且 name 不在 cssNumber 数组之内,就需要加 'px',否则不需要
// 例如 'width'、'font-size' 就需要加 'px', 'font-weight' 就不需要加
value + "px" :
value
// 前文定义----------------------
// cssNumber = {
// 'column-count': 1,
// 'columns': 1,
// 'font-weight': 1,
// 'line-height': 1,
// 'opacity': 1,
// 'z-index': 1,
// 'zoom': 1
// },
// function dasherize(str) {
// return str.replace(/::/g, '/')
// .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
// .replace(/([a-z\d])([A-Z])/g, '$1_$2')
// .replace(/_/g, '-')
// .toLowerCase()
// }
}
// 获取一个元素的默认 display 样式值,可能的结果是:inline block inline-block table .... (none 转换为 block)
// $.fn.show 方法中用到了
function defaultDisplay(nodeName) {
var element, display
// 前文定义 elementDisplay = {}
if (!elementDisplay[nodeName]) {
// 如果 elementDisplay 对象中,没有存储 nodeName 的信息
// 则新建一个 nodeName 元素,添加到 body 中
element = document.createElement(nodeName)
document.body.appendChild(element)
// 获取它的默认的 display 样式信息。
display = getComputedStyle(element, '').getPropertyValue("display")
// 接着马上移除元素!!!
element.parentNode.removeChild(element)
// 'none' 换成 'block',另外还可能是 'inline' 'inline-block' 'table' 等等...
display == "none" && (display = "block")
// 存储下来
elementDisplay[nodeName] = display
// 下文定义
// var nativeGetComputedStyle = getComputedStyle;
// window.getComputedStyle = function(element){
// try {
// return nativeGetComputedStyle(element)
// } catch(e) {
// return null
// }
// }
// 解释:
// 如果浏览器支持 getComputedStyle 则使用,如果不支持,就返回 null
// getComputedStyle(elem, '伪类,如 :link') 返回一个 CSSStyleDeclaration 对象,里面存储了元素的样式信息,可以通过 getPropertyValue('name') 方法获取
}
// 最终返回 display 结果
return elementDisplay[nodeName]
}
// 返回一个元素的子元素,数组形式
function children(element) {
// 有些浏览器支持 elem.children 获取子元素,有些不支持
return 'children' in element ?
// 上文定义 slice = [].slice
// slice.call(likeArr) 可以将对象数组转换为真正的数组
slice.call(element.children) :
// 浏览器不支持 elem.children 只能通过 elem.childNodes 获取子元素
// 只去 node.nodeType == 1 的子元素,通过 $.map 拼接成数组
// $.map 下文定义的, $.map = function (elements, callback) {....}
// $.map 作用:针对 elements(对象数组或数组),对每个元素都经过 callback 函数的过滤,并将过滤通过的元素,push到一个新数组中,返回新数组
$.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}
// 上文定义 zepto = {}
// 上文定义 zepto.matches = function(element, selector) { /* 判断elem是否符合selector的要求 */ }
// `$.zepto.fragment` takes a html string and an optional tag name
// to generate DOM nodes nodes from the given html string.
// The generated DOM nodes are returned as an array.
// This function can be overriden in plugins for example to make
// it compatible with browsers that don't support the DOM fully.
zepto.fragment = function(html, name, properties) {
/*
参数:
@html: 待处理的html字符串
@name: 通过 name 可在 containers 中查找容器节点,如果不传入,取得的容器默认为 div
@properties: 节点属性对象
*/
var dom, nodes, container
// 上文定义:singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, // 匹配 <img /> <p></p> 不匹配 <img src=""/> <p>123</p>
// 如果 html 是单标签,则直接用该标签创建元素
// RegExp.$1 表示正则中的第一个括号匹配的内容,在此即 (\w+) 匹配的内容,
// A special case optimization for a single tag
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
if (!dom) {
// 说明 html 不是单标签,dom未被赋值
// 上文定义 tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, // 单标签
// 将 <p/>或<p />,替换为 <p></p>,将<p abc/>替换为<p>abc</p>
// <input/> (在 tagExpanderRE 中定义)的不替换
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
// fragmentRE = /^\s*<(\w+|!)[^>]*>/, // 取出html代码中第一个html标签(或注释),如取出 <p>123</p><h1>345</h1> 中的 <p>
// 如果 name 未传入,则赋值为 html 的第一个标签
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
// 上文定义
// // 指定特殊元素的 容器
// containers = {
// 'tr': document.createElement('tbody'),
// 'tbody': table,
// 'thead': table,
// 'tfoot': table,
// 'td': tableRow,
// 'th': tableRow,
// // 除了上面指定的,其他所有元素的容器都是 div
// '*': document.createElement('div')
// },
if (!(name in containers)) name = '*'
container = containers[name]
container.innerHTML = '' + html // 转变为字符串的快捷方式
// 遍历 container 的子元素(先转换为数组形式)
// 返回的同时,将每个子元素移除。
// $.each 返回的是一个数组,因为第一个参数就是数组 slice.call(container.childNodes)
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}
// 赋值属性
if (isPlainObject(properties)) {
// 先将dom转换为 zepto 对象
nodes = $(dom)
$.each(properties, function(key, value) {
// 上文定义:
// // 应该通过方法调用来设置/获取的特殊属性
// methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
if (methodAttributes.indexOf(key) > -1) nodes[key](value) // 满足 methodAttributes 的,通过方法赋值
else nodes.attr(key, value) // 否则,通过属性复制
})
}
// 最终返回的dom可能有两种形式
// 第一,如果 html 是单标签,则dom被复制为一个zepto对象 dom = $(document.createElement(RegExp.$1))
// 第二,如果 html 不是单标签,则dom被复制为一个DOM节点的数组
return dom
}
// 上文定义 zepto = {}
// 上文定义 zepto.matches = function(element, selector) { /* 判断elem是否符合selector的要求 */ }
// 上文定义 zepto.fragment = function(html, name, properties) { /* 通过html字符串获取文档碎片 */ }
// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. Note that `__proto__` is not supported on Internet
// Explorer. This method can be overriden in plugins.
zepto.Z = function(dom, selector) {
dom = dom || []
// 将 dom 隐式原型强制改为 $.fn
// 下文 zepto.Z.prototype = $.fn 因此,dom.__proto__ = $.fn 即 dom.__proto__ = zepto.Z.prototype 可以不较真的认为 zepto.Z 就是一个构造函数(但感觉这么设计,有些蹩脚)
dom.__proto__ = $.fn
dom.selector = selector || ''
return dom
}
// `$.zepto.isZ` should return `true` if the given object is a Zepto
// collection. This method can be overriden in plugins.
zepto.isZ = function(object) {
// 上文 dom.__proto__ = $.fn
// 下文 zepto.Z.prototype = $.fn
// 可知:dom.__proto__ === $.fn === zepto.Z.prototype
// 因此,zepto对象都符合 object instanceof zepto.Z
return object instanceof zepto.Z
}
// `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
// takes a CSS selector and an optional context (and handles various
// special cases).
// This method can be overriden in plugins.
zepto.init = function(selector, context) {
var dom
// If nothing given, return an empty Zepto collection
if (!selector) return zepto.Z()
// Optimize for string selectors
else if (typeof selector == 'string') {
// 字符串的情况,一般有两种:
// 第一,一段 html 代码,旨在通过zepto生成dom对象
// 第二,一段查询字符串,旨在通过zepto查找dom对象
// 将查询结果存储到 dom 变量中
selector = selector.trim()
// If it's a html fragment, create nodes from it
// Note: In both Chrome 21 and Firefox 15, DOM error 12
// is thrown if the fragment doesn't begin with <
// 上文定义:
// // 取出html代码中第一个html标签(或注释),如取出 <p>123</p><h1>345</h1> 中的 <p>
// fragmentRE = /^\s*<(\w+|!)[^>]*>/,
if (selector[0] == '<' && fragmentRE.test(selector))
// 第一,RegExp.$1取出来的就是第一个标签名称,即正则中 (\w+|!) 对应的内容
// 第二,此时的 context 应该传入的是css属性对象(这里会产生歧义,老版的不会传入 context)
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// If there's a context, create a collection on that context first, and select
// nodes from there
// 如果 selector 不是html字符串标签,并且 context 有值,则从context中查找
// find 应该是在 $.fn 中定义的,有待解读???
else if (context !== undefined) return $(context).find(selector)
// If it's a CSS selector, use it to select nodes.
// 除了以上情况,就从整个 document 执行 qsa 的查找
else dom = zepto.qsa(document, selector)
}
// If a function is given, call it when the DOM is ready
// 如果是函数,则dom ready时执行,
// ready方法应该在 $.fn 中定义,有待解毒
else if (isFunction(selector)) return $(document).ready(selector)
// If a Zepto collection is given, just return it
// 传入的参数本身就已经是 zepto 对象,则直接返回
else if (zepto.isZ(selector)) return selector
else {
// compact函数:踢出数组中 == null 的元素
// normalize array if an array of nodes is given
if (isArray(selector)) dom = compact(selector)
// 如果传入的是object,直接强制塞进一个数组
// Wrap DOM nodes.
else if (isObject(selector)) dom = [selector], selector = null // 及时清空 selector 不妨碍下面的判断
// 从此往下,感觉和上文 selector 是字符串的情况下,重复了
// ????????
// fragmentRE.test 即判断字符串是否是 html 标签开头(即是否是html fragement)
// If it's a html fragment, create nodes from it
else if (fragmentRE.test(selector))
//此时,context 也是属性集合,不是容器!!!
//(这里会产生歧义,老版的不会传入 context)
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null // 及时清空 selector 不妨碍下面的判断
// If there's a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// And last but no least, if it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// 最终,还是通过 zepto.Z 创建了对象
// 这里的 dom,其实就是一个数组
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context){
return zepto.init(selector, context)
}
// $ 最终被这个匿名函数所返回,并复制给了全局的 Zepto 变量
// 全局的 zepto 变量暴露给了 window,并且可能有一个别名—— $
// 此 $ 非彼 $
// 对于初学者来说,这里肯定非常绕(还不如把这里的 $ 改改名字)
function extend(target, source, deep) {
// key 在上文已经定义,否则就污染全局变量了
for (key in source)
// 深度递归,首先必须 deep 参数为 true
// 其次,source[key] 必须是数组或者对象,才有必要深度递归(否则没必要)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// source[key] 是对象,而 target[key] 不是对象
// 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
// source[key] 是数组,而 target[key] 不是数组
// 则 target[key] = [] 初始化一下,否则递归会出错的
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
// 执行递归
extend(target[key], source[key], deep)
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
// 一般传入的参数会是:
// (targetObj, srcObj1, srcObj2, srcObj1...)
// (true, targetObj, srcObj1, srcObj2, srcObj1...)
// arguments 是对象数组,slice.call 会返回真正的数组(此处返回从第二项开始)
var deep, args = slice.call(arguments, 1)
// 第一个参数是boolean,这里会把第二个参数当做 target,其他的作为 source
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
// 将所有的 source 添加到 target 中
args.forEach(function(arg){ extend(target, arg, deep) })
return target
// 感觉这样设计是比较好,很好的将业务和底层进行了分离(虽然比较简单):
// 核心方法再 function extend(...){...} 中定义,
// 而 $.extend 方法中做一些外围的判断和处理,最终调用 extend 函数去执行
}
// `$.zepto.qsa` is Zepto's CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overriden in plugins.
zepto.qsa = function(element, selector){
/*
@element: 容器
@selector: 选择器
*/
var found,
maybeID = selector[0] == '#',
maybeClass = !maybeID && selector[0] == '.',
// ID或class形式:返回 selector.slice(1) 即ID或者class的值
// 否则:返回 selector,如通过 tagName 查询
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
// 是否是一个简单的字符串(可能是一个复杂的选择器,如 'div#div1 .item[link] .red')
isSimple = simpleSelectorRE.test(nameOnly)
// 上文定义:
// // 匹配一个包括(字母、数组、下划线、-)的字符串
// simpleSelectorRE = /^[\w-]*$/,
// 以下代码的基本思路是:
// 1. 优先通过 ID 获取元素;
// 2. 然后试图通过 className 和 tagName 获取元素
// 3. 最后通过 querySelectorAll 来获取
return (isDocument(element) && isSimple && maybeID) ?
// 这是最简单的形式:容器是document、选择器是一个id
// 因为 getElementById 只能在 document 上用,所以这里单独拿出来
( (found = element.getElementById(nameOnly)) ?
[found] :
[]
) :
(element.nodeType !== 1 && element.nodeType !== 9) ?
// 容器不是一般元素,也不是document,直接返回 []
[] :
// 将获取的所有元素集合,都转换为数组
slice.call(
isSimple && !maybeID
// isSimple情况下,nameOnly 只可能是 className 或者 tagName
// getElementsByClassName 和 getElementsByTagName 可以在 elem 上用,而且比 querySelectorAll 速度快
// 所以,只要elem容器有值,尽量单独拿出来处理
?
maybeClass ?
element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
element.getElementsByTagName(selector) // Or a tag
// 最后其他情况,只能通过 querySelectorAll 来处理
:
element.querySelectorAll(selector) // Or it's not simple, and we need to query all
)
}
// 根据 selector 筛选 nodes
// 并将 nodes 封装为 zepto 对象
// $.fn.filter 下文定义
function filtered(nodes, selector) {
return selector == null ? $(nodes) : $(nodes).filter(selector)
}
// 判断 parent 是否包含 node
$.contains = document.documentElement.contains ?
// 浏览器支持 contains 方法
function(parent, node) {
return parent !== node && parent.contains(node)
} :
// 不支持 contains 方法
function(parent, node) {
while (node && (node = node.parentNode))
if (node === parent) return true
return false
}
// 如果 arg 是函数,则改变函数的执行环境和参数
// 如果不是,直接返回 arg
// $.fn.html 方法就用到了
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
// 设置属性
function setAttribute(node, name, value) {
value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}
// 设置或获取 node 的 className
// 考虑 svg ????
// access className property while respecting SVGAnimatedString
function className(node, value){
var klass = node.className || '',
svg = klass && klass.baseVal !== undefined
// 获取
if (value === undefined) return svg ? klass.baseVal : klass
// 设置
svg ? (klass.baseVal = value) : (node.className = value)
}
// 将字符串变成响应的对象或者值,例如源代码的注释:
// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// "08" => "08"
// JSON => parse if valid
// String => self
function deserializeValue(value) {
try {
return value ?
// value『有值』的情况:
value == "true" || // 如果 value == 'true',那么这个表达式本身就返回 true ,导致整个函数返回true
// value !== 'true' 的情况:
(
value == "false" ? false : // "null" => null
value == "null" ? null : // "null" => null
+value + "" == value ? +value : // 数字:"42" => 42 "42.5" => 42.5 ( 但是 '08' 却不符合这个条件 )
/^[\[\{]/.test(value) ? $.parseJSON(value) : // '[...]' 或者 '{...}'
value // 其他
)
// value『无值』的情况: undefined / '' / flase / 0 / null
: value
} catch(e) {
return value
}
}
// 将上文定义的函数,暴露给 $ 对象(其实 $ 是一个 function)
$.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}
$.inArray = function(elem, array, i){
return emptyArray.indexOf.call(array, elem, i)
}
$.camelCase = camelize
$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}
// plugin compatibility
$.uuid = 0
$.support = { }
$.expr = { }
// 重新组织 elements 对象(数组、对象或者对象数组),针对每一个元素,都用 callback 进行检验
// 检验通过后,将元素push进一个新数组,并返回
$.map = function(elements, callback){
var value, values = [], i, key
// 数组,或者对象数组
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
// 遍历,经过 callback 验证,push到结果中
value = callback(elements[i], i)
if (value != null) values.push(value)
}
// 对象
else
for (key in elements) {
// 遍历,经过 callback 验证,push到结果中
value = callback(elements[key], key)
if (value != null) values.push(value)
}
// 返回数组
// flatten 函数上文定义的,作用:无论 values 是否是数组,都将返回一个正确的数组。例如,传入 'abc' ,返回 ['abc']
return flatten(values)
}
// 遍历 elements 所有元素(数组、对象数组、对象),执行 callback 方法,最终还是返回 elements
// 注意1:callback.call(elements[i], i, elements[i]) 函数执行的环境和参数
// 注意2:=== false) return elements 一旦有函数返回 false,即跳出循环,类似 break
// 注意3:无论哪种情况,最终返回的还是 elements
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}
// 上文定义:filter = emptyArray.filter
// 筛选数组
$.grep = function(elements, callback){
return filter.call(elements, callback)
}
if (window.JSON) $.parseJSON = JSON.parse
// Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
/*
上文将 class2type 赋值为 {}
最终将 class2type 赋值为:
{
'[object boolean]': 'boolean',
'[object number]': 'number',
'[object string]': 'string',
...
}
存储这个数据是为了方便的获取一些对象的类型,
例如 Object.prototype.toString.call([]) 返回的是 '[Object Array]'
那么即可根据这个获取 [] 的类型是 'array'
*/
})
// Define methods that will be available on all
// Zepto collections
$.fn = {
// 为何要这么多数组的方法?
// 因为一个 zepto 对象,本身就是一个数组
// Because a collection acts like an array
// copy over these useful array functions.
forEach: emptyArray.forEach,
reduce: emptyArray.reduce, // 方法何用????
push: emptyArray.push,
sort: emptyArray.sort,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,
// `map` and `slice` in the jQuery API work differently
// from their array counterparts
map: function(fn){
// $.map 上文定义的, $.map = function (elements, callback) {....}
// $.map 作用:针对 elements(对象、对象数组或数组),对每个元素都经过 callback 函数的过滤,并将过滤通过的元素,push到一个新数组中,返回新数组
// 最后,用 $ 封装返回
return $(
// $.map 返回的是一个数组
$.map(this,
// 针对每一个元素,都执行传入的函数,如果函数返回的 !=null 就将插入到新返回的数组
function(el, i){ return fn.call(el, i, el) }
)
)
/*
$('div').map(function(key, value){
return value.id;
// 或者 return this.id;
})
这个结果就是 $(['div1', 'div2' ...])
*/
},
slice: function(){
// 直接数组的slice方法,并将结果用 $ 封装返回
return $(slice.apply(this, arguments))
},
// 在 zepto.init 函数中,当传入的函数是函数时,就用到了 ready
// else if (isFunction(selector)) return $(document).ready(selector)
ready: function(callback){
// need to check if document.body exists for IE as that browser reports
// document ready when it hasn't yet created the body element
// 下文定义:readyRE = /complete|loaded|interactive/,
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
// 返回当前对象
return this
},
get: function(idx){
return idx === undefined ?
slice.call(this) : // 未传参数,直接返回一整个数组
// 有参数,则试图返回单个元素(大于0,小于0 两种情况)
this[
idx >= 0 ?
idx :
idx + this.length
]
},
// 将zepto集合变为纯数组
toArray: function(){ return this.get() },
size: function(){
return this.length
},
// 将元素从这个DOM树中移除
remove: function(){
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
},
each: function(callback){
// [].every ES5中Array的新特性。循环数组每个元素,返回是否符合callback函数的要求
// every 函数返回的是 false 或者 true(不过这里返回什么无所谓,执行就可以了)
emptyArray.every.call(this, function(el, idx){
return callback.call(el, idx, el) !== false
})
// 最后返回本身对象
return this
},
filter: function(selector){
// not函数下文定义
// 如果给not传入的参数是函数,则返回不符合这个函数规则的元素的数组(用 $ 封装)
if (isFunction(selector)) return this.not(this.not(selector))
// 上文定义:zepto.matches 判断elements是否符合 selector 的要求
// zepto.matches = function(element, selector) {...}
return $(filter.call(this, function(element){
// 利用 [].filter 方法做筛选,利用 zepto.matches 做判断
return zepto.matches(element, selector)
}))
},
// $('div') 可能只有三个 div 节点,那么 $('div').add('p') 再三个 div 节点的基础上,增加三个 p 节点
add: function(selector,context){
// uniq函数——数组去重,例如:用来将 [1,1,2,2,3,3] 替换为 [1,2,3]
return $(uniq(this.concat($(selector,context))))
},
is: function(selector){
// 注意:这里只对 this[0] 第一个元素做判断了,其他的元素不管了
return this.length > 0 && zepto.matches(this[0], selector)
},
not: function(selector){
var nodes=[] // 存储最后返回的结果
// 如果参数是函数
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx){
// 遍历对象的所有元素,对每个元素都执行传入的函数
// 当函数返回 false 时(即不符合函数的规则),则将当前元素push到结果中,等待返回
if (!selector.call(this,idx)) nodes.push(this)
})
// 如果参数不是函数
else {
// 为 excludes 赋值
var excludes =
// 如果 selector 是字符串(css选择器),则用filter过滤,将结果存储到 excludes 中
typeof selector == 'string' ? this.filter(selector) :
// 如果 selector 不是字符串
// 如果是数组或者对象数组(并且 selector.item 是函数???),则生成数组,赋值给 excludes
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector)
// 否则直接生成 zepto 对象,赋值给 excludes
: $(selector)
// 至此,excludes 中就存储了通过 selector 查找出来的元素
// [].forEach 是ES5的新特性
this.forEach(function(el){
// 取出 excludes 中不包含的元素,push到结果中
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
// 返回最后的结果,用 $ 封装
return $(nodes)
},
has: function(selector){
// 经过 filter 函数处理,返回的是一个处理后的值
return this.filter(function(){
return isObject(selector) ?
// 如果 seletor 是 object(可能是elem节点),则用 $.contains 判断
$.contains(this, selector) :
// 否则(selector是css选择字符串)则返回find后的size(如果 size === 0 即相当于返回 false)
$(this).find(selector).size()
// $.fn.find 在下文定义
})
},
eq: function(idx){
// 取出指定index的元素
// 可支持 -1、0、1、2 ……
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
},
first: function(){
var el = this[0]
// 不是 object 则直接返回
// 是 object 类型,则用 $ 封装 (因为时刻都要支持链式操作!!!)
return el && !isObject(el) ? el : $(el)
},
last: function(){
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
find: function(selector){
// result 存储返回结果
var result, $this = this
// 如果没有参数,就返回一个空的 zepto 对象
if (!selector) result = $()
// 如果selector是对象
else if (typeof selector == 'object')
result = $(selector).filter(function(){
var node = this
return emptyArray.some.call($this, function(parent){
return $.contains(parent, node)
})
})
// 如果 selector 不是对象(即是css选择器):
// 如果只有一个元素,则使用 qsa 判断,结果经过 $ 封装后赋值给 result
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
// 如果有多个元素,则使用 map 遍历所有元素,使用 qsa 针对每个元素判断,符合条件即返回(map将返回包含符合条件的元素的新数组,并 $ 封装,支持链式操作!!)
else result = this.map(function(){ return zepto.qsa(this, selector) })
// 返回最终结果
return result
},
// 从元素本身开始,逐级向上级元素匹配,并返回最先匹配selector的元素
closest: function(selector, context){
var node = this[0], collection = false
// 如果 selector 是对象,则用 $ 封装后,赋值给 collection
if (typeof selector == 'object') collection = $(selector)
while (
// while循环的判断条件:
// 第一,node有值(node一开始被赋值为对象的第一个元素)
// 第二,collection有值(传入的selector是对象)则collection包含node;collection无值(传入的selector是字符串,css选择),则node满足selector条件
// 满足第一个条件,不满足第二条件,则循环继续(node试图赋值为node.parentNode);否则,循环跳出(说明已经找到了符合条件的父节点)
node && !(
collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)
)
)
// node赋值成 node.parentNode
// 前提条件是:node != context && node 不是 document,如果是这两个条件之一,那就不继续赋值
node = node !== context && !isDocument(node) && node.parentNode