開篇說明
對的,如你所見,又開始造輪子了。哈哈,造輪子我們是認真的~
原始碼閱讀是必須的,Underscore是因為剛剛學習整理了一波函數語言程式設計,加上自己曾經沒有太多閱讀原始碼的經驗,先拿Underscore練練手,跟著前輩們走一走,學一學。也相同時能夠夯實js基礎,從原始碼中學習到更多的編碼技巧
Underscore原始碼閱讀大致按照官方文件來編寫.儘量的說明每一個函式的寫法,希望自己可以從中可以收穫大神的編碼功力。
閱讀目錄
- 窺探Underscore原始碼系列-開篇
- 窺探Underscore原始碼系列-工具
- 窺探Underscore原始碼系列-集合
- 窺探Underscore原始碼系列-陣列
- 窺探Underscore原始碼系列-函式
- 窺探Underscore原始碼系列-物件
- 窺探Underscore原始碼系列-感悟
原始碼閱讀
整體結構、變數介紹
(function(){}())
複製程式碼
常規操作哈,跟jQuery一毛一樣,通過IIFE來包裹業務邏輯,目的簡單:1、避免全域性汙染。2、保護隱私
var root = typeof self == `object` && self.self === self && self ||
typeof global == `object` && global.global === global && global ||
this ||
{};
var previousUnderscore = root._;
複製程式碼
通過global和self來判斷是node環境還是window環境,說白了,就是為了拿到全域性變數。因為我們需要一個全域性的變數_,所以為了防止衝突,我們這裡拿到root後,先暫存下之前的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;
var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeCreate = Object.create;
var Ctor = function(){};
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
if (typeof exports != `undefined` && !exports.nodeType) {
if (typeof module != `undefined` && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
_.VERSION = `1.8.3`;
複製程式碼
由於Underscore本身依賴很多原生js的方法,所以這裡為了避免原型鏈的查詢效能消耗,Underscore通過區域性變數來儲存一些常用的物件和方法。既可以提升效能,減少物件成員訪問深度也可以減少程式碼的冗長。
下面的Ctor和_ 是為了物件導向而準備的。
迭代
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);
};
};
var builtinIteratee;
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) && !_.isArray(value)) return _.matcher(value);
return _.property(value);
};
_.iteratee = builtinIteratee = function(value, context) {
return cb(value, context, Infinity);
};
複製程式碼
這裡的迭代,我們需要理清楚一個概念,在Underscore中,我們需要改變那種命令式的程式設計方式,具體的可以看我之前寫的關於函數語言程式設計的文章哈。
所以這裡想說的就是關於遍歷迭代的東西。
var results = _.map([1,2,3],function(elem){
return elem*2;
}); // => [2,4,6]
_.map = _.collect = function (obj, iteratee, context) {
iteratee = cb(iteratee, context);
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;
};
複製程式碼
我們傳遞給的 _.map 的第二個引數就是一個 iteratee,他可能是函式,物件,甚至是字串。
- value 為 null。則 iteratee 的行為只是返回當前迭代元素自身
- value 為一個函式。那麼通過內建函式 optimizeCb 對其進行優化
- value 為一個物件。那麼返回的 iteratee(_.matcher)的目的是想要知道當前被迭代元素是否匹配給定的這個物件
- value 是字面量,如數字,字串等。他指示了一個物件的屬性 key,返回的 iteratee(_.property)將用來獲得該屬性對應的值
optimizeCb()
在上面的分析中,我們知道,當傳入的 value 是一個函式時,value 還要經過一個叫 optimizeCb 的內建函式才能獲得最終的 iteratee:
var cb = function (value, context, argCount) {
// ...
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
// ...
};
複製程式碼
所以此處的optimizeCb必然是優化回撥的作用了。
// 優化回撥的函式,遍歷
var optimizeCb = function(func, context, argCount) {
// void 0 會返回真正的undefined 此處確保上下文的存在
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
// argCount為0時候,迭代過程中,我們只需要這個value就可以了
return func.call(context, value);
};
// The 2-parameter case has been omitted only because no current consumers
// 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);
};
};
複製程式碼
整體的程式碼非常清晰,待優化的回撥函式func,上下文context以及迭代回撥需要的引數個數。
上面的這個優化的回撥涉及到不同地方使用的不同迭代。這裡暫時 先放一放。等過了一遍原始碼後,看到每一個用到迭代的地方,在回頭來看,就會明白很多。
rest引數
在 ES6中,我們定義不定參方法的時候可以這麼寫
let a = (b,...c)=>{
console.log(b,c);
}
複製程式碼
但是在此之前,Underscore實現了自己的reset,使用如下:
function a(a,b,c,d,e){
console.log(a,b,c,d,e)
}
let aa = restArgs(a);//let aa = restArgs(a,4)
aa(1,2,3,4,5,6,7,8,8)
複製程式碼
看下restArgs的實現:
var restArgs = function(func, startIndex) {
//未傳則取形參個數減一
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
// 多傳了幾個引數
//length為多傳了幾個引數
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
//優化。注意rest引數總是最後一個引數, 否則會有歧義
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);
}
//撇去常用的startIndex,這裡迴圈
//先拿到前面引數
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
//拿到後面的陣列
args[startIndex] = rest;
return func.apply(this, args);
};
};
複製程式碼
物件導向
關於物件導向,這裡不做過多解釋了,可以參考我的另一篇文章:
javasript設計模式之物件導向。
我們直接看他的繼承實現吧
var Ctor = function(){};
// 定義了一個用於繼承的內部方法
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {};
// nativeCreate = Object.create;
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;
};
複製程式碼
es5 中,我們有一種建立物件的方式,Object.create 。
function Animal(name){
this.name = name;
}
Animal.prototype.eat = function(){
console.log(this.name,`鳥為食亡`);
}
var dog = Object.create(Animal.prototype);
dog.name = "毛毛";
dog.eat();
複製程式碼
ok,大概從上大家就看出來create的作用了。
baseCrate中,首先判斷是否為物件,否則退出。瀏覽器能力檢測是否具備Object.create方法,具備則用。否則採用寄生式繼承建立物件。需要注意的是,baseCreate僅僅支援原型繼承,而不能像Object.create那樣傳遞屬性列表。
結束語
開篇簡單的介紹Collection Functions上面的程式碼部分。在介紹Collection Function每個方法實現之前,我們將在下一篇看一下一些工具方法的編寫方式。
的確在造造輪子,只是更想自己擼一遍優秀程式碼。