forked from yoyoyohamapi/underscore
-
Notifications
You must be signed in to change notification settings - Fork 0
/
underscore.analysis.js
2947 lines (2738 loc) · 105 KB
/
underscore.analysis.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
// Underscore.js 1.8.3
// http://underscorejs.org
// (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function () {
// Baseline setup
// 基础定义
// 由于underscore即支持浏览器端运行,又支持服务端运行,所以,需要判定根节点是'window'对象还是'global'对象
// 值得注意的是, 如果当前系统中存在了self对象,且满足一定条件,那么他表示的就是浏览器端的根对象(全局对象)
// 这个可以通过在chrome控制台敲击self证实
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this;
// 如果之前系统存在了_, 那么保存, 而不是粗暴的替换
var previousUnderscore = root._;
// 保存常用原型的引用, 避免对象属性的查找开销
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
// 保存常用方法的引用, 避免属性查找的性能开销
var push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// are declared here.
// 保存一些ES5常用的原生方法引用
var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeCreate = Object.create;
// Ctor: 亦即constructor的缩写,这个空的构造函数将在之后广泛用于对象创建
var Ctor = function () {
};
// Create a safe reference to the Underscore object for use below.
// 创建一个underscore的对象引用, 保证不重复创建
var _ = function (obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// 将underscore对象挂载到合适的位置
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// Current version.
_.VERSION = '1.8.3';
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
/** 优化回调(特指函数中传入的回调)
*
* @param func 待优化回调函数
* @param context 执行上下文
* @param argCount 参数个数
* @returns {*}
*/
var optimizeCb = function (func, context, argCount) {
// void 0 会返回纯正的undefined,这样做避免undefined已经被污染带来的判定失效
// 一定要保证回调的执行上下文存在
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
// 回调参数为1时, 即迭代过程中,我们只需要值
case 1:
return function (value) {
return func.call(context, value);
};
// 2个参数的情况几乎不存在, 所以省却判断
// 3个参数(值,索引,被迭代集合对象)
case 3:
return function (value, index, collection) {
return func.call(context, value, index, collection);
};
// 4个参数(累加器(比如reducer需要的), 值, 索引, 被迭代集合对象)
case 4:
return function (accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function () {
return func.apply(context, arguments);
};
};
var builtinIteratee;
// An 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.
/**
* 为迭代过程中的元素生产一个回调函数, 该回调函数能够应用到集合中的每个元素
* @param value
* @param context
* @param argCount
* @example
* 在_.map函数中:
* _.map = _.collect = function (obj, iteratee, context) {
* iteratee = cb(iteratee, context);
* // 同样,根据obj是对象还是数组分别考虑
* var keys = !isArrayLike(obj) && _.keys(obj),
* length = (keys || obj).length,
* results = Array(length); // 定长初始化数组
* for (var index = 0; index < length; index++) {
* var currentKey = keys ? keys[index] : index;
* results[index] = iteratee(obj[currentKey], currentKey, obj);
* }
* return results;
* };
* @returns {*}
*/
var cb = function (value, context, argCount) {
// 是否用默认的迭代器
if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
// 如果value不存在, 则回调只是一个返回自身的函数
if (value == null) return _.identity;
// 如果value是一个回调函数, 则需要优化回调
// ex.
// var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
// 如果value是个对象, 则返回一个matcher进行对象匹配
// ex.
// _.find(obj, {x: 2})
if (_.isObject(value)) return _.matcher(value);
// 否则, 如果value只是一个字面量, 则把value看做是属性名称, 返回一个对应的属性获得函数
return _.property(value);
};
// External wrapper for our callback generator. Users may customize
// `_.iteratee` if they want additional predicate/iteratee shorthand styles.
// This abstraction hides the internal-only argCount argument.
/**
* 内置的迭代回调
* @param value
* @param context
*/
_.iteratee = builtinIteratee = function (value, context) {
return cb(value, context, Infinity);
};
/**
* 类ES6 rest参数的实现,使某个函数具备支持rest参数的能力
* @param func 需要rest参数的函数
* @param startIndex 从哪里开始标识rest参数, 如果不传递, 默认最后一个参数为rest参数
* @returns {Function} 返回一个具有rest参数的函数
*/
var restArgs = function (func, startIndex) {
// rest参数从哪里开始,如果没有,则默认视函数最后一个参数为rest参数
// 注意, 函数对象的length属性, 揭示了函数的参数个数
/*
ex: function add(a,b) {return a+b;}
console.log(add.length;) // 2
*/r
startIndex = startIndex == null ? func.length - 1 : +startIndex;
// 返回一个支持rest参数的函数
return function () {
// 校正参数, 以免出现负值情况
var length = Math.max(arguments.length - startIndex, 0);
// 为rest参数开辟数组存放
var rest = Array(length);
// 假设参数从2个开始: func(a,b,*rest)
// 调用: func(1,2,3,4,5); 实际的调用是:func.call(this, 1,2, [3,4,5]);
for (var index = 0; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
// 根据rest参数不同, 分情况调用函数, 需要注意的是, rest参数总是最后一个参数, 否则会有歧义
switch (startIndex) {
case 0:
// call的参数一个个传
return func.call(this, rest);
case 1:
return func.call(this, arguments[0], rest);
case 2:
return func.call(this, arguments[0], arguments[1], rest);
}
// 如果不是上面三种情况, 而是更通用的(应该是作者写着写着发现这个switch case可能越写越长, 就用了apply)
var args = Array(startIndex + 1);
// 先拿到前面参数
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
// 拼接上剩余参数
args[startIndex] = rest;
return func.apply(this, args);
};
};
/**
* 创建一个对象,该对象继承自prototype
* 并且保证该对象在其原型上挂载属性不会影响所继承的prototype
* @param {object} prototype
*/
var baseCreate = function (prototype) {
if (!_.isObject(prototype)) return {};
// 如果存在原生的创建方法(Object.create),则用原生的进行创建
if (nativeCreate) return nativeCreate(prototype);
// 利用Ctor这个空函数,临时设置对象原型
Ctor.prototype = prototype;
// 创建对象,result.__proto__ === prototype
var result = new Ctor;
// 还原Ctor原型
Ctor.prototype = null;
return result;
};
// property('name')(Tiger), 获得对象属性
// 这体现了函数式编程的灵活
// var nameProperty = property('name'), 就获得了一个专门用于采集对象name属性的函数
var property = function (key) {
return function (obj) {
return obj == null ? void 0 : obj[key];
};
};
// 最大数组长度, 避免IOS 8出现的bug
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
// 设置一个常用方法,获得对象长度
// 如果在OO的开发方式中, 我们获得对象长度是通过对象属性: obj.length
// 而在FP中, 对象也只是数据的一个表现形式, 他只是被函数所加工: getLength(obj)
var getLength = property('length');
/**
* 判断集合是否是近似数组的, 方便集合迭代过程中的循环判定
* @param collection 集合对象
*/
var isArrayLike = function (collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
// Collection Functions
// 集合部分的函数
// 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方法将ES5的forEach换为了函数式表达
* @param obj 待迭代集合
* @param iteratee 迭代过程中每个被迭代元素的回调函数
* @param context 上下文
* @example
* // 数组迭代
* _.each([1, 2, 3], alert);
* // 对象迭代
* _.each({one: 1, two: 2, three: 3}, alert);
*/
_.each = _.forEach = function (obj, iteratee, context) {
// 首先要优化回调过程
iteratee = optimizeCb(iteratee, context);
var i, length;
// 区分数组和对象的迭代过程
if (isArrayLike(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++) {
// 对象的迭代回调传入三个参数(迭代值, 迭代的key, 迭代对象)
iteratee(obj[keys[i]], keys[i], obj);
}
}
// 返回对象自身, 以便进行链式构造
return obj;
};
// Return the results of applying the iteratee to each element.
/**
* map,collect函数将ES5的数组的map方法换为了函数是表达
* @param obj 对象
* @param iteratee 迭代回调
* @param context 执行上下文
* @example
* _.map([1, 2, 3], function(num){ return num * 3; });
*/
_.map = _.collect = function (obj, iteratee, context) {
iteratee = cb(iteratee, context);
// 同样,根据obj是对象还是数组分别考虑
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
results = Array(length); // 定长初始化数组
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
};
// Create a reducing function iterating left or right.
/**
* reduce函数的工厂函数, 用于生成一个reducer, 通过参数决定reduce的方向
* @param dir 方向 left or right
* @returns {function}
*/
var createReduce = function (dir) {
// Wrap code that reassigns argument variables in a separate function than
// the one that accesses `arguments.length` to avoid a perf hit. (#1991)
var reducer = function (obj, iteratee, memo, initial) {
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
// 如果reduce没有初始化memo, 则默认为首个元素(从左开始则为第一个元素,从右则为最后一个元素)
if (!initial) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
for (; index >= 0 && index < length; index += dir) {
var currentKey = keys ? keys[index] : index;
// 执行reduce回调,刷新当前值
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
return function (obj, iteratee, memo, context) {
// 如果参数正常,则代表已经初始化了memo
var initial = arguments.length >= 3;
// 所有的传入回调都要通过optimizeCb进行优化,
// reducer因为引入了累加器,所以优化函数的第三个参数传入了4,
// 这样, 新的迭代回调第一个参数就是当前的累加结果:
// _.reduce([1,2,3],function(prev,current){})
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
};
};
// 分别定义向左及向右的reduce函数
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
/**
* 向左reduce
* @alias _.foldl
* @alias _.inject
* @example
* var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
*/
_.reduce = _.foldl = _.inject = createReduce(1);
// The right-associative version of reduce, also known as `foldr`.
/**
* 向右reducer
* @alias _.foldr
* @example
* // 从右边开始, 扁平化一个序列
* var list = [[0, 1], [2, 3], [4, 5]];
* var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
*/
_.reduceRight = _.foldr = createReduce(-1);
// Return the first value which passes a truth test. Aliased as `detect`.
/**
* 根据真值检测函数, 在集合内搜索
* @param obj 待查询对象,
* @param predicate 真值检测函数
* @param context 执行上下文
* @alias _.detect
* @example
* // 获得集合中的偶数
* var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
*/
_.find = _.detect = function (obj, predicate, context) {
// 如果是对象,则根据key查找;如果是数组,则根据下标查找
var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
var key = keyFinder(obj, predicate, context);
if (key !== void 0 && key !== -1) return obj[key];
};
/**
* 根据真值检测函数, 过滤对象
* 如果真值检测通过, 元素被保留
* @param obj 待过滤对象
* @param predicate 真值检测函数
* @param context 执行上下文
* @alias _.select
* @example
* // 保留偶数
* var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
*/
_.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的反运算,
* 如果真值检测通过, 元素被丢弃
* @param obj 待过滤对象
* @param predicate 真值检测函数
* @param context 执行上下文
* @example
* // 保留奇数
* var odds = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
*/
_.reject = function (obj, predicate, context) {
return _.filter(obj, _.negate(cb(predicate)), context);
};
/**
* 迭代对象里面的每个元素, 只有每个元素都通过真值检测, 才返回true
* @param obj 待迭代对象
* @param predicate 真值检测函数
* @param context 执行上下文
* @alias _.all
* @example
* _.every([true, 1, null, 'yes'], _.identity);
*/
_.every = _.all = 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;
// 一旦有元素没有通过真值检测, 立即返回false
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
};
/**
* 迭代一个对象所有元素, 如果任意一个元素通过真值检测, 则返回true
* @param obj 迭代对象
* @param predicate 真值检测函数
* @param context 执行上下文
* @alias _.any
* @example
* _.some([null, 0, 'yes', false]);
*/
_.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;
}
return false;
};
/**
* 检测一个数组或者对象是否包含一个指定的元素
* @param obj 待检测对象
* @param item 指定元素
* @param fromIndex 从哪个位置开始查找
* @param guard ?
* @alias _.includes
* @alias _.include
* @example
*_.contains([1, 2, 3], 3);
* // => true
*/
_.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
// 如果不是数组, 则根据值查找
if (!isArrayLike(obj)) obj = _.values(obj);
if (typeof fromIndex != 'number' || guard) fromIndex = 0;
return _.indexOf(obj, item, fromIndex) >= 0;
};
// Invoke a method (with arguments) on every item in a collection.
/**
* 迭代集合, 调用每个元素的属性方法method
* @param obj 迭代对象
* @param method 待调用方法
* @param args 调用所需参数,这些参数每次都会传入method中
* @example
* _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
* // => [[1, 5, 7], [1, 2, 3]]
*/
// 首先通过restArgs包裹invoke, 使得_.invoke支持rest参数
_.invoke = restArgs(function (obj, method, args) {
// 通过闭包避免每次重复调用_.isFunction(method)
var isFunc = _.isFunction(method);
return _.map(obj, function (value) {
var func = isFunc ? method : value[method];
// 如果对象上不存在方法, 则返回null
return func == null ? func : func.apply(value, args);
});
});
/**
* 获得对象集合中对应属性的对应值(摘出来)
* @param obj 传入集合
* @param key 需要摘出来的属性(如果传入的集合中的各个元素是数组, 则key代表下标)
* @example
* var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
* _.pluck(stooges, 'name');
* // => ["moe", "larry", "curly"]
* _.pluck([[1,2,3],[4,5,7],[8,9,10]], 2);
* // => [3,7,10]
*/
_.pluck = function (obj, key) {
// 迭代集合, 每个迭代元素返回其对应属性的对应值
return _.map(obj, _.property(key));
};
/**
* 类似sql中where查询条件, 对于一个对象集合, 返回满足where条件的对象
* @param obj 待迭代参数
* @param attrs where条件对象
* @example
* _.where(listOfPlays, {author: "Shakespeare", year: 1611});
* // => [{title: "Cymbeline", author: "Shakespeare", year: 1611},
* // {title: "The Tempest", author: "Shakespeare", year: 1611}]
*/
_.where = function (obj, attrs) {
return _.filter(obj, _.matcher(attrs));
};
/**
* 迭代对象集合, 获得第一个满足条件的对象
* @param obj 对象集合
* @param attrs where条件对象
* _.findWhere(publicServicePulitzers, {newsroom: "The New York Times"});
// => {year: 1918, newsroom: "The New York Times",
// reason: "For its public service in publishing in full so many official reports,
// documents and speeches by European statesmen relating to the progress and
// conduct of the war."}
*/
_.findWhere = function (obj, attrs) {
return _.find(obj, _.matcher(attrs));
};
/**
* 获得集合中的最大值
* @param obj 对象集合
* @param iteratee 如果传递了iteratee, 则以iteratee作为最大值的计算依据
* @param context 执行上下文
* @example
* var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
* _.max(stooges, function(stooge){ return stooge.age; });
* // => {name: 'curly', age: 60};
* @returns {number}
*/
_.max = function (obj, iteratee, context) {
// 默认返回-Infinity
var result = -Infinity, lastComputed = -Infinity,
value, computed;
if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
// 如果没有传递iteratee, 则按值进行比较
obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value != null && value > result) {
result = value;
}
}
} else {
// 否则, 以iteratee为最大值依据, 每次传入当前迭代值给iteratee, 算出最大值
iteratee = cb(iteratee, context);
_.each(obj, function (v, index, list) {
computed = iteratee(v, index, list);
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = v;
lastComputed = computed;
}
});
}
return result;
};
/**
* 获得集合中的最小值
* @param obj 对象集合
* @param iteratee 最小值依据
* @param context 执行上下文
* @returns {Number} 默认返回Infinity
*/
_.min = function (obj, iteratee, context) {
var result = Infinity, lastComputed = Infinity,
value, computed;
if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
obj = isArrayLike(obj) ? obj : _.values(obj);
for (var i = 0, length = obj.length; i < length; i++) {
value = obj[i];
if (value != null && value < result) {
result = value;
}
}
} else {
iteratee = cb(iteratee, context);
_.each(obj, function (v, index, list) {
computed = iteratee(v, index, list);
if (computed < lastComputed || computed === Infinity && result === Infinity) {
result = v;
lastComputed = computed;
}
});
}
return result;
};
/**
* 返回一个随机乱序的集合副本, 显然, 该函数不是一个纯函数
* @param obj 集合对象
* @example
* _.shuffle([1, 2, 3, 4, 5, 6]);
* // => [4, 1, 6, 3, 5, 2]
*/
_.shuffle = function (obj) {
return _.sample(obj, Infinity);
};
// Sample **n** random values from a collection using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
// If **n** is not specified, returns a single random element.
// The internal `guard` argument allows it to work with `map`.
/**
* 从集合中产生一个随机样本。
* 采用了[Fisher-Yates shuffle算法(洗牌算法)](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle)
* 洗牌算法用来对序列随机排序
* @param obj 对象
* @param n 需要返回的随机元素个数, 否则将返回一个单一的随机项。
* @param guard
* @example
* _.sample([1, 2, 3, 4, 5, 6], 3);
// => [1, 6, 2]
_.sample([1, 2, 3, 4, 5, 6]);
// => 4
* @returns {*}
*/
_.sample = function (obj, n, guard) {
if (n == null || guard) {
if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
// 如果是对象,乱序key的排列
var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
var length = getLength(sample);
// 校正参数n,使得0<=n<length
n = Math.max(Math.min(n, length), 0);
var last = length - 1;
// 开始洗牌算法, 洗出来n个就停止了
for (var index = 0; index < n; index++) {
// 从[index, last]获得一个随机位置
var rand = _.random(index, last);
// 当前值
var temp = sample[index];
// 交换当前值与随机位置上的值
sample[index] = sample[rand];
sample[rand] = temp;
// 此时,排序后的第一个数据sample[0]已经确定
}
return sample.slice(0, n);
};
// Sort the object's values by a criterion produced by an iteratee.
/**
* 类似Sql中的sort关键字, 根据某个key进行排序
* @param obj 集合
* @param iteratee 排序依据
* @param context 执行上下文
* @example
* _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
* // => [5, 4, 6, 3, 1, 2]
*/
_.sortBy = function (obj, iteratee, context) {
var index = 0;
iteratee = cb(iteratee, context);
// 先通过map生成新的对象集合,该对象提供了通过iteratee计算后的值, 方便排序
// [{value:1,index:0,criteria: sin(1)}, ...]
// 再排序.sort
// 最后再通过pluck把值摘出来
return _.pluck(_.map(obj, function (value, key, list) {
return {
value: value,
index: index++,
criteria: iteratee(value, key, 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');
};
/**
* group函数将在涉及到分组的地方使用
* @param behavior 获得组别之后的行为
* @param partition 是否进行划分
* @returns {Function}
* @example
*/
var group = function (behavior, partition) {
// 返回一个分组函数
return function (obj, iteratee, context) {
// 分组结果初始化
// 如果是进行划分(二分)的话, 则结果分为两个组
var result = partition ? [[], []] : {};
iteratee = cb(iteratee, context);
_.each(obj, function (value, index) {
// 计算得到分组组别key, 如果是划分的话, key就是一个bool
var key = iteratee(value, index, obj);
// 获得组别后, 执行定义的行为
behavior(result, value, key);
});
return result;
};
};
/**
* 类似sql中的group by关键字
* @param obj 待分组集合
* @param iteratee 分组依据, 如果是函数, 则需要根据函数计算组别; 如果是字符串,
* @param context 执行上下文
* @example
* _.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
* // => {1: [1.3], 2: [2.1, 2.4]}
* _.groupBy(['one', 'two', 'three'], 'length');
* // => {3: ["one", "two"], 5: ["three"]}
* @type {Function}
*/
_.groupBy = group(function (result, value, key) {
// groupBy的分组行为为:
// 如果分组结果中存在了key(存在了分组), 满足该分组条件的value会追加到该分组中
// 否则新创建一个分组, 并将value放入当中
if (_.has(result, key)) result[key].push(value); else result[key] = [value];
});
/**
* 根据XX索引集合对象
* @param list 集合对象
* @param iteratee 索引依据
* @example
* var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
* _.indexBy(stooges, 'age');
* => {
"40": {name: 'moe', age: 40},
"50": {name: 'larry', age: 50},
"60": {name: 'curly', age: 60}
}
* @type {Function}
*/
_.indexBy = group(function (result, value, key) {
// 获得分组后的行为, 每个分组对应一个对象
result[key] = value;
});
/**
* 计算各分组中的元素数
* @param list 集合对象
* @param iteratee 分组依据
* @type {Function}
* @example:
* _.countBy([1, 2, 3, 4, 5], function(num) {
return num % 2 == 0 ? 'even': 'odd';
});
// => {odd: 3, even: 2}
*/
_.countBy = group(function (result, value, key) {
// 获得分组后的行为, 分组保存组内元素个数
if (_.has(result, key)) result[key]++; else result[key] = 1;
});
// 重要正则
// [^\ud800-\udfff]: 表示不包含代理对代码点的所有字符
// [\ud800-\udbff][\udc00-\udfff]: 表示合法的代理对的所有字符
// [\ud800-\udfff]: 表示代理对的代码点(本身不是合法的Unicode字符)
// 参考文献:
// [字符编码的那些事](http://licstar.net/archives/tag/utf-8)
// [知乎关于underscore这个正则的提问](https://www.zhihu.com/question/38324041)
var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
// Safely create a real, live array from anything iterable.
/**
* 将对象转换为数组
* @param obj
* @returns {*}
*/
_.toArray = function (obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
// 尤其注意的是string的转换
if (_.isString(obj)) {
// Keep surrogate pair characters together
// match每一个字符到数组中, 通过reStrSymbol保证了:
// 1. 不含代理对代码点的所有字符
// 2. 合法代理对的所有字符
// 3. 代理对代码点的字符
// 都能match的数组
return obj.match(reStrSymbol);
}
if (isArrayLike(obj)) return _.map(obj, _.identity);
return _.values(obj);
};
/**
* 返回集合长度, 如果是对象, 返回key的数目, 如果是数组, 返回数组长度
* @param obj 集合
* @returns {number}
*/
_.size = function (obj) {
if (obj == null) return 0;
return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
/**
* 划分函数, 讲一个集合对象划分为两个数组, 划分依据来源于真值预测
* @param array 真值预测
* @param predicate 真值预测函数
* @example
* _.partition([0, 1, 2, 3, 4, 5], isOdd);
* // => [[1, 3, 5], [0, 2, 4]]
* @type {Function}
*/
_.partition = group(function (result, value, pass) {
// 分组后的行为,
result[pass ? 0 : 1].push(value);
}, true);
// Array Functions
// ---------------
/**
* 获得数组的第一个元素,
* @param array 数组
* @param n 如果传递了参数n, 则返回前n个元素
* @alias _.head
* @alias _.take
* @example
* _.first([5, 4, 3, 2, 1]);
* => 5
*/
_.first = _.head = _.take = function (array, n, guard) {
if (array == null || array.length < 1) return void 0;
if (n == null || guard) return array[0];
return _.initial(array, array.length - n);
};
/**
* 返回除了最后一个元素的数组所有元素
* @param array
* @param n 如果传递了参数n, 则返回除了最后n元素的以外的所有数组元素
* @param guard
* @example
* _.initial([5, 4, 3, 2, 1]);
* => [5, 4, 3, 2]
*/
_.initial = function (array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
/**
* 返回数组最后的元素
* @param array 数组
* @param n 如果传递了参数n, 则返回最后n个元素
* @param guard
* @example
* _.last([5, 4, 3, 2, 1]);
* => 1
*/
_.last = function (array, n, guard) {
if (array == null || array.length < 1) return void 0;
if (n == null || guard) return array[array.length - 1];
return _.rest(array, Math.max(0, array.length - n));
};
/**
* 返回数组除了第一个元素外的所有元素
* @param array 数组
* @param n 如果传递了参数n, 返回前n个元素以外的所有元素
* @param guard
* @alias _.tail
* @alias _.drop
* @example
* _.rest([5, 4, 3, 2, 1]);
* => [4, 3, 2, 1]
*/
_.rest = _.tail = _.drop = function (array, n, guard) {
return slice.call(array, n == null || guard ? 1 : n);
};
/**
* 返回一个除去所有false值的 array副本。 在javascript中, false, null, 0, "", undefined 和 NaN 都是false值.
* @param array
* @example
* _.compact([0, 1, false, 2, '', 3]);
* => [1, 2, 3]
*/
_.compact = function (array) {
// Boolean(1) => true, Boolean(0) => false
return _.filter(array, Boolean);
};
/**
* 递归展平集合
* @param input 输入
* @param shallow 如果shallow为true,数组将只减少一维的嵌套。(浅展平)
* @param strict 严格模式下, input必须为数组
* @param output 输出数组
* @example
* @returns {*|Array}
*/
var flatten = function (input, shallow, strict, output) {
output = output || [];
var idx = output.length; // 输出数组的下标
for (var i = 0, length = getLength(input); i < length; i++) {
// 获得元素值
var value = input[i];
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
// Flatten current level of array or arguments object.
if (shallow) {
// 如果不是深度展开
// 只是从value(数组)中不断抽出元素赋值output中
// 例如, value=[1,[3],[4,5]]
// output = [....1,3,[4,5]....]
var j = 0, len = value.length;
while (j < len) output[idx++] = value[j++];
} else {
// 否则需要递归展开
flatten(value, shallow, strict, output);
// 刷新下标
idx = output.length;
}
} else if (!strict) {
// 如果不是严格模式, 则value可以不是数组
output[idx++] = value;
}
}
return output;
};
/**
* 展平一个数组
* @param array 待展开数组
* @param shallow 是否只是浅展平,如果shallow为true,数组将只减少一维的嵌套
* @example
* // 深度展平(所有元素不再被数组包裹)
* _.flatten([1, [2], [3, [[4]]]]);
* // => [1, 2, 3, 4];
* // 浅展平(只展开一层)
* _.flatten([1, [2], [3, [[4]]]], true);
* // => [1, 2, 3, [[4]]];
*
* @returns {*|Array}
*/
_.flatten = function (array, shallow) {
return flatten(array, shallow, false);
};
// Return a version of the array that does not contain the specified value(s).
/**
* 返回一个排除掉values的数组
* @param array 数组
* @param values 支持rest参数,
* @example
* _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
* => [2, 3, 4]
* @type {Function}
*/
_.without = restArgs(function (array, otherArrays) {
return _.difference(array, otherArrays);
});
/**
* 返回array去重后的副本
* @param array 待去重数组
* @param isSorted 数组是否排序, 如果该参数设置为true, 那么可以加快该函数的执行过程
* @param iteratee 比较函数, 默认是 ===
* @param context 执行上下文
* @alias _.unique
* @example
* _.uniq([1, 2, 1, 3, 1, 4]);
* // => [1, 2, 3, 4]
* _.unique([{age:13, name:"tom"},{age:15, name:"jack"},{age:13, name:"bob"}], 'age']
* // => [{age:13, name:"tom"}, {age:15, name: "jack"}]
*/
_.uniq = _.unique = function (array, isSorted, iteratee, context) {
// 如果第二个参数不是bool, 则应当理解为是比较函数, 且默认是没有排序的数组
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}