underscore.js做爲一個函數式庫,也能夠說是個工具庫,在平常開發中能夠顯著的幫咱們提示開發效率。固然了,同等的還有lodash等更爲流行的庫,可是這並不妨礙咱們欣賞underscore.js的設計藝術,體驗做者強大的代碼抽象和函數複用功力。html
代碼更多的在於分享一些有用、但卻容易被忽略的內容或設計邏輯。像一些已經爛大街的函數節流/去抖啊,就再也不囉嗦了。前端
還等什麼呢?開始,寶貝兒~~node
;(function() {
// 代碼內容
})();
// ES6一個文件自己就是一個模塊
// 所以能夠在ES6中不須要當即執行函數
複製代碼
// js中undefined是能夠重寫的
function test() {
var undefined = 1;
console.log(undefined) // 1
}
test()
// 實現一個可靠的undefined
void(0) // undefined
void 0 // undefined
// 或者jq的方式
;(function(window, undefined){
// 在當即執行函數中不傳遞undefined來獲取undefined
console.log(undefined)
})(window)
複製代碼
/**
* underscore實現
* 基本的一個判斷思路是,若是是客戶端則返回客戶端的全局對象
* 若是是服務端,返回服務端的全局對象
* 不然返回this對象
* 再不然,返回一個空對象
*/
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
// 實現細節
// 若是self是一個對象,檢測self.self是否等於self,若是相等返回self。
typeof self == 'object' && self.self === self && self
// global在node環境中指代全局環境
複製代碼
客戶端中,如下都表示全局對象window:git
window
window.window
window.self
self
self.self
self.window
document.defaultView
複製代碼
做者採起self來判斷,是由於考慮到一些沒有窗口的上下文,例如Web Workers。github
// 對象類型檢測
var isObject = function(obj){
var type = typeof obj;
return type === 'function' || type === 'object' && !!type;
}
// 定義用來建立對象的構造函數
var Ctr = function(){};
// Es5原生支持的建立方法
var nativeCreate = Object.create;
// 建立對象
var baseCreate = function (prototype) {
// 若是傳入的參數不是對象,則返回空對象
if (!isObject(prototype)) return {};
// 若是是原生支持create方法,則使用原生方法
if (nativeCreate) return nativeCreate(prototype);
// 不然,經過Ctr構造函數繼承prototype對象後實例化建立一個對象
Ctr.prototype = prototype;
var result = new Ctr;
// 防止內存泄露,用完即銷燬
Ctr.prototype = null;
return result;
};
// 換成咱們可使用以下方法建立
var baseCreate = Object.create || function(prototype) {
if (!isObject(prototype)) return {};
var F = function(){};
F.prototype = prototype;
var result = new F();
F.prototype = null;
return result;
};
複製代碼
// 使用hasOwnProperty實現判斷
var has = function(obj, path) {
return obj != null && Object.hasOwnProperty.call(obj, path);
};
複製代碼
var shallowProperty = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
// 由此能夠獲得一個獲取length屬性的函數
var genLength = shallowProperty('length');
複製代碼
// 判斷類數組的思想是:
// 該集合擁有length屬性且類型爲number
// 而且length值>= 0 && <= 數組長度的極限值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' &&
length >= 0 &&
length <= MAX_ARRAY_INDEX;
};
複製代碼
// 先定義一些工具
// 求min-max直接的隨機數,含min,不含max
const random = (min, max) => {
return min + Math.floor(Math.random() * (max - min + 1));
}
// 接收一個值並直接返回
const identity = arg => arg;
// 抽樣算法
// 循環樣本,不停的從剩餘樣本中隨機抽出一個樣本,
// 將抽出的樣本與當前樣本互換位置,直到知足抽樣數量中止
const sample = (arr, n) => {
var sampleArr = arr.map(identity);
var index = 0,
length = sampleArr.length,
last = sampleArr.length - 1;
// 最大抽樣數爲數組長度
n = Math.max(Math.min(n, length), 0);
for (; index < n; index++) {
// 從剩餘樣本隨機抽樣
var tempIndex = random(index, last);
// 將抽樣結果與當前樣本互換
var temp = sampleArr[index]
sampleArr[index] = sampleArr[tempIndex]
sampleArr[tempIndex] = temp;
}
// 返回抽樣結果
return sampleArr.slice(0, n)
}
sample([1,2,3,4,5,6], 4) // 隨機抽出四個
// 洗牌算法
const shuffle = (arr) => sample(arr, Infinity);
複製代碼
// 利用filter和Boolean篩選
const arr = [1,2, '', false, undefined, null, NaN, '4'];
arr.filter(Boolean); // [1, 2, "4"]
複製代碼
// 延遲執行,就是運用一個定時器,延遲執行函數
var delay = function(func, wait) {
var args = Array.prototype.slice.call(arguments, 2);
return setTimeout(function() {
return func.apply(null, args);
}, wait);
};
// 注意,在使用時最後先綁定好函數的this做用域,
// 避免以後的this被錯誤綁定
var log = console.log.bind(console);
delay(log, 1000, '123'); // 1s後輸出123
複製代碼
// 從右往左執行
var compose = function() {
var args = arguments,
start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
while(i--) result = args[i].call(this, result);
return result;
}
}
// 執行
var funcA = (str) => str + 'aaaa';
var funcB = (str) => str + 'bbb';
var func = compose(funcB, funcA);
console.log(func('hello, ')); // hello, aaaabbb
複製代碼
var has = function(obj, key) {
return (obj !== null && obj !== undefined) &&
Object.prototype.hasOwnProperty.call(obj, key);
}
var o = {a: 1};
console.log(has(o, 'a')); // true
console.log(has(o, 'b')); // false
複製代碼
// MDN上提供的profill
// 在不支持Object.keys的時候進行profill
// 增長了ie9之前for/in不支持Enmus的遍歷的bug
if (Object.keys) {
Object.keys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [],
prop, i;
// 循環獲取全部自身屬性
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
// 若是是IE9之前,修復for/in沒法遍歷Enums類型的屬性bug
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
// 即檢測,若是obj重寫了Enums類型的屬性,保證能夠遍歷到
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
}
})();
}
複製代碼
// underscore實現了一個很巧妙的擴展函數的工廠函數
// 經過該工廠函數來建立各類不一樣的擴展函數
var createAssigner = function(keysFunc, defaults) {
// 參數keysFunc是用來獲取keys的函數
// default表示是否覆蓋原屬性,,默認覆蓋
return function(obj) {
var length = arguments.length;
if (defaults) obj = Object(obj);
if (length < 2 || obj === undefined || obj === null) return obj;
// 遍歷多有待擴展對象
for (var index = 1; index < length; index++) {
var source = arguments[index],
keys = keysFunc(source),
l = keys.length;
// 遍歷每一個對象的全部屬性
for (var i = 0; i < l; i++) {
var key = keys[i];
// 只有default爲true或者原屬性爲default的時候才覆蓋
if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
}
}
// 建立一個只擴展自身屬性的函數,不包含繼承的
// 這裏Object.keys只是爲了簡化,underscore中有_.keys實現來實現一樣的效果
var extendOwn = assign = createAssigner(Object.keys);
console.log(extendOwn({a: 1, b: 2}, {c: 1}, {d: 2}));
// 擴展全部屬性,包含繼承的
var entends = createAssigner(_.allKeys)
複製代碼
// 這裏想說的是,能夠看到不少庫都會使用下面這種方式,而不是obj.hasOwnProperty
// 緣由是防止用戶的對象重載了該屬性,從而致使報錯
// 由於js並無把hasOwnProperty做爲關鍵詞被平板2
Object.prototype.hasOwnProperty
複製代碼
// 如下數據類型均可以經過
// Object.prototype.toString的方法監測數據類型
// 和underscore的檢測原理是同樣的,實現方法不同
'Arguments',
'Function',
'String',
'Number',
'Date',
'RegExp',
'Error',
'Symbol',
'Map',
'WeakMap',
'Set',
'WeakSet'
// 寫一個類型檢查函數的工廠函數
var createTypeCheck = function(name) {
return function(obj) {
return Object.prototype.toString.call(obj) === '[object ' + name + ']';
}
}
// 經過工廠函數建立類型檢查函數
var isNumber = createTypeCheck('Number');
console.log(isNumber(123), isNumber(''));
var isString = createTypeCheck('String');
console.log(isString(123), isString(''));
var isObject = createTypeCheck('Object');
console.log(isObject(123), isObject(''), isObject({}));
// 數組檢查,能夠優先使用原生的方法
// 也正體現了該寫法比underscore的靈活性
var isArray = Array.isArray || createTypeCheck('Array');
console.log(isArray([]), isArray({})); // true false
// 其餘同理
……
// 檢查是不是元素
var isElement = function (obj) {
return !!(obj && obj.nodeType === 1);
};
// undefined檢查
var isUndefined = function(obj) {
return obj === void 0;
}
複製代碼
// 解決庫的命名衝突
// 像underscore庫的_,很容易與其餘lodash等庫衝突,還有jq/zepto的$等
// 所以提供了noConflict方法來解決衝突
// 核心思想就是交還原庫的控制權,從新命名
_.noConflict = function() {
// 交還原庫的控制權
root._ = previousUnderscore;
// 返回this能夠給該庫從新命名
return this;
};
// 例如
var underscore = _.noConflict();
複製代碼
// 生成 [min, max] 直接是隨機數,包含min,不含max
// 若是隻傳遞一個參數,則生成[0, max]直接的隨機數
var random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min))
}
複製代碼
// 天了擼,利用閉包,繞了一大圈,
// 結果只是把值原封不動的返回了。
// 看似無用,實則能夠幫咱們緩存當時的值
var memoConstant = function(value) {
return function() {
return value;
};
};
// 實例
var a = 1;
var constantA = memoConstant(a);
a = 2;
console.log(constantA())
複製代碼
百尺竿頭、日進一步
我是愣錘,一名前端愛好者
歡迎批評與交流算法
本期配圖主題:百變女神陳鈺琪小姐姐。 喜歡的點個贊吧~~~api