大涛子客栈

原文:zepto-core-1.1.6 源码注释

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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
/* 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:]+)[^>]*)\/>/gi,
// 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 在上文已经定义,否则就污染全局变量了
// 深度递归,首先必须 deep 参数为 true
// 其次,source[key] 必须是数组或者对象,才有必要深度递归(否则没必要)
for (key in source)
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;

// 返回最终结果
return $(node);
},

// 获取对象集合每个元素所有的祖先元素。 $('h1').parents() => [<div#container>, <body>, <html>]
parents: function(selector) {
var ancestors = [],
nodes = this;
while (nodes.length > 0)
// 可能需要执行多次 while 循环
// 每次执行 $.map 函数都会对 nodes 重新赋值,然后再判断是否需要继续循环
// 因为要获取每个元素的所有祖先元素,所以要多次循环
nodes = $.map(nodes, function(node) {
// 使用 $.map(返回符合条件的元素的新数组,并用 $ 封装)遍历所有元素
if (
(node = node.parentNode) &&
!isDocument(node) &&
ancestors.indexOf(node) < 0
) {
// 将符合条件的元素push到结果中
// 条件:不能是 document,结果中元素不能重复。否则不执行push
ancestors.push(node);

// 返回的 node ,将拼接出新数组,重新复制给 nodes,然后试图继续执行 while 循环
return node;
}
});

// 如果css选择器参数给出,过滤出符合条件的元素
return filtered(ancestors, selector);
},

// 获取对象集合中每个元素的直接父元素。如果css选择器参数给出。过滤出符合条件的元素。
parent: function(selector) {
// pluck 函数在下文定义
// parent 函数,只获取第一级父节点即可
return filtered(uniq(this.pluck("parentNode")), selector);
},

// 获得每个匹配元素集合元素的直接子元素,可通过 selector 过滤
children: function(selector) {
return filtered(
this.map(function() {
return children(this);
}),
selector
);
},

// 获得每个匹配元素集合元素的子元素,包括文字和注释节点
contents: function() {
return this.map(function() {
return slice.call(this.childNodes);
});
},

// 获取对象集合中所有元素的兄弟节点,可通过 selector 过滤
siblings: function(selector) {
return filtered(
this.map(function(i, el) {
// 获取兄弟节点
return filter.call(children(el.parentNode), function(child) {
return child !== el;
});
}),
selector
);
},
empty: function() {
return this.each(function() {
this.innerHTML = "";
});
},
// `pluck` is borrowed from Prototype.js
pluck: function(property) {
// 获取自定义属性,返回值,拼接数组
return $.map(this, function(el) {
return el[property];
});
},
show: function() {
// 返回当前对象,保证可链式操作
return this.each(function() {
// 第一步,针对内联样式,将 none 改为空字符串,如 <p id="p2" style="display:none;">p2</p>
this.style.display == "none" && (this.style.display = "");

// 第二步,针对css样式,如果是 none 则修改为默认的显示样式
if (getComputedStyle(this, "").getPropertyValue("display") == "none")
this.style.display = defaultDisplay(this.nodeName);

// show 方法是为了显示对象,而对象隐藏的方式有两种:内联样式 或 css样式
// this.style.display 只能获取内联样式的值(获取属性值)
// getComputedStyle(this, '').getPropertyValue("display") 可以获取内联、css样式的值(获取 renderTree 的值)
// 因此,这两步都要做判断,
});
},
replaceWith: function(newContent) {
// 先在前面插入,然后将当前对象移除
return this.before(newContent).remove();
},
wrap: function(structure) {
// 是否是函数
var func = isFunction(structure);

if (this[0] && !func)
var dom = $(structure).get(0),
// 何时用 clone ?
// 第一,dom.parentNode 说明 dom 在文档结构中,不 clone 就会被移动
// 第二,this.length > 1 说明当前对象有多个元素,每个元素都要添加,所有要clone
clone = dom.parentNode || this.length > 1;

return this.each(function(index) {
// 借用 wrapAll 方法来做包装
$(this).wrapAll(
func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom
);
});
},
// 在所有匹配元素外面包一个单独的结构
wrapAll: function(structure) {
if (this[0]) {
// 先将 structure 插入到文档结构
$(this[0]).before((structure = $(structure)));

var children;
// drill down to the inmost element
// 通过循环,将 structure 重新赋值为当前 structure 的最深处的一个子元素
while ((children = structure.children()).length)
structure = children.first();

// 将所有子元素都包裹进 structure
$(structure).append(this);
}

// 返回当前对象
return this;
},
wrapInner: function(structure) {
// 是否是函数
var func = isFunction(structure);

// 返回对象自身,保证链式操作
return this.each(function(index) {
var self = $(this),
contents = self.contents(),
// 是函数,即获取函数执行的返回结果;否则直接用 structure 参数
dom = func ? structure.call(this, index) : structure;

// 如果当前元素有内容,则通过内容 wrapAll。无内容,则直接用自身的 append 增加
contents.length ? contents.wrapAll(dom) : self.append(dom);
});
},
unwrap: function() {
// 通过 this.parent() 获取每个元素的父节点(集合)
// 遍历这个父节点的集合
this.parent().each(function() {
// 将当前父节点替换为它的子节点
$(this).replaceWith($(this).children());
});

// 返回对象自身,保证链式操作
return this;
},
clone: function() {
// 通过 this.map 循环对象,
// 针对每个元素都返回它的clone 这个 this 和前面的 this 不一样
// 返回新数组(用 $ 封装)
return this.map(function() {
return this.cloneNode(true);
});
},
hide: function() {
return this.css("display", "none");
},
// 切换显示和隐藏
toggle: function(setting) {
/*
@setting
true : 强制切换为 show
false : 强制切换为 hide
*/
return this.each(function() {
var el = $(this);

// 条件判断:
// 如果 setting === undefined 则看 el.css("display") == "none"
// 如果 setting !== undefined 则看 !!setting
(setting === undefined
? el.css("display") == "none"
: setting)
? el.show() // 如果 true 则显示
: el.hide(); // 如果 false 则隐藏
});
},
// 借助 previousElementSibling 属性
prev: function(selector) {
return $(this.pluck("previousElementSibling")).filter(selector || "*");
},
// 借助 nextElementSibling 属性
next: function(selector) {
return $(this.pluck("nextElementSibling")).filter(selector || "*");
},
html: function(html) {
return;

// 情况1:有参数,赋值,并返回自身
0 in arguments
? this.each(function(idx) {
var originHtml = this.innerHTML;
// 传入的 html 参数允许是一个字符串,也允许是一个函数
// 通过 funcArg 函数:
// 1.如果 html 是字符串,则返回html
// 2.如果 html 是函数,则执行执行函数(传入 idx、originHtml),返回函数执行结果
$(this)
.empty()
.append(funcArg(this, html, idx, originHtml));
})
: /*
插播:
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
*/

// 情况2:无参数,取值
0 in this
? this[0].innerHTML // 直接取第一个元素的 innerHTML
: null;
},
text: function(text) {
return;

// 情况1:有参数,赋值,并返回自身
0 in arguments
? this.each(function(idx) {
// funcArg的应用,和html方法中一样
var newText = funcArg(this, text, idx, this.textContent);
this.textContent = newText == null ? "" : "" + newText;
})
: // 情况2:无参数,取值
0 in this
? this[0].textContent
: null; // 直接借用 textContent 属性
},
attr: function(name, value) {
var result;

return;

// 情况1:无第二个参数,读取值(读取值只能读取第一个元素的值)
typeof name == "string" && !(1 in arguments)
? !this.length || this[0].nodeType !== 1
? undefined
: /*
注释:
this[0]是一个DOM节点,有『属性』也有『特性』
result = this[0].getAttribute(name) 试图获取 DOM节点属性
name in this[0] 判断是不是js对象的属性
然后,该返回哪一个就返回哪一个
*/
!(result = this[0].getAttribute(name)) && name in this[0]
? this[0][name]
: result
: // 情况2:有第二个参数,设置值(针对每个元素设置值)
this.each(function(idx) {
if (this.nodeType !== 1) return;

// 传入的参数可能是一个对象集合
// 此时,是不是应该放在『情况1』当中???此时,value根本没有用啊???
if (isObject(name))
for (key in name) setAttribute(this, key, name[key]);
// 传入的不是对象,即设置一个单一的属性。
// 但是,这里的 value 参数可以是一个函数
// funcArg 即处理了 value 是函数和非函数的两种情况
else
setAttribute(
this,
name,
funcArg(this, value, idx, this.getAttribute(name))
);
});
},
removeAttr: function(name) {
return this.each(function() {
this.nodeType === 1 &&
name.split(" ").forEach(
function(attribute) {
setAttribute(this, attribute); // 将属性设置为空,setAttribute会移除属性
},
this // 改参数将成为 forEach 中函数的this
);
});
},

// 读取、设置属性(js对象的属性)
prop: function(name, value) {
// propMap 中存储的:key是html中的属性名称,value是js对象中的属性名称
// 例如,html中的 "class" 在DOM对象中,就需要使用 "className" 这个名字读取,同理于:for maxlength cellspacing 等等
name = propMap[name] || name;

/*
上文定义:
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'
}
*/

return;

// 有第二个参数,设置属性
1 in arguments
? this.each(function(idx) {
// 设置属性值,funcArg处理函数或者非函数
this[name] = funcArg(this, value, idx, this[name]);
})
: // 无第二个参数,读取属性(读取第一个元素的)
this[0] && this[0][name];
},

// 前面加上 'data-' 通过 attr 设置或者读取
data: function(name, value) {
/*
上文定义:
capitalRE = /([A-Z])/g, //大写字母
*/

// 前面加上 'data-' 将 'A' 替换为 '-a'
var attrName = "data-" + name.replace(capitalRE, "-$1").toLowerCase();

var data =
1 in arguments ? this.attr(attrName, value) : this.attr(attrName);

return data !== null ? deserializeValue(data) : undefined;

/*
上文定义的,deserializeValue 函数的作用是:

// 将字符串变成响应的对象或者值,例如源代码的注释:
// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// "08" => "08"
// JSON => parse if valid
// String => self
*/
},
val: function(value) {
return;

// 有参数,设置值
0 in arguments
? this.each(function(idx) {
// 遍历每个元素,直接对 value 属性赋值
this.value = funcArg(this, value, idx, this.value);
})
: // 无参数,读取值
this[0] &&
// 如果元素是 <select multiple> 多选列表
(this[0].multiple
? // 返回所有选中的option的值的数组
$(this[0])
.find("option")
.filter(function() {
return this.selected;
})
.pluck("value")
: /*
上文定义:
pluck: function(property){
return $.map(this, function(el){ return el[property] })
},
*/

// 如果不是,直接获取 value
this[0].value);
},

// 获取、设置元素的 offset
offset: function(coordinates) {
// 如果有 coordinates 参数,设置坐标值,并返回当前对象
if (coordinates)
return this.each(function(index) {
var $this = $(this),
// 支持函数(传入 $this.offset() 做参数)和非函数
coords = funcArg(this, coordinates, index, $this.offset()),
// 找到最近的 “relative”, “absolute” or “fixed” 的祖先元素,并获取它的 offset()
parentOffset = $this.offsetParent().offset(),
// left 和 top 需要去掉定位的祖先元素的 left、top 值
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
};

// static时,设置 top、left是无效的
if ($this.css("position") == "static") props["position"] = "relative";

// 通过 css 赋值
$this.css(props);
});

// 当前对象是空,则返回 null
if (!this.length) return null;

// 如果没有 coordinates 参数,则返回第一个元素的坐标值
var obj = this[0].getBoundingClientRect();
/*
elem.getBoundingClientRect() 返回一个对象,
包含元素的 top bottom left right width height 的值
但是这个 top、bottom、left、right 是相对于浏览器窗口的距离,而不是页面的边界
(注意,elem.getBoundingClientRect()在IE低版本浏览器有2px的兼容问题)

window.pageXOffset 和 window.pageYOffset 可获取网页滚动的距离,
IE低版本需要用 document.body.scrollLeft 和 document.body.scrollTop 兼容
*/
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
};
},

// 设置、获取 css
css: function(property, value) {
// 只有一个参数,获取第一个元素的样式
if (arguments.length < 2) {
var computedStyle,
element = this[0];
if (!element) return; // 如果第一个元素无值,直接返回。否则继续

// 获取元素的计算后的样式
computedStyle = getComputedStyle(element, "");
if (typeof property == "string")
// 情况1,参数为字符串形式
// 先从elem内联样式获取(element.style),此时需要 camelize(property) 转换,如将 background-color 变为 backgroundColor
// 如果未找到,则从css样式获取 computedStyle.getPropertyValue(property)
// (重要)注释:elem.style 只能获取元素设置的内联样式、不能获取css样式;而 getComputedStyle 可获取内联、css样式。
return (
element.style[camelize(property)] ||
computedStyle.getPropertyValue(property)
);
else if (isArray(property)) {
// 情况2,参数为数组形式(注意,此时 isObject 情况尚未判断)
var props = {};
$.each(property, function(_, prop) {
props[prop] =
element.style[camelize(prop)] ||
computedStyle.getPropertyValue(prop);
});
return props; // 返回一个对象
}
}

// 其他情况:有两个参数、property是对象
var css = "";
if (type(property) == "string") {
// 情况1,property 是字符串,设置单个样式
if (!value && value !== 0)
// 如果value参数是 '' null undefined 则移除这个css样式
// 注:此计算只适用于内联样式的删除,对 css 样式无效,因为它只通过 this.style.removeProperty 计算,而 this.style 获取不到css样式
this.each(function() {
this.style.removeProperty(dasherize(property));
});
// value有正常值,将 css 生成一个字符串(如 'font-size:20px')等待赋值给内联样式
// maybeAddPx(property, value) 需要增加 px 的增加上
else css = dasherize(property) + ":" + maybeAddPx(property, value);
} else {
// 情况2,property 是对象(此时就不管第二个参数是什么了,不用第二个参数),一次性设置多个样式
for (key in property)
if (!property[key] && property[key] !== 0)
// 如果对象属性值是 '' null undefined 则移除这个css样式,同理,只针对内联样式
this.each(function() {
this.style.removeProperty(dasherize(key));
});
// 否则,给 css 赋值一个字符串,多样式属性用 ; 隔开
else
css += dasherize(key) + ":" + maybeAddPx(key, property[key]) + ";";
}

// 针对每个元素,设置内联样式(this.style.cssText可获取、设置内联样式)
// 最后返回自身
return this.each(function() {
this.style.cssText += ";" + css;
});

/*
上文定义:
// 将 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()
}
*/
},
// 获取一个元素的索引值(从0开始计数)。当elemen参数没有给出时,返回当前元素在兄弟节点中的位置
index: function(element) {
/*
上文定义:
$.fn.indexOf: emptyArray.indexOf
*/

// 其实 this 本身就是一个数组,数组本身就有 indexOf ,为何还要上文的这个赋值呢?
// 因为上文中,this.__proto__ 修改了,不是 Array.prototype 了,也就没有 indexOf 方法了
// 因此要手动赋值,需要将数组常用的方法在重新赋值给 $.fn.indexOf

return element
? this.indexOf($(element)[0])
: this.parent()
.children()
.indexOf(this[0]);
},
hasClass: function(name) {
if (!name) return false;
return emptyArray.some.call(
this,
function(el) {
// this 就是 classRE(name) 的返回值(返回一个正则)
// function className(node, value){...} 获取或者设置elem的className
return this.test(className(el));
},
classRE(name)
);

// array.some(callback,[ thisObject]); 只要数组中一项符合callback要求,即返回true

/*
// 上文定义 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|$)/,
// ...
// }
}
*/
},
addClass: function(name) {
if (!name) return this;

// 针对所有元素都添加className,最终返回本身
return this.each(function(idx) {
// 说明当前元素不是 DOM node
if (!("className" in this)) return;

// classList 是一开始就定义的空变量
classList = [];
// 获取元素的 clasname // 支持传入函数
var cls = className(this),
newName = funcArg(this, name, idx, cls);
// 把要赋值的值,按照空白分组,遍历
newName.split(/\s+/g).forEach(function(klass) {
// 把当前元素不存在的class,push到classlist中
if (!$(this).hasClass(klass)) classList.push(klass);
}, this);
// 如果classlist有数据,则为当前元素赋值最新的class值(现有的classname和新的classname拼接)
classList.length &&
className(this, cls + (cls ? " " : "") + classList.join(" "));
});
},
removeClass: function(name) {
// 针对所有元素都移除className,最终返回本身
return this.each(function(idx) {
// 说明当前元素不是 DOM node
if (!("className" in this)) return;

// 如果参数空,则移除元素的所有class
if (name === undefined) return className(this, "");

// 获取现有的classname
classList = className(this);
// (可以传入函数)遍历新的classname字符串
funcArg(this, name, idx, classList)
.split(/\s+/g)
.forEach(function(klass) {
// classRE(klass) 返回一个正则,匹配 'classname' 或 ' classname ' 或 ' classname' 或 'classname '
// 针对传入的classname字符串,对每个符合条件的classname,都替换为 ' '(即删除了)
classList = classList.replace(classRE(klass), " ");
});

// 对整理好的classname,重新赋值给当前元素
className(this, classList.trim());
});
},
toggleClass: function(name, when) {
// when 参数相当于一个条件:
// 如果 when === true 则单纯执行 addClass
// 如果 when === false 则单纯执行 removeClass

if (!name) return this;
return this.each(function(idx) {
// name 可接收函数,可以是空白分割开来的多个classname
var $this = $(this),
names = funcArg(this, name, idx, className(this));
// 用空白分割开多个class
names.split(/\s+/g).forEach(function(klass) {
// 如果有 when 参数,则只通过when参数判断,true则只执行addClass,false则只执行removeClass
// 如果没有 when 参数,则判断元素有没有该class,有则移除,没有则添加
(when === undefined
? !$this.hasClass(klass)
: when)
? $this.addClass(klass)
: $this.removeClass(klass);
});
});
},
scrollTop: function(value) {
if (!this.length) return;

// 普通elem有 scrollTop 属性,可以获取或者设置top值
// window对象没有 scrollTop 属性,通过 pageYOffset 获取,通过 scrollTo() 赋值

var hasScrollTop = "scrollTop" in this[0];
// value 无值,获取 top
if (value === undefined)
return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset;
// value 有值,设置 top
return this.each(
hasScrollTop
? function() {
this.scrollTop = value;
}
: function() {
this.scrollTo(this.scrollX, value);
}
); // window.scrollX 获取横向滚动值
},
scrollLeft: function(value) {
if (!this.length) return;
var hasScrollLeft = "scrollLeft" in this[0];
if (value === undefined)
return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset;
return this.each(
hasScrollLeft
? function() {
this.scrollLeft = value;
}
: function() {
this.scrollTo(value, this.scrollY);
}
); // window.scrollX 获取纵向滚动值
},
position: function() {
if (!this.length) return;

var elem = this[0],
// Get *real* offsetParent
offsetParent = this.offsetParent(), // 找到第一个定位过的祖先元素 “relative”, “absolute” or “fixed”
// Get correct offsets
offset = this.offset(), // 获取自身的offset
parentOffset = rootNodeRE.test(offsetParent[0].nodeName)
? { top: 0, left: 0 }
: offsetParent.offset(); // 获取定位祖先元素的offset( body、html直接设置 top:0;left:0 )
// 上文定义: rootNodeRE = /^(?:body|html)$/i,

// 去掉当前元素的 margin 宽度
// Subtract element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat($(elem).css("margin-top")) || 0;
offset.left -= parseFloat($(elem).css("margin-left")) || 0;

// 增加父元素的 border 宽度
// Add offsetParent borders
parentOffset.top +=
parseFloat($(offsetParent[0]).css("border-top-width")) || 0;
parentOffset.left +=
parseFloat($(offsetParent[0]).css("border-left-width")) || 0;

// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
};
},
offsetParent: function() {
// 通过 this.map 遍历当前对象所有元素,进行计算,然后拼接新的数组,并返回。保证链式操作
return this.map(function() {
var parent = this.offsetParent || document.body; // elem.offsetParent 可返回最近的改元素最近的已经定位的父元素
while (
parent &&
!rootNodeRE.test(parent.nodeName) &&
$(parent).css("position") == "static"
)
// 如果获取的parent不是null、不是body或html、而且position==static
// 则继续向上查找 offsetParent、大不了找到 body 为止
parent = parent.offsetParent;

// 最后返回改元素
return parent;
});
}
};

// for now
$.fn.detach = $.fn.remove;

// Generate the `width` and `height` functions
["width", "height"].forEach(function(dimension) {
// 将 width height 变为 Width Height
var dimensionProperty = dimension.replace(/./, function(m) {
return m[0].toUpperCase();
});

$.fn[dimension] = function(value) {
var offset,
el = this[0];

// 情况1,无参数,获取第一个元素的值
if (value === undefined)
return isWindow(el)
? el["inner" + dimensionProperty] // window.innerHeight
: isDocument(el)
? el.documentElement["scroll" + dimensionProperty] // document.documentElement.scrollHeight
: (offset = this.offset()) && offset[dimension];
// this.offset().width
// 情况2,有参数,设置所有元素的值
else
return this.each(function(idx) {
el = $(this);
// 通过 css() 方法设置,支持传入函数
el.css(dimension, funcArg(this, value, idx, el[dimension]()));
});
};
});

// 针对当前元素、遍历子元素,都执行 fun 函数
function traverseNode(node, fun) {
fun(node);
for (var i = 0, len = node.childNodes.length; i < len; i++)
traverseNode(node.childNodes[i], fun);
}

// 上文定义 adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],

// Generate the `after`, `prepend`, `before`, `append`,
// `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
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) {
argType = type(arg);
return argType == "object" || argType == "array" || 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
)
window["eval"].call(window, el.innerHTML);
});
});
});
};

// after => insertAfter
// prepend => prependTo
// before => insertBefore
// append => appendTo
$.fn[
inside ? operator + "To" : "insert" + (operatorIndex ? "Before" : "After")
] = function(html) {
$(html)[operator](this);
return this;
};
});

zepto.Z.prototype = $.fn;

// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq;
zepto.deserializeValue = deserializeValue;
$.zepto = zepto;

return $;
})();

window.Zepto = Zepto;
window.$ === undefined && (window.$ = Zepto);

 评论