Baseline setup line:6
透過 IIFE(Immediately Invoked Function Expression) 立即執行函示
建立一個private的 scope 避免全域變數被污染
root line:14 1
2
3
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ;
這邊主要是在指定root object
並且可以拆成三個部分來看
第一個是針對瀏覽器
1
typeof self == 'object' && self.self === self && self
在最後一個&&之前是true的話會回傳self
第二個是針對node的環境
1
typeof global == 'object' && global.global === global && global
若是在node 環境下就會回傳global
而最後一行的this就是針對一些虛擬機的root
Protos line:22 1
2
var ArrayProto = Array .prototype, ObjProto = Object .prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol .prototype : null ;
這裡是先將常用的prototype透過變數暫存起來
並且在要將整個原始碼minified的時候可以減少檔案的大小
下面這樣的原始碼是不可以壓縮的
1
Array .prototype.someMethod = ....
但若是用像是ArrayProto變數存起來
則可以將原始碼壓縮成
Native function 接下來是先將原始碼中常用的native function做個reference
在未來使用上可以不用一直的做look up
也方便做更改
line: 25 1
2
3
4
5
6
7
8
var push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
var nativeIsArray = Array .isArray,
nativeKeys = Object .keys,
nativeCreate = Object .create;
surrogate-prototype-swapping 下面兩段程式碼要一起看
line: 37 1
2
3
4
5
6
7
8
9
10
11
var Ctor = function ( ) {};
var baseCreate = function (prototype ) {
if (!_.isObject(prototype)) return {};
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null ;
return result;
};
在第37行的時候定義了一個空的function
是為了之後要建立一個新的物件並且從其他物件繼承prototype時所需要的
為了避免某些環境沒有Object.create
因此透過 surrogate-prototype-swapping 來做物件的繼承
safe reference line: 40 1
2
3
4
5
var _ = function (obj ) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this ._wrapped = obj;
};
這邊會確保 _ 是透過 new 的方式去建立的Object
若是透過 _(obj) 去執行
此時的this指向的是全域
會形成全域變數
因此這段程式碼會執行第二個判斷式
確保 _(obj) 是透過 new 關鍵字所建立的Object
exports line 52 1
2
3
4
5
6
7
8
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module .nodeType && module .exports) {
exports = module .exports = _;
}
exports._ = _;
} else {
root._ = _;
}
這段主要就是讓root scope可以使用underscore的API
optimizeCb line: 67 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 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 );
};
};
這段希望能夠優化程式內部使用function的時候的速度
透過下面這段程式碼可以看到call的執行速度可以比apply快上不少
test_apply_call.js 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function work (a, b, c ) {
return a * b * c;
}
var a = [1 , 2 , 3 ];
console .time('apply-cost' );
for (var i = 0 ; i < 1000000 ; i++) {
work.apply(this , a);
}
console .timeEnd('apply-cost' );
console .time('call-cost' );
for (var i = 0 ; i < 1000000 ; i++) {
work.call(this , 1 , 2 , 3 );
}
console .timeEnd('call-cost' );
因此透過optimizeCb在function能夠透過call執行時就透過call
若例外才用apply來執行
cb line: 87
內建的iteratee(迭代器)
line: 92 1
2
3
4
5
6
7
8
9
10
11
12
var cb = function (value, context, argCount ) {
if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
if (value == null ) return _.identity;
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
if (_.isObject(value)) return _.matcher(value);
return _.property(value);
};
_.iteratee = builtinIteratee = function (value, context ) {
return cb(value, context, Infinity );
};
cb 則是根據value的類型來回傳相對應的callback function
restArgs line: 109 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var restArgs = function (func, startIndex ) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function ( ) {
var length = Math .max(arguments .length - startIndex, 0 ),
rest = Array (length),
index = 0 ;
for (; index < length; index++) {
rest[index] = arguments [index + startIndex];
}
switch (startIndex) {
case 0 : 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);
}
var args = Array (startIndex + 1 );
for (index = 0 ; index < startIndex; index++) {
args[index] = arguments [index];
}
args[startIndex] = rest;
return func.apply(this , args);
};
};
restArgs接受兩個參數,第一個是func,第二個是startIndex
並回傳一個function執行傳入的func
在過程中將func中的參數做成Array的封裝
原先當我們的function接收的參數是動態的時候
我們會透過
1
var args = Array .slice.call(arguments , 1 );
來把arguments變成Array來做操作
restArgs在underscore內部的作用為
將多餘的參數用Array的方式保存成為最後一個參數
1
2
3
4
5
6
7
8
9
function orig (a, b, rest ) {
...
}
var test = restArgs(orig, 2 );
test(1 , 2 ) => a: 1 , b : 2 , rest : [],
test(1 , 2 , 3 ) => a: 1 , b : 2 , rest : [3 ],
test(1 , 2 , 3 , 4 ) => a: 1 , b : 2 , rest : [3 , 4 ],
property line: 142 1
2
3
4
5
var property = function (key ) {
return function (obj ) {
return obj == null ? void 0 : obj[key];
};
};
underscore的 private function
算是一個檢驗property的factory
透過下面的例子來看
line: 152 1
2
3
4
5
6
var MAX_ARRAY_INDEX = Math .pow(2 , 53 ) - 1 ;
var getLength = property('length' );
var isArrayLike = function (collection ) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
我們透過 property('length') 建立一個 getLength的function
這個function可以接收一個object
若object有'length' 的 property 就會回傳相對應的value
否則就 void 0 (什麼都不做)
最後透過檢驗Object的長度是否存在及合法
來檢驗Object是不是一個ArrayLike的物件