-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathunderscore-notes.js
More file actions
1894 lines (1602 loc) · 75.1 KB
/
underscore-notes.js
File metadata and controls
1894 lines (1602 loc) · 75.1 KB
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
// Underscore.js 1.8.3
// http://underscorejs.org
// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function() {
// Baseline setup
// --------------
// 保存全局对象
// 在浏览器中是 window
// 在服务端是 exports
// Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// 保存全局对象上的_属性
// 在后面的noConflict会有用
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// 保存Array、Object、Function的prototype引用
// 方便压缩而不是gzip
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// 保存一些常见的数组或者对象方法引用
// Create quick reference variables for speed access to core prototypes.
var
push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// 保存es5中常见的部分方法
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind,
nativeCreate = Object.create;
// Naked function reference for surrogate-prototype-swapping.
var Ctor = function(){};
// _构造函数,实现了非new调用也可以返回实例对象,可以想想jQuery的使用方式
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
if (obj instanceof _) return obj; // 如果obj已经是_的实例,则直接返回
if (!(this instanceof _)) return new _(obj); // 如果调用方式是_()的形式则手动new _()调用返回
this._wrapped = obj; // 将obj数据存放在_wrapped属性上方便后面使用
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// Current version.
_.VERSION = '1.8.3';
// 一个经过了优化的回调函数返回函数
// 我觉得其实目的就是两个
// 1 绑定this作用域
// 2 尽量使用指定参数,而不是arguments
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
// 内部函数cb
// 如果value为空,就返回一个返回参数自身的回调函数
// 如果value为一个函数,就返回一个绑定了this作用域的回调函数
// 如果value为一个对象,就返回一个是否匹配属性的函数
// 否则返回一个读取对象value属性的回调函数
// A mostly-internal function to generate callbacks that can be applied
// to each element in a collection, returning the desired result — either
// identity, an arbitrary callback, a property matcher, or a property accessor.
var cb = function(value, context, argCount) {
if (value == null) return _.identity;
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
if (_.isObject(value)) return _.matcher(value);
return _.property(value);
};
// 一个重要的内部函数用来生成可应用到集合中每个元素的回调
// 调用了内部函数cb,解析请看上个函数
_.iteratee = function(value, context) {
return cb(value, context, Infinity);
};
// 这个函数非常重要,为下文中的extend、extendOwn、assign、defaults提供生成的来源
// extend 拷贝obj后的参数对象到obj上,并且相同的属性后者会覆盖前者(这里的拷贝包括原型上的)
// extendOwn、assign拷贝obj后的参数对象到obj上,并且相同的属性后者会覆盖前者(这里的拷贝不包括原型上的)
// defaults 拷贝obj后的参数对象到obj上,并且相同的属性只会进行一次覆盖即前者key对应的value为undefined的时候(这里的拷贝包括原型上的)
// 特别注意下划线中的extend、extendOwn、assign、defaults的拷贝不是深复制
// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, undefinedOnly) {
return function(obj) {
var length = arguments.length;
if (length < 2 || obj == null) return obj; // 当参数只有obj一个或者obj为空时 直接返回obj
for (var index = 1; index < length; index++) { // 从第二个参数开始拷贝
var source = arguments[index], // 取出其中一个source
keys = keysFunc(source), // keysFunc对应的是_.keys || _.allKeys
l = keys.length;
for (var i = 0; i < l; i++) { // 循环往obj中拷贝
var key = keys[i];
// 因为extend和extendOwn、assign传的undefinedOnly为undefined,而defaults传的是true,所以正好实现了几个函数对应的功能
if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
};
};
// 常见的实现继承的方式之一
// An internal function for creating a new object that inherits from another.
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {}; // 如果prototype不是object类型直接返回空对象
if (nativeCreate) return nativeCreate(prototype); // 如果原生支持create则用原生的
Ctor.prototype = prototype; // 将prototype赋值为Ctor构造函数的原型
var result = new Ctor; // 创建一个Ctor实例对象
Ctor.prototype = null; // 为了下一次使用,将原型清空
return result; // 最后将实例返回
};
// 返回一个能够读取obj对象的key属性的函数
var property = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key]; // 如果传入的obj为null或者undefined就返回undefined,否则返回obj的key属性
};
};
// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = property('length');
// 判断对象是否为类数组
// 判断的依据是对象含有的length属性值是否是有意义的正数
// 其实函数和window对象都有length属性,所以这里判断是不够严谨的
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
// Collection Functions
// --------------------
// 模拟数组的forEach方式
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles raw objects in addition to array-likes. Treats all
// sparse array-likes as if they were dense.
_.each = _.forEach = function(obj, iteratee, context) {
iteratee = optimizeCb(iteratee, context); // 绑定作用后的函数
var i, length;
if (isArrayLike(obj)) { // 如果是类数组类型的obj
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else { // 支持对象类型的数据迭代
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};
// 模拟原生数组的map方法,不同之处在于其还可以对对象起作用
// Return the results of applying the iteratee to each element.
_.map = _.collect = function(obj, iteratee, context) {
iteratee = cb(iteratee, context); // 老规矩,先进行一下作用域绑定
var keys = !isArrayLike(obj) && _.keys(obj), // 非类数组对象就用keys读取obj的key
length = (keys || obj).length,
results = Array(length);
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index; // 数组是0,1,2 对象是对应的key
results[index] = iteratee(obj[currentKey], currentKey, obj); // 拿到回调的返回值给result赋值
}
return results;
};
// 用来创建reduce、reduceRight的函数
// 最后reduce、reduceRight不仅仅支持数组,对象也支持
// 看着比较复杂,但其实在理解原生的reduce和reduceRight的基础上去看这个函数还是很清晰的
// Create a reducing function iterating left or right.
function createReduce(dir) {
// Optimized iterator function as using arguments.length
// in the main function will deoptimize the, see #1991.
function iterator(obj, iteratee, memo, keys, index, length) { // 真正执行迭代的地方
for (; index >= 0 && index < length; index += dir) {
var currentKey = keys ? keys[index] : index; // 如果keys存在则认为是obj形式的参数,所以读取keys中的属性值,否则类数组只需要读取索引index即可
memo = iteratee(memo, obj[currentKey], currentKey, obj); // 接着就是执行外部传入的回调了,并将结果赋值为memo,也就是我们最后要到的值
}
return memo;
}
return function(obj, iteratee, memo, context) {
iteratee = optimizeCb(iteratee, context, 4); // 首先绑定一下this作用域
var keys = !isArrayLike(obj) && _.keys(obj), // 如果不是类数组就读取其keys
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1; // 默认开始迭代的位置,从左边第一个开始还是右边第一个
// Determine the initial value if none is provided.
if (arguments.length < 3) { // 如果没有传入初始化值,则将第一个值(左边第一个或者右边第一个)作为初始值
memo = obj[keys ? keys[index] : index];
index += dir; // 从索引为1开始或者索引为length - 2开始迭代
}
return iterator(obj, iteratee, memo, keys, index, length); // 接着开始进入自定义的迭代函数,请往上看
};
}
// 模拟数组的reduce函数,将数组或者对象中的元素进行处理,最后得到一个值
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
_.reduce = _.foldl = _.inject = createReduce(1);
// // 模拟数组的reduceRight函数,将数组或者对象中的元素进行处理,最后得到一个值
// The right-associative version of reduce, also known as `foldr`.
_.reduceRight = _.foldr = createReduce(-1);
// 在obj中查找,返回第一个通过predicate迭代函数检测的元素值
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, predicate, context) {
var key;
if (isArrayLike(obj)) {
key = _.findIndex(obj, predicate, context);
} else {
key = _.findKey(obj, predicate, context);
}
if (key !== void 0 && key !== -1) return obj[key]; // 返回符合条件的value
};
// 模拟数组原生的filter方法
// 原理很简单就是返回一个新的数组,数组中的值是回调中经过帅选条件后的值
// Return all the elements that pass a truth test.
// Aliased as `select`.
_.filter = _.select = function(obj, predicate, context) {
var results = [];
predicate = cb(predicate, context);
_.each(obj, function(value, index, list) {
if (predicate(value, index, list)) results.push(value);
});
return results;
};
// 与filter功能相反
// 过滤出不满足的条件的元素
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
return _.filter(obj, _.negate(cb(predicate)), context);
};
// 判断obj中的所有元素是不是都满足一个条件
// 如果都满足就返回true
// 只要有一个不满足就返回false
// Determine whether all of the elements match a truth test.
// Aliased as `all`.
_.every = _.all = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = !isArrayLike(obj) && _.keys(obj), // 短路写法,非类数组则获取其keys
length = (keys || obj).length; // 获取obj的length
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index; // keys若能转化为"真" 则说明obj是对象类型
if (!predicate(obj[currentKey], currentKey, obj)) return false; // 只要有一个不满足就返回false,中断迭代
}
return true;
};
// 判断obj中是不是至少有一个元素满足一个条件
// 如果有至少一个满足就返回true
// 所有都不满足则返回false
// Determine if at least one element in the object matches a truth test.
// Aliased as `any`.
_.some = _.any = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
if (predicate(obj[currentKey], currentKey, obj)) return true; // 只要有一个满足条件就返回true
}
return false;
};
// 判断obj中是否含有item这个值
// Determine if the array or object contains a given item (using `===`).
// Aliased as `includes` and `include`.
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
if (!isArrayLike(obj)) obj = _.values(obj); // 如果obj不是类数组,就取对象的值拼成数组
if (typeof fromIndex != 'number' || guard) fromIndex = 0; // 默认fromIndex传入的不是数字就从0的位置开始查找,(guard后续分析到再看)
return _.indexOf(obj, item, fromIndex) >= 0; // 判断调用indexOf方法返回查找后的索引值是否大于0(indexOf调用后不存在返回-1)
};
// 在obj的每个元素上执行method方法,第三个参数及之后的每个参数都会传递给method方法
// method方法执行时,若没有特殊的处理,内部的this,是obj的每一项
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method); // 先判断一下method是不是一个函数
return _.map(obj, function(value) {
var func = isFunc ? method : value[method]; // 如果是函数就直接用,否则去value上读取
return func == null ? func : func.apply(value, args); // 最后执行对应的方法,并将结果返回
});
};
// 可以看做是map的简化版,用来获取数组中对象的某一属性值,最后返回该属性值的集合
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, _.property(key)); // _.property(key)函数,返回一个能够获取obj对象key属性的函数
};
// 遍历obj中的每一个值, 返回一个数组
// 数组中得到的值有一个特性,这些值都包含attrs的值
// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
_.where = function(obj, attrs) {
return _.filter(obj, _.matcher(attrs)); // matcher执行后返回的是一个函数
};
//遍历obj返回第一个包含attrs值的值
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matcher(attrs));
};
// 返回obj中的最大值。
// 如果传递iteratee参数,iteratee将作为obj中每个值的排序依据。
// 如果obj为空,将返回-Infinity,所以你可能需要事先用isEmpty检查 obj
// Return the maximum element (or element-based computation).
_.max = function(obj, iteratee, context) {
var result = -Infinity, lastComputed = -Infinity,
value, computed;
if (iteratee == null && obj != null) { // 针对没有传入iteratee的情况
obj = isArrayLike(obj) ? obj : _.values(obj); // 如果是类数组则直接进行下面的比较,否则拿到obj的values值得集合
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value > result) {
result = value;
}
}
} else { // 如果传了回调函数
iteratee = cb(iteratee, context); // 绑定this作用域
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list); // 获取计算后的值
if (computed > lastComputed || computed === -Infinity && result === -Infinity) { // 最后一个或运算是为了保证例如[{age: -Infinity}]类型,最后需要返回{age: -Infinity},而不是-Infinity
result = value;
lastComputed = computed;
}
});
}
return result;
};
// 返回obj中的小值。
// 如果传递iteratee参数,iteratee将作为obj中每个值的排序依据。
// 如果obj为空,将返回-Infinity,所以你可能需要事先用isEmpty检查 obj
// 分析方法和max几乎一样
// Return the minimum element (or element-based computation).
_.min = function(obj, iteratee, context) {
var result = Infinity, lastComputed = Infinity,
value, computed;
if (iteratee == null && obj != null) {
obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value < result) {
result = value;
}
}
} else {
iteratee = cb(iteratee, context);
_.each(obj, function(value, index, list) {
computed = iteratee(value, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// 返回一个随机打乱的obj副本(最后的返回值是一个数组)
// Shuffle a collection, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
_.shuffle = function(obj) {
var set = isArrayLike(obj) ? obj : _.values(obj); // 如果是一个类数组,就直接用obj,否则读取其values
var length = set.length;
var shuffled = Array(length);
for (var index = 0, rand; index < length; index++) {
rand = _.random(0, index); // 这里很关键每次都是随机生成[0, index)中的某个值,index为0的时候就只能为0了
if (rand !== index) shuffled[index] = shuffled[rand]; // 当随机出来的rand值和当前的index值不相等的时候,就把shuffled中index位置的值设置为rand位置的值
shuffled[rand] = set[index]; // 把shuffled中随机出来的rand位置的值设置为obj中index位置的值
}
return shuffled;
};
// 从obj中返回一个随机打乱的obj副本
// 如果没有传入n,则随机返回obj中的一个值
// 如果传入了先随机打乱,再返回前n个值
// Sample **n** random values from a collection.
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
_.sample = function(obj, n, guard) {
if (n == null || guard) {
if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)]; // 对应返回其中一个值的情况
}
return _.shuffle(obj).slice(0, Math.max(0, n)); // 对应返回n个随机值的情况
};
// 返回一个排序之后的obj的副本
// Sort the object's values by a criterion produced by an iteratee.
_.sortBy = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iteratee(value, index, list)
};
}).sort(function(left, right) { // 这里后续还需要自己研读
var a = left.criteria;
var b = right.criteria;
if (a !== b) {
if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index - right.index;
}), 'value');
};
// An internal function used for aggregate "group by" operations.
var group = function(behavior) {
return function(obj, iteratee, context) {
var result = {};
iteratee = cb(iteratee, context);
_.each(obj, function(value, index) {
var key = iteratee(value, index, obj);
behavior(result, value, key);
});
return result;
};
};
// 将一个集合分组为多个集合
// 通过 iterator 返回的结果进行分组
// 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组.
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = group(function(result, value, key) {
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
// 和groupBy非常像
// 不同的是如果知道obj的键是唯一的时候,可以用indexBy
// Indexes the object's values by a criterion, similar to `groupBy`, but for
// when you know that your index values will be unique.
_.indexBy = group(function(result, value, key) {
result[key] = value;
});
// 返回各组中的对象的数量的计数
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
_.countBy = group(function(result, value, key) {
if (_.has(result, key)) result[key]++; else result[key] = 1;
});
// 将obj转化为数组
// Safely create a real, live array from anything iterable.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj); // 如果是数组 拷贝一份返回
if (isArrayLike(obj)) return _.map(obj, _.identity); // 如果是类数组,使用map操作之后的数组返回
return _.values(obj); // 其他情况获取values返回
};
// 返回obj的长度
// 如果是类数组,直接返回length长度
// 否则先获取keys,在返回长度
// Return the number of elements in an object.
_.size = function(obj) {
if (obj == null) return 0;
return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
// 分区:拆分一个数组为一个含有两个数组元素的二维数组
// 其中第一个数组是通过了predicate判断的
// 第二个则是未通过的
// Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
_.partition = function(obj, predicate, context) {
predicate = cb(predicate, context);
var pass = [], fail = []; // 准备好了两个容器来装通过和未通过的元素
_.each(obj, function(value, key, obj) {
(predicate(value, key, obj) ? pass : fail).push(value); // 通过 ? 往pass里面添加元素 : 往fail里面添加
});
return [pass, fail];
};
// Array Functions
// ---------------
// 一个能够返回数组前n个元素的函数,默认是1
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0; // 如果array为空直接返回undefined
if (n == null || guard) return array[0]; //如果没传n,直接返回数组第一个值
return _.initial(array, array.length - n); // 结合initial,相当于(slice(0, array.length - (array.length - n))) => slice(0, n)
};
// 一个能够返回排除数组后面n个元素的数组的函数,默认是1
// (n == null || guard ? 1 : n) 的结果是1或者n,至于n有什么作用到后面我们会说!!!先埋一个坑
// Math.max其实是防止(array.length - x)减出来是个负数,如果是负数则取0
// Returns everything but the last entry of the array. Especially useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N.
_.initial = function(array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// 一个获取数组最后n个元素构成的数组,默认是最后一位
// Get the last element of an array. Passing **n** will return the last N
// values in the array.
_.last = function(array, n, guard) {
if (array == null) return void 0;
if (n == null || guard) return array[array.length - 1];
return _.rest(array, Math.max(0, array.length - n));
};
// 一个能够返回数组第n个元素之后的元素构成的数组,注意不包含第n个元素
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
// Especially useful on the arguments object. Passing an **n** will return
// the rest N values in the array.
_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, n == null || guard ? 1 : n);
};
// 返回筛选掉数组的值为false副本
// 这个时候_.identity的作用体现了
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, _.identity);
};
// 将一个嵌套多层的数组转化为只有一层的数组,如果你传递 shallow参数,数组将进行一维的嵌套
// Internal implementation of a recursive `flatten` function.
var flatten = function(input, shallow, strict, startIndex) {
var output = [], idx = 0;
for (var i = startIndex || 0, length = getLength(input); i < length; i++) { // 对数组进行遍历
var value = input[i];
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { // 如果是数组或者arguments对象则进行进一步判断
//flatten current level of array or arguments object
if (!shallow) value = flatten(value, shallow, strict); // shallow决定是否进行深度铺平,如果为false,则进行递归,一层层铺平
var j = 0, len = value.length; // 否则直接将数组的值,添加在output上,而不会再理会value中是否有值为数组
output.length += len;
while (j < len) {
output[idx++] = value[j++];
}
} else if (!strict) { // 否则直接给output赋值,并且给idx索引增1
output[idx++] = value;
}
}
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
_.flatten = function(array, shallow) {
return flatten(array, shallow, false);
};
// 返回一个删除所有values值后的 array副本
// 作用何difference类似,只不过difference要求只穿函数
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// 返回 array去重后的副本, 使用 === 做相等测试.
// 如果您确定 array 已经排序, 那么给 isSorted 参数传递 true值, 此函数将运行的更快的算法.
// 如果要处理对象元素, 传递 iteratee函数来获取要对比的属性.
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}
if (iteratee != null) iteratee = cb(iteratee, context);
var result = [];
var seen = []; // 这里之前做一些参数处理
for (var i = 0, length = getLength(array); i < length; i++) {
var value = array[i],
computed = iteratee ? iteratee(value, i, array) : value;
if (isSorted) { // 因为已经确定了是排序过了 所以只需要和上一个数比较就好
if (!i || seen !== computed) result.push(value);
seen = computed;
} else if (iteratee) { // 如果是回调函数的形式
if (!_.contains(seen, computed)) { // seen用来缓存已经比较过的值
seen.push(computed);
result.push(value); // 缓存seen中没有则将数组中的值添加进去,从而达到去重
}
} else if (!_.contains(result, value)) { // 这种场景最简单 _.uniq([1, 2, 1, 3, 1, 4]) => [1, 2, 3, 4]
result.push(value);
}
}
return result;
};
// 返回传入的数组的并集
// 原理很简单,先将所有传入的数组铺平得到一维的数组
// 再进行去重,便得到并集
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(flatten(arguments, true, true));
};
// 返回传入 arrays(数组)交集
// 既然是交集,肯定返回值在每个传入的数组中都存在
// 其实我想是不是先找出传入的数组中length最短的那个,在进行后续的比较整个计算时间会短一点?
// Produce an array that contains every item shared between all the
// passed-in arrays.
_.intersection = function(array) {
var result = [];
var argsLength = arguments.length;
for (var i = 0, length = getLength(array); i < length; i++) {
var item = array[i];
if (_.contains(result, item)) continue;
for (var j = 1; j < argsLength; j++) {
if (!_.contains(arguments[j], item)) break;
}
if (j === argsLength) result.push(item);
}
return result;
};
// 返回array中,其他的数组中不存在的值
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = flatten(arguments, true, true, 1); // 先将第一个参数之后的输入值铺平为一维数组
return _.filter(array, function(value){
return !_.contains(rest, value); // 帅选array的值,不在铺平后的rest中
});
};
// 和unzip的功能是一致的,区别在于使用方式
// _.unzip([[], [], []])
// _.zip([], [], [])
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
return _.unzip(arguments);
};
// 将每个arrays中相应位置的值合并在一起,类似下面的用法
// [['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]]
// [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
// Complement of _.zip. Unzip accepts an array of arrays and groups
// each array's elements on shared indices
_.unzip = function(array) {
var length = array && _.max(array, getLength).length || 0;
var result = Array(length);
for (var index = 0; index < length; index++) {
result[index] = _.pluck(array, index);
}
return result;
};
// 将数组转化为对象
// 1. ([[], []])
// 2. ([], [])
// Converts lists into objects. Pass either a single array of `[key, value]`
// pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values.
_.object = function(list, values) {
var result = {};
for (var i = 0, length = getLength(list); i < length; i++) {
if (values) { // 对应情况2的调用方式
result[list[i]] = values[i];
} else { // 对应情况1的调用方式
result[list[i][0]] = list[i][1];
}
}
return result;
};
// 创建findIndex和findLastIndex的函数封装
// findIndex从0开始查找
// findLastIndex从数组的length - 1开始查找
// Generator function to create the findIndex and findLastIndex functions
function createPredicateIndexFinder(dir) {
return function(array, predicate, context) {
predicate = cb(predicate, context);
var length = getLength(array);
var index = dir > 0 ? 0 : length - 1;
for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
}
return -1;
};
}
// Returns the first index on an array-like that passes a predicate test
_.findIndex = createPredicateIndexFinder(1);
_.findLastIndex = createPredicateIndexFinder(-1);
// 使用二分查找法找寻指定值在array中的位置
// 要搜索的obj不一定在array中,返回的index是值比较近的较小的index
// Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iteratee, context) {
iteratee = cb(iteratee, context, 1);
var value = iteratee(obj);
var low = 0, high = getLength(array);
while (low < high) {
var mid = Math.floor((low + high) / 2);
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
};
// 创建indexOf和lastIndexOf的函数封装
// Generator function to create the indexOf and lastIndexOf functions
function createIndexFinder(dir, predicateFind, sortedIndex) {
return function(array, item, idx) {
var i = 0, length = getLength(array);
if (typeof idx == 'number') {
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
} else if (sortedIndex && idx && length) {
idx = sortedIndex(array, item);
return array[idx] === item ? idx : -1;
}
if (item !== item) {
idx = predicateFind(slice.call(array, i, length), _.isNaN);
return idx >= 0 ? idx + i : -1;
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
};
}
// Return the position of the first occurrence of an item in an array,
// or -1 if the item is not included in the array.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
// 一个用来创建整数灵活编号的函数
// _.range(10) [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// _.range(0, 10, 3) [ 0, 3, 6, 9 ]
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (stop == null) {
stop = start || 0;
start = 0;
}
step = step || 1; // 做一些参数的处理
var length = Math.max(Math.ceil((stop - start) / step), 0); // Math.ceil处理针对(10/3)除不尽的情况,并且需要向上取整
var range = Array(length);
for (var idx = 0; idx < length; idx++, start += step) {
range[idx] = start;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Determines whether to execute a function as a constructor
// or a normal function with the provided arguments
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); // 如果调用方式不是new func的形式就直接调用sourceFunc,并且给到对应的参数即可
var self = baseCreate(sourceFunc.prototype); // 处理new调用的形式
var result = sourceFunc.apply(self, args);
if (_.isObject(result)) return result;
return self;
};
// 绑定函数 func 到对象 context 上,
// 也就是无论何时调用函数, 函数里的 this 都指向这个 context.
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); // 如果原生支持bind函数,就用原生的,并将对应的参数传进去
if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); // 如果传入的func不是一个函数类型 就抛出异常
var args = slice.call(arguments, 2); // 把第三个参数以后的值存起来,接下来请看executeBound
var bound = function() {
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
};
return bound;
};
// 局部应用一个函数填充在任意个数的arguments
// 不改变其动态this值,和bind方法很相近
// 可以在partial调用的时传进_来占位,进而用在传进的回调函数实际参数进行替换
// Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context. _ acts
// as a placeholder, allowing any combination of arguments to be pre-filled.
_.partial = function(func) {
var boundArgs = slice.call(arguments, 1); // 获取除了传进回调函数之外的其他参数
var bound = function() {
var position = 0, length = boundArgs.length;
var args = Array(length); // 先创建一个和boundArgs长度等长的空数组
for (var i = 0; i < length; i++) { // 处理占位元素_
args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; // 如果发现boundArgs中有_的占位元素,就依次用arguments中的元素进行替换boundArgs
}
while (position < arguments.length) args.push(arguments[position++]); // 把auguments中的其他元素添加到boundArgs中
return executeBound(func, bound, this, this, args);
};
return bound;
};
// 把methodNames参数指定的一些方法绑定到object上,这些方法就会在对象的上下文环境中执行
// Bind a number of an object's methods to that object. Remaining arguments
// are the method names to be bound. Useful for ensuring that all callbacks
// defined on an object belong to it.
_.bindAll = function(obj) {
var i, length = arguments.length, key;
if (length <= 1) throw new Error('bindAll must be passed function names');
for (i = 1; i < length; i++) { // 从第一个实参开始处理,这些便是需要绑定this作用域到obj的函数
key = arguments[i];
obj[key] = _.bind(obj[key], obj); // 调用内部的bind方法进行this绑定
}
return obj;
};
// memoize函数可以缓存某函数处理之后的结果,对于耗时较长的操作很有帮助
// 如果传递了 hashFunction 参数,就用 hashFunction 的返回值作为key存储函数的计算结果
// hashFunction 默认使用function的第一个参数作为key
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key); // 注意hasher,如果传了hasher,就用hasher()执行的结果作为缓存func()执行的结果的key
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // 如果没有在cache中查找到对应的key就去计算一次,并缓存下来
return cache[address];
};
memoize.cache = {};
return memoize; // 返回一个具有cache静态属性的函数
};
// 类似setTimeout,等待wait秒之后调用func。
// 如果传递可选的参数arguments,当函数function执行时, arguments 会作为参数传入
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2); // 读取第三个参数开始的其他参数
return setTimeout(function(){
return func.apply(null, args); // 执行func并将参数传入
}, wait);
};
// 延迟调用function
// 这里使用了_.partial, _.delay,并且有一个_作为函数占位,等待_.defer实际传入
// 类似使用延时为1的setTimeout方法。对于执行开销大的计算和无阻塞UI线程的HTML渲染时候非常有用
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = _.partial(_.delay, _, 1);
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
// 函数节流,非常重要。
_.throttle = function(func, wait, options) {
var context, args, result;
// 存储定时器
var timeout = null;
// 记录上一次返回的节流函数执行时间
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = _.now();
// 默认是会尽快执行传入的函数,但是如果传入leading为false则反之