underscore 是一款成熟可靠的第三方開源庫,正如 jQuery 統一了不一樣瀏覽器之間的 DOM 操做的差別,讓咱們能夠簡單地對 DOM 進行操做,underscore 則提供了一套完善的函數式編程的接口,讓咱們更方便地在 JavaScript 中實現函數式編程。前端
jQuery 在加載時,會把自身綁定到惟一的全局變量 $
上,underscore 與其相似,會把自身綁定到惟一的全局變量 _
上,這也是爲啥它的名字叫 underscore 的緣由。node
在搭建 underscore 以前,讓咱們先來了解一下什麼是 「當即執行函數(IIFE)」.webpack
當即執行函數,顧名思義,就是定義好的匿名函數當即執行,寫法以下:git
(function(name) {
console.log(name);
})('suporka');
複製代碼
其做用是:經過定義一個匿名函數,建立了一個新的函數做用域,至關於建立了一個「私有」的命名空間,該命名空間的變量和方法,不會破壞污染全局的命名空間。github
// 函數外部拿不到內部的變量,所以不會形成變量污染,內部的變量在內部使用便可
(function() {
var name = 'suporka';
})();
console.log(name); // name is undefinded
複製代碼
_
的掛載當咱們在瀏覽器中使用 _.map([1,2,3], function(item){console.log(item)})
時, _
是掛載在 Window
對象上的,若是咱們想在 node 環境中使用呢 ?web
(function() {
// root 爲掛載對象,爲 self 或 global 或 this 或 {}
var root =
(typeof self == 'object' && self.self === self && self) ||
(typeof global == 'object' && global.global === global && global) ||
this ||
{};
// _ 應該是一個對象,對象內有屬性函數
var _ = {};
root._ = _;
_.VERSION = '1.9.1'; // 給咱們的 underscore 一個版本號吧
})();
複製代碼
首先咱們實現一個倒裝字符串的方法編程
(function() {
// root 爲掛載對象,爲 self 或 global 或 this 或 {}
var root =
(typeof self == 'object' && self.self === self && self) ||
(typeof global == 'object' && global.global === global && global) ||
this ||
{};
// _ 應該是一個對象,對象內有屬性函數
var _ = {};
root._ = _;
_.VERSION = '1.9.1'; // 給咱們的 underscore 一個版本號吧
/** * 字符串倒裝 */
_.reverse = function(string) {
return string
.split('')
.reverse()
.join('');
};
})();
_.reverse('suporka'); // akropus
複製代碼
不錯,很快實現,可是這種是函數式寫法,調用一個函數去實現,若是咱們要實現面向對象寫法呢?如 _('suporka').reverse()
! underscore 是支持這種寫法的,仔細觀察 _('suporka')
, 你會發現,_
是一個函數啊,和咱們前面定義的 var _ = {};
不一致,那麼該怎麼實現呢?數組
咱們先測試一下:若是 _
爲函數,咱們須要保存其傳進來的參數 obj . new _() 生成一個實例原型對象瀏覽器
function _(obj) {
this._wrapped = obj;
}
_.reverse = function(string) {
return string
.split('')
.reverse()
.join('');
};
_.reverse('suporka'); // "akropus", 函數式調用沒問題
new _('suporka');
複製代碼
從圖中咱們能夠看出,實例原型對象的 __proto__
(原型)的 constructor 構造函數指回了原來的 _(obj) 函數,要調用其 reverse() 方法只能 new _('suporka').constructor.reverse()
多了一個層級,不符合咱們本來的指望。那咱們不如在_proto_
屬性下增長一個和 reverse 同樣的函數,這樣不就能夠直接調用了嗎?app
let us try it !
function _(obj) {
this._wrapped = obj;
}
_.reverse = function(string) {
return string
.split('')
.reverse()
.join('');
};
_.reverse('suporka'); // "akropus", 函數式調用沒問題
_.prototype.reverse = function() {
return this._wrapped
.split('')
.reverse()
.join('');
};
new _('suporka').reverse(); // "akropus", 面向對象式調用沒問題
複製代碼
_() function
new _('suporka').reverse()
有點累贅,去掉 new
, 重寫 function _()
var _ = function(obj) {
// 若是傳入的是實例後對象,返回它
if (obj instanceof _) return obj;
// 若是尚未實例化,new _(obj)
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
_('suporka').reverse(); // "akropus", 面向對象式調用沒問題
複製代碼
/** * 數組或對象遍歷方法,並返回修改後的對象或數組 * @param iteratee 回調函數 * @param context 回調函數中this的指向 */
_.map = function(obj, iteratee, context) {
var length = obj.length,
results = Array(length);
for (var index = 0; index < length; index++) {
results[index] = iteratee.call(context, obj[index], index, obj);
}
return results;
};
_.prototype.map = function(iteratee, context) {
var length = this._wrapped.length,
results = Array(length);
for (var index = 0; index < length; index++) {
results[index] = iteratee.call(
context,
this._wrapped[index],
index,
this._wrapped
);
}
return results;
};
_([1, 2, 3]).map(
function(item) {
console.log(item + this.value);
},
{ value: 1 }
); // 2,3,4
_.map(
[1, 2, 3],
function(item) {
console.log(item + this.value);
},
{ value: 1 }
); // 2,3,4
複製代碼
嗯嗯,真好,完美實現。到這裏你會發現一個問題,每次我新增一個方法,都得在 prototype
上同時寫多一次這個類似函數,你會發現二者之間只是 obj
換成了 this._wrapped
.有沒有辦法讓它自動生成呢?答案確定是有!
在這以前,咱們須要先實現一個遍歷方法 each(),以下:
// 最大數值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
// 判斷是否爲數組
var isArrayLike = function(collection) {
var length = collection.length;
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
/** * 數組或對象遍歷方法 */
_.each = function(obj, callback) {
var length,
i = 0;
if (isArrayLike(obj)) {
// 數組
length = obj.length;
for (; i < length; i++) {
// 這裏隱式的調用了一次 callback.call(obj[i], obj[i], i);
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
} else {
// 對象
for (i in obj) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
}
return obj;
};
複製代碼
用 each() 來遍歷 _
上掛載的全部方法函數,並給 prototype 建立相應的方法函數。那麼,在此以前,咱們須要知道 _
上掛載了哪些方法名,來寫個 functions() 實現它
/** * 判斷是否爲 function */
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
/** * 獲取_的全部屬性函數名 */
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
複製代碼
用 each()實現它:
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.each(_.functions(_), function(name) {
var func = _[name];
_.prototype[name] = function() {
var args = [this._wrapped];
// args = [this._wrapped, arguments[0], arguments[1]...], 至關於用 this._wrapped 代替 obj 實現
push.apply(args, arguments);
return func.apply(_, args);
};
});
複製代碼
(function() {
// root 爲掛載對象,爲 self 或 global 或 this 或 {}
var root =
(typeof self == 'object' && self.self === self && self) ||
(typeof global == 'object' && global.global === global && global) ||
this ||
{};
var _ = function(obj) {
// 若是傳入的是實例後對象,返回它
if (obj instanceof _) return obj;
// 若是尚未實例化,new _(obj)
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// 最大數值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
// 判斷是否爲數組
var isArrayLike = function(collection) {
var length = collection.length;
return (
typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX
);
};
root._ = _;
_.VERSION = '1.9.1'; // 給咱們的 underscore 一個版本號吧
/** * 字符串倒裝 */
_.reverse = function(string) {
return string
.split('')
.reverse()
.join('');
};
/** * 判斷是否爲 function */
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
/** * 獲取_的全部屬性函數名 */
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
/** * 數組或對象遍歷方法,並返回修改後的對象或數組 * @param iteratee 回調函數 * @param context 回調函數中this的指向 */
_.map = function(obj, iteratee, context) {
var length = obj.length,
results = Array(length);
for (var index = 0; index < length; index++) {
results[index] = iteratee.call(context, obj[index], index, obj);
}
return results;
};
/** * 數組或對象遍歷方法 */
_.each = function(obj, callback) {
var length,
i = 0;
if (isArrayLike(obj)) {
// 數組
length = obj.length;
for (; i < length; i++) {
// 這裏隱式的調用了一次 callback.call(obj[i], obj[i], i);
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
} else {
// 對象
for (i in obj) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
}
return obj;
};
_.each(_.functions(_), function(name) {
var func = _[name];
_.prototype[name] = function() {
var args = [this._wrapped];
// args = [this._wrapped, arguments[0], arguments[1]...], 至關於用 this._wrapped 代替 obj 實現
push.apply(args, arguments);
return func.apply(_, args);
};
});
})();
複製代碼