Baseline setup

line:6
1
2
3
(function() {
}());

透過 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變數存起來

則可以將原始碼壓縮成

1
a.someMethod = ...

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(){};
// line: 132
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {};
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype; // 繼承prototype
var result = new Ctor; // 建立一個繼承了prototype的物件
Ctor.prototype = null; // 將Ctor的prototype清除
return result; // 回傳繼承prototype的物件
};

在第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);
};
// The 2-parameter case has been omitted only because no current consumers
// made use of it.
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');
// output
// apply-cost: 47.439ms
// call-cost: 8.755ms

因此透過optimizeCb在function能夠透過call執行時就透過call

若例外才用apply來執行

cb

line: 87
1
var builtinIteratee;

內建的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);
};
// line: 103
_.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的物件