-
Notifications
You must be signed in to change notification settings - Fork 642
/
underscore-1.8.3-analysis.js
3028 lines (2645 loc) · 112 KB
/
underscore-1.8.3-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-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
// 中文注释 by hanzichi @https://github.com/hanzichi
// 我的源码解读顺序(跟系列解读文章相对应)
// Object -> Array -> Collection -> Function -> Utility
(function() {
// Baseline setup
// 基本设置、配置
// --------------
// Establish the root object, `window` in the browser, or `exports` on the server.
// 将 this 赋值给局部变量 root
// root 的值, 客户端为 `window`, 服务端(node) 中为 `exports`
var root = this;
// Save the previous value of the `_` variable.
// 将原来全局环境中的变量 `_` 赋值给变量 previousUnderscore 进行缓存
// 在后面的 noConflict 方法中有用到
var previousUnderscore = root._;
// Save bytes in the minified (but not gzipped) version:
// 缓存变量, 便于压缩代码
// 此处「压缩」指的是压缩到 min.js 版本
// 而不是 gzip 压缩
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;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
// ES5 原生方法, 如果浏览器支持, 则 underscore 中会优先使用
var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind,
nativeCreate = Object.create;
// Naked function reference for surrogate-prototype-swapping.
var Ctor = function(){};
// Create a safe reference to the Underscore object for use below.
// 核心函数
// `_` 其实是一个构造函数
// 支持无 new 调用的构造函数(思考 jQuery 的无 new 调用)
// 将传入的参数(实际要操作的数据)赋值给 this._wrapped 属性
// OOP 调用时,_ 相当于一个构造函数
// each 等方法都在该构造函数的原型链上
// _([1, 2, 3]).each(alert)
// _([1, 2, 3]) 相当于无 new 构造了一个新的对象
// 调用了该对象的 each 方法,该方法在该对象构造函数的原型链上
var _ = function(obj) {
// 以下均针对 OOP 形式的调用
// 如果是非 OOP 形式的调用,不会进入该函数内部
// 如果 obj 已经是 `_` 函数的实例,则直接返回 obj
if (obj instanceof _)
return obj;
// 如果不是 `_` 函数的实例
// 则调用 new 运算符,返回实例化的对象
if (!(this instanceof _))
return new _(obj);
// 将 obj 赋值给 this._wrapped 属性
this._wrapped = obj;
};
// 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.
// 将上面定义的 `_` 局部变量赋值给全局对象中的 `_` 属性
// 即客户端中 window._ = _
// 服务端(node)中 exports._ = _
// 同时在服务端向后兼容老的 require() API
// 这样暴露给全局后便可以在全局环境中使用 `_` 变量(方法)
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// Current version.
// 当前 underscore 版本号
_.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.
// underscore 内部方法
// 根据 this 指向(context 参数)
// 以及 argCount 参数
// 二次操作返回一些回调、迭代方法
var optimizeCb = function(func, context, argCount) {
// 如果没有指定 this 指向,则返回原函数
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);
};
// 如果有指定 this,但没有传入 argCount 参数
// 则执行以下 case
// _.each、_.map
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
// _.reduce、_.reduceRight
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
// 其实不用上面的 switch-case 语句
// 直接执行下面的 return 函数就行了
// 不这样做的原因是 call 比 apply 快很多
// .apply 在运行前要对作为参数的数组进行一系列检验和深拷贝,.call 则没有这些步骤
// 具体可以参考:
// https://segmentfault.com/q/1010000007894513
// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3
// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.4
return function() {
return func.apply(context, arguments);
};
};
// 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);
};
_.iteratee = function(value, context) {
return cb(value, context, Infinity);
};
// An internal function for creating assigner functions.
// 有三个方法用到了这个内部函数
// _.extend & _.extendOwn & _.defaults
// _.extend = createAssigner(_.allKeys);
// _.extendOwn = _.assign = createAssigner(_.keys);
// _.defaults = createAssigner(_.allKeys, true);
var createAssigner = function(keysFunc, undefinedOnly) {
// 返回函数
// 经典闭包(undefinedOnly 参数在返回的函数中被引用)
// 返回的函数参数个数 >= 1
// 将第二个开始的对象参数的键值对 "继承" 给第一个参数
return function(obj) {
var length = arguments.length;
// 只传入了一个参数(或者 0 个?)
// 或者传入的第一个参数是 null
if (length < 2 || obj == null) return obj;
// 枚举第一个参数除外的对象参数
// 即 arguments[1], arguments[2] ...
for (var index = 1; index < length; index++) {
// source 即为对象参数
var source = arguments[index],
// 提取对象参数的 keys 值
// keysFunc 参数表示 _.keys
// 或者 _.allKeys
keys = keysFunc(source),
l = keys.length;
// 遍历该对象的键值对
for (var i = 0; i < l; i++) {
var key = keys[i];
// _.extend 和 _.extendOwn 方法
// 没有传入 undefinedOnly 参数,即 !undefinedOnly 为 true
// 即肯定会执行 obj[key] = source[key]
// 后面对象的键值对直接覆盖 obj
// ==========================================
// _.defaults 方法,undefinedOnly 参数为 true
// 即 !undefinedOnly 为 false
// 那么当且仅当 obj[key] 为 undefined 时才覆盖
// 即如果有相同的 key 值,取最早出现的 value 值
// *defaults 中有相同 key 的也是一样取首次出现的
if (!undefinedOnly || obj[key] === void 0)
obj[key] = source[key];
}
}
// 返回已经继承后面对象参数属性的第一个参数对象
return obj;
};
};
// An internal function for creating a new object that inherits from another.
// use in `_.create`
var baseCreate = function(prototype) {
// 如果 prototype 参数不是对象
if (!_.isObject(prototype)) return {};
// 如果浏览器支持 ES5 Object.create
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;
};
// 闭包
var property = function(key) {
return function(obj) {
return obj == null ? void 0 : 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
// Math.pow(2, 53) - 1 是 JavaScript 中能精确表示的最大数字
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
// getLength 函数
// 该函数传入一个参数,返回参数的 length 属性值
// 用来获取 array 以及 arrayLike 元素的 length 属性值
var getLength = property('length');
// 判断是否是 ArrayLike Object
// 类数组,即拥有 length 属性并且 length 属性值为 Number 类型的元素
// 包括数组、arguments、HTML Collection 以及 NodeList 等等
// 包括类似 {length: 10} 这样的对象
// 包括字符串、函数等
var isArrayLike = function(collection) {
// 返回参数 collection 的 length 属性值
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
// Collection Functions
// 数组或者对象的扩展方法
// 共 25 个扩展方法
// --------------------
// 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.
// 与 ES5 中 Array.prototype.forEach 使用方法类似
// 遍历数组或者对象的每个元素
// 第一个参数为数组(包括类数组)或者对象
// 第二个参数为迭代方法,对数组或者对象每个元素都执行该方法
// 该方法又能传入三个参数,分别为 (item, index, array)((value, key, obj) for object)
// 与 ES5 中 Array.prototype.forEach 方法传参格式一致
// 第三个参数(可省略)确定第二个参数 iteratee 函数中的(可能有的)this 指向
// 即 iteratee 中出现的(如果有)所有 this 都指向 context
// notice: 不要传入一个带有 key 类型为 number 的对象!
// notice: _.each 方法不能用 return 跳出循环(同样,Array.prototype.forEach 也不行)
_.each = _.forEach = function(obj, iteratee, context) {
// 根据 context 确定不同的迭代函数
iteratee = optimizeCb(iteratee, context);
var i, length;
// 如果是类数组
// 默认不会传入类似 {length: 10} 这样的数据
if (isArrayLike(obj)) {
// 遍历
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else { // 如果 obj 是对象
// 获取对象的所有 key 值
var keys = _.keys(obj);
// 如果是对象,则遍历处理 values 值
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj); // (value, key, obj)
}
}
// 返回 obj 参数
// 供链式调用(Returns the list for chaining)
// 应该仅 OOP 调用有效
return obj;
};
// Return the results of applying the iteratee to each element.
// 与 ES5 中 Array.prototype.map 使用方法类似
// 传参形式与 _.each 方法类似
// 遍历数组(每个元素)或者对象的每个元素(value)
// 对每个元素执行 iteratee 迭代方法
// 将结果保存到新的数组中,并返回
_.map = _.collect = function(obj, iteratee, context) {
// 根据 context 确定不同的迭代函数
iteratee = cb(iteratee, context);
// 如果传参是对象,则获取它的 keys 值数组(短路表达式)
var keys = !isArrayLike(obj) && _.keys(obj),
// 如果 obj 为对象,则 length 为 key.length
// 如果 obj 为数组,则 length 为 obj.length
length = (keys || obj).length,
results = Array(length); // 结果数组
// 遍历
for (var index = 0; index < length; index++) {
// 如果 obj 为对象,则 currentKey 为对象键值 key
// 如果 obj 为数组,则 currentKey 为 index 值
var currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
// 返回新的结果数组
return results;
};
// Create a reducing function iterating left or right.
// dir === 1 -> _.reduce
// dir === -1 -> _.reduceRight
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;
// 迭代,返回值供下次迭代调用
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
// 每次迭代返回值,供下次迭代调用
return memo;
}
// _.reduce(_.reduceRight)可传入的 4 个参数
// obj 数组或者对象
// iteratee 迭代方法,对数组或者对象每个元素执行该方法
// memo 初始值,如果有,则从 obj 第一个元素开始迭代
// 如果没有,则从 obj 第二个元素开始迭代,将第一个元素作为初始值
// context 为迭代函数中的 this 指向
return function(obj, iteratee, memo, context) {
iteratee = optimizeCb(iteratee, context, 4);
var keys = !isArrayLike(obj) && _.keys(obj),
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];
// 根据 dir 确定是向左还是向右遍历
index += dir;
}
return iterator(obj, iteratee, memo, keys, index, length);
};
}
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
// 与 ES5 中 Array.prototype.reduce 使用方法类似
// _.reduce(list, iteratee, [memo], [context])
// _.reduce 方法最多可传入 4 个参数
// memo 为初始值,可选
// context 为指定 iteratee 中 this 指向,可选
_.reduce = _.foldl = _.inject = createReduce(1);
// The right-associative version of reduce, also known as `foldr`.
// 与 ES5 中 Array.prototype.reduceRight 使用方法类似
_.reduceRight = _.foldr = createReduce(-1);
// Return the first value which passes a truth test. Aliased as `detect`.
// 寻找数组或者对象中第一个满足条件(predicate 函数返回 true)的元素
// 并返回该元素值
// _.find(list, predicate, [context])
_.find = _.detect = function(obj, predicate, context) {
var key;
// 如果 obj 是数组,key 为满足条件的下标
if (isArrayLike(obj)) {
key = _.findIndex(obj, predicate, context);
} else {
// 如果 obj 是对象,key 为满足条件的元素的 key 值
key = _.findKey(obj, predicate, context);
}
// 如果该元素存在,则返回该元素
// 如果不存在,则默认返回 undefined(函数没有返回,即返回 undefined)
if (key !== void 0 && key !== -1) return obj[key];
};
// Return all the elements that pass a truth test.
// Aliased as `select`.
// 与 ES5 中 Array.prototype.filter 使用方法类似
// 寻找数组或者对象中所有满足条件的元素
// 如果是数组,则将 `元素值` 存入数组
// 如果是对象,则将 `value 值` 存入数组
// 返回该数组
// _.filter(list, predicate, [context])
_.filter = _.select = function(obj, predicate, context) {
var results = [];
// 根据 this 指向,返回 predicate 函数(判断函数)
predicate = cb(predicate, context);
// 遍历每个元素,如果符合条件则存入数组
_.each(obj, function(value, index, list) {
if (predicate(value, index, list)) results.push(value);
});
return results;
};
// Return all the elements for which a truth test fails.
// 寻找数组或者对象中所有不满足条件的元素
// 并以数组方式返回
// 所得结果是 _.filter 方法的补集
_.reject = function(obj, predicate, context) {
return _.filter(obj, _.negate(cb(predicate)), context);
};
// Determine whether all of the elements match a truth test.
// Aliased as `all`.
// 与 ES5 中的 Array.prototype.every 方法类似
// 判断数组中的每个元素或者对象中每个 value 值是否都满足 predicate 函数中的判断条件
// 如果是,则返回 ture;否则返回 false(有一个不满足就返回 false)
// _.every(list, [predicate], [context])
_.every = _.all = function(obj, predicate, context) {
// 根据 this 指向,返回相应 predicate 函数
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;
// 如果有一个不能满足 predicate 中的条件
// 则返回 false
if (!predicate(obj[currentKey], currentKey, obj))
return false;
}
return true;
};
// Determine if at least one element in the object matches a truth test.
// Aliased as `any`.
// 与 ES5 中 Array.prototype.some 方法类似
// 判断数组或者对象中是否有一个元素(value 值 for object)满足 predicate 函数中的条件
// 如果是则返回 true;否则返回 false
// _.some(list, [predicate], [context])
_.some = _.any = function(obj, predicate, context) {
// 根据 context 返回 predicate 函数
predicate = cb(predicate, context);
// 如果传参是对象,则返回该对象的 keys 数组
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length;
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
// 如果有一个元素满足条件,则返回 true
if (predicate(obj[currentKey], currentKey, obj)) return true;
}
return false;
};
// Determine if the array or object contains a given item (using `===`).
// Aliased as `includes` and `include`.
// 判断数组或者对象中(value 值)是否有指定元素
// 如果是 object,则忽略 key 值,只需要查找 value 值即可
// 即该 obj 中是否有指定的 value 值
// 返回布尔值
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
// 如果是对象,返回 values 组成的数组
if (!isArrayLike(obj)) obj = _.values(obj);
// fromIndex 表示查询起始位置
// 如果没有指定该参数,则默认从头找起
if (typeof fromIndex != 'number' || guard) fromIndex = 0;
// _.indexOf 是数组的扩展方法(Array Functions)
// 数组中寻找某一元素
return _.indexOf(obj, item, fromIndex) >= 0;
};
// Invoke a method (with arguments) on every item in a collection.
// Calls the method named by methodName on each value in the list.
// Any extra arguments passed to invoke will be forwarded on to the method invocation.
// 数组或者对象中的每个元素都调用 method 方法
// 返回调用后的结果(数组或者关联数组)
// method 参数后的参数会被当做参数传入 method 方法中
// _.invoke(list, methodName, *arguments)
_.invoke = function(obj, method) {
// *arguments 参数
var args = slice.call(arguments, 2);
// 判断 method 是不是函数
var isFunc = _.isFunction(method);
// 用 map 方法对数组或者对象每个元素调用方法
// 返回数组
return _.map(obj, function(value) {
// 如果 method 不是函数,则可能是 obj 的 key 值
// 而 obj[method] 可能为函数
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
// 一个数组,元素都是对象
// 根据指定的 key 值
// 返回一个数组,元素都是指定 key 值的 value 值
/*
var property = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
*/
// _.pluck(list, propertyName)
_.pluck = function(obj, key) {
return _.map(obj, _.property(key));
};
// 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));
};
// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
// 寻找第一个有指定 key-value 键值对的对象
_.findWhere = function(obj, attrs) {
return _.find(obj, _.matcher(attrs));
};
// Return the maximum element (or element-based computation).
// 寻找数组中的最大元素
// 或者对象中的最大 value 值
// 如果有 iteratee 参数,则求每个元素经过该函数迭代后的最值
// _.max(list, [iteratee], [context])
_.max = function(obj, iteratee, context) {
var result = -Infinity, lastComputed = -Infinity,
value, computed;
// 单纯地寻找最值
if (iteratee == null && obj != null) {
// 如果是数组,则寻找数组中最大元素
// 如果是对象,则寻找最大 value 值
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);
// result 保存结果元素
// lastComputed 保存计算过程中出现的最值
// 遍历元素
_.each(obj, function(value, index, list) {
// 经过迭代函数后的值
computed = iteratee(value, index, list);
// && 的优先级高于 ||
if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
result = value;
lastComputed = computed;
}
});
}
return result;
};
// Return the minimum element (or element-based computation).
// 寻找最小的元素
// 类似 _.max
// _.min(list, [iteratee], [context])
_.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;
};
// Shuffle a collection, using the modern version of the
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
// 将数组乱序
// 如果是对象,则返回一个数组,数组由对象 value 值构成
// Fisher-Yates shuffle 算法
// 最优的洗牌算法,复杂度 O(n)
// 乱序不要用 sort + Math.random(),复杂度 O(nlogn)
// 而且,并不是真正的乱序
// @see https://github.com/hanzichi/underscore-analysis/issues/15
_.shuffle = function(obj) {
// 如果是对象,则对 value 值进行乱序
var set = isArrayLike(obj) ? obj : _.values(obj);
var length = set.length;
// 乱序后返回的数组副本(参数是对象则返回乱序后的 value 数组)
var shuffled = Array(length);
// 枚举元素
for (var index = 0, rand; index < length; index++) {
// 将当前所枚举位置的元素和 `index=rand` 位置的元素交换
rand = _.random(0, index);
if (rand !== index) shuffled[index] = shuffled[rand];
shuffled[rand] = set[index];
}
return shuffled;
};
// 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`.
// 随机返回数组或者对象中的一个元素
// 如果指定了参数 `n`,则随机返回 n 个元素组成的数组
// 如果参数是对象,则数组由 values 组成
_.sample = function(obj, n, guard) {
// 随机返回一个元素
if (n == null || guard) {
if (!isArrayLike(obj)) obj = _.values(obj);
return obj[_.random(obj.length - 1)];
}
// 随机返回 n 个
return _.shuffle(obj).slice(0, Math.max(0, n));
};
// Sort the object's values by a criterion produced by an iteratee.
// 排序
// _.sortBy(list, iteratee, [context])
_.sortBy = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
// 根据指定的 key 返回 values 数组
// _.pluck([{}, {}, {}], 'value')
return _.pluck(
// _.map(obj, function(){}).sort()
// _.map 后的结果 [{}, {}..]
// sort 后的结果 [{}, {}..]
_.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.
// behavior 是一个函数参数
// _.groupBy, _.indexBy 以及 _.countBy 其实都是对数组元素进行分类
// 分类规则就是 behavior 函数
var group = function(behavior) {
return function(obj, iteratee, context) {
// 返回结果是一个对象
var result = {};
iteratee = cb(iteratee, context);
// 遍历元素
_.each(obj, function(value, index) {
// 经过迭代,获取结果值,存为 key
var key = iteratee(value, index, obj);
// 按照不同的规则进行分组操作
// 将变量 result 当做参数传入,能在 behavior 中改变该值
behavior(result, value, key);
});
// 返回结果对象
return result;
};
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
// groupBy_ _.groupBy(list, iteratee, [context])
// 根据特定规则对数组或者对象中的元素进行分组
// result 是返回对象
// value 是数组元素
// key 是迭代后的值
_.groupBy = group(function(result, value, key) {
// 根据 key 值分组
// key 是元素经过迭代函数后的值
// 或者元素自身的属性值
// result 对象已经有该 key 值了
if (_.has(result, key))
result[key].push(value);
else result[key] = [value];
});
// 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) {
// key 值必须是独一无二的
// 不然后面的会覆盖前面的
// 其他和 _.groupBy 类似
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) {
// 不同 key 值元素数量
if (_.has(result, key))
result[key]++;
else result[key] = 1;
});
// Safely create a real, live array from anything iterable.
// 伪数组 -> 数组
// 对象 -> 提取 value 值组成数组
// 返回数组
_.toArray = function(obj) {
if (!obj) return [];
// 如果是数组,则返回副本数组
// 是否用 obj.concat() 更方便?
if (_.isArray(obj)) return slice.call(obj);
// 如果是类数组,则重新构造新的数组
// 是否也可以直接用 slice 方法?
if (isArrayLike(obj)) return _.map(obj, _.identity);
// 如果是对象,则返回 values 集合
return _.values(obj);
};
// Return the number of elements in an object.
// 如果是数组(类数组),返回长度(length 属性)
// 如果是对象,返回键值对数量
_.size = function(obj) {
if (obj == null) return 0;
return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};
// Split a collection into two arrays: one whose elements all satisfy the given
// predicate, and one whose elements all do not satisfy the predicate.
// 将数组或者对象中符合条件(predicate)的元素
// 和不符合条件的元素(数组为元素,对象为 value 值)
// 分别放入两个数组中
// 返回一个数组,数组元素为以上两个数组([[pass array], [fail array]])
_.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);
});
return [pass, fail];
};
// Array Functions
// 数组的扩展方法
// 共 20 个扩展方法
// Note: All array functions will also work on the arguments object.
// However, Underscore functions are not designed to work on "sparse" arrays.
// ---------------
// 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`.
// 返回数组第一个元素
// 如果有参数 n,则返回数组前 n 个元素(组成的数组)
_.first = _.head = _.take = function(array, n, guard) {
// 容错,数组为空则返回 undefined
if (array == null) return void 0;
// 没指定参数 n,则默认返回第一个元素
if (n == null || guard) return array[0];
// 如果传入参数 n,则返回前 n 个元素组成的数组
// 返回前 n 个元素,即剔除后 array.length - n 个元素
return _.initial(array, array.length - n);
};
// 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.
// 传入一个数组
// 返回剔除最后一个元素之后的数组副本
// 如果传入参数 n,则剔除最后 n 个元素
_.initial = function(array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array.
// 返回数组最后一个元素
// 如果传入参数 n
// 则返回该数组后 n 个元素组成的数组
// 即剔除前 array.length - n 个元素
_.last = function(array, n, guard) {
// 容错
if (array == null) return void 0;
// 如果没有指定参数 n,则返回最后一个元素
if (n == null || guard) return array[array.length - 1];
// 如果传入参数 n,则返回后 n 个元素组成的数组
// 即剔除前 array.length - n 个元素
return _.rest(array, Math.max(0, array.length - 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.
// 传入一个数组
// 返回剔除第一个元素后的数组副本
// 如果传入参数 n,则剔除前 n 个元素
_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, n == null || guard ? 1 : n);
};
// Trim out all falsy values from an array.
// 去掉数组中所有的假值
// 返回数组副本
// JavaScript 中的假值包括 false、null、undefined、''、NaN、0
// 联想 PHP 中的 array_filter() 函数
// _.identity = function(value) {
// return value;
// };
_.compact = function(array) {
return _.filter(array, _.identity);
};
// Internal implementation of a recursive `flatten` function.
// 递归调用数组,将数组展开
// 即 [1, 2, [3, 4]] => [1, 2, 3, 4]
// flatten(array, shallow, false)
// flatten(arguments, true, true, 1)
// flatten(arguments, true, true)
// flatten(arguments, false, false, 1)
// ===== //
// input => Array 或者 arguments
// shallow => 是否只展开一层
// strict === true,通常和 shallow === true 配合使用
// 表示只展开一层,但是不保存非数组元素(即无法展开的基础类型)
// flatten([[1, 2], 3, 4], true, true) => [1, 2]
// flatten([[1, 2], 3, 4], false, true) = > []
// startIndex => 从 input 的第几项开始展开
// ===== //
// 可以看到,如果 strict 参数为 true,那么 shallow 也为 true
// 也就是展开一层,同时把非数组过滤
// [[1, 2], [3, 4], 5, 6] => [1, 2, 3, 4]
var flatten = function(input, shallow, strict, startIndex) {
// output 数组保存结果
// 即 flatten 方法返回数据
// idx 为 output 的累计数组下标
var output = [], idx = 0;
// 根据 startIndex 变量确定需要展开的起始位置
for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
var value = input[i];
// 数组 或者 arguments
// 注意 isArrayLike 还包括 {length: 10} 这样的,过滤掉
if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
// flatten current level of array or arguments object
// (!shallow === true) => (shallow === false)
// 则表示需深度展开
// 继续递归展开
if (!shallow)
// flatten 方法返回数组
// 将上面定义的 value 重新赋值
value = flatten(value, shallow, strict);
// 递归展开到最后一层(没有嵌套的数组了)
// 或者 (shallow === true) => 只展开一层
// value 值肯定是一个数组
var j = 0, len = value.length;
// 这一步貌似没有必要
// 毕竟 JavaScript 的数组会自动扩充
// 但是这样写,感觉比较好,对于元素的 push 过程有个比较清晰的认识
output.length += len;
// 将 value 数组的元素添加到 output 数组中
while (j < len) {
output[idx++] = value[j++];
}
} else if (!strict) {
// (!strict === true) => (strict === false)
// 如果是深度展开,即 shallow 参数为 false
// 那么当最后 value 不是数组,是基本类型时
// 肯定会走到这个 else-if 判断中
// 而如果此时 strict 为 true,则不能跳到这个分支内部
// 所以 shallow === false 如果和 strict === true 搭配
// 调用 flatten 方法得到的结果永远是空数组 []
output[idx++] = value;
}
}
return output;
};
// Flatten out an array, either recursively (by default), or just one level.
// 将嵌套的数组展开
// 如果参数 (shallow === true),则仅展开一层
// _.flatten([1, [2], [3, [[4]]]]);
// => [1, 2, 3, 4];
// ====== //
// _.flatten([1, [2], [3, [[4]]]], true);
// => [1, 2, 3, [[4]]];
_.flatten = function(array, shallow) {
// array => 需要展开的数组
// shallow => 是否只展开一层
// false 为 flatten 方法 strict 变量
return flatten(array, shallow, false);
};
// Return a version of the array that does not contain the specified value(s).
// without_.without(array, *values)
// Returns a copy of the array with all instances of the values removed.
// ====== //
// _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
// => [2, 3, 4]
// ===== //
// 从数组中移除指定的元素
// 返回移除后的数组副本
_.without = function(array) {
// slice.call(arguments, 1)
// 将 arguments 转为数组(同时去掉第一个元素)
// 之后便可以调用 _.difference 方法
return _.difference(array, slice.call(arguments, 1));
};
// 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`.
// 数组去重
// 如果第二个参数 `isSorted` 为 true
// 则说明事先已经知道数组有序
// 程序会跑一个更快的算法(一次线性比较,元素和数组前一个元素比较即可)
// 如果有第三个参数 iteratee,则对数组每个元素迭代
// 对迭代之后的结果进行去重
// 返回去重后的数组(array 的子数组)
// PS: 暴露的 API 中没 context 参数
// _.uniq(array, [isSorted], [iteratee])
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
// 没有传入 isSorted 参数
// 转为 _.unique(array, false, undefined, iteratee)
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}
// 如果有迭代函数
// 则根据 this 指向二次返回新的迭代函数
if (iteratee != null)
iteratee = cb(iteratee, context);