最近在拜读只有1700行(含注释)代码的Underscore.js 1.9.1,记录一些东西
(参考https://underscorejs.org/underscore.js,https://github.com/hanzichi/underscore-analysis)
- void 0替代了undefined;因为在低版本ie中undefined可以被重新赋值,或者在局部作用域中也可以被重新赋值;所以undefined显得不那么靠谱,于是就用void 0替代了undefined,void运算符对表达式求值都会返回undefined;
- 数据类型判断,先看原声方法支持与否,不支持再用Object.prototype.toString.call方法进行判断;不过在 IE < 9 下对 arguments 调用 Object.prototype.toString.call,结果是 [object Object],这时可以用arguments.callee是否存在来判断;dom元素判断方法为存在且nodeType为1;
- 如果在对象中重写了原型上的不可枚举属性,那么for in是可以取到这个属性的;但是在低版本ie中是不会取到的,它们会被认定为不可枚举属性;可以用obj.propertyIsEnumerable(prop)来确定对象中指定的属性是否可以被for in循环枚举,但是通过原型链继承的属性除外;
- createAssigner(补)
- 判断是否相同(注意0 === -0,但不相同)
/* 1 */ if (a === b) return a !== 0 || 1 / a === 1 / b; /* 2 null undefined */ if (a == null || b == null) return false; /* 3 NaN */ if (a !== a) return b !== b; /* 4 primitive */ var type = typeof a; if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; /* 5 正则 和 String */ return '' + a === '' + b; /* 6 Number */ if (+a !== +a) return +b !== +b; return +a === 0 ? 1 / +a === 1 / b : +a === +b; /* 7 Date Boolean */ return +a === +b; /* 8 Symbol */ var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
-
Object Functions(补)
- 位运算符&换算成二进制后按位与计算;不同于&&;
// 判断偶数 var isEven = function(num) { return !(num & 1); };
-
数组去重
/* 1 一一比较 */ function removeSame(arr){ return arr.filter(function(item, index, arr){ return arr.indexOf(item) === index; }); } /* 2 */ [...new Set(arr)] /* 3 */ Array.from(new Set(arr));
- flatten
// shallow为false深度展开,当shallow strict都为true可忽略非数组元素 var flatten = function(input, shallow, strict, output) { output = output || []; var idx = output.length; for (var i = 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { if (shallow) { var j = 0, len = value.length; while (j < len) output[idx++] = value[j++]; } else { flatten(value, shallow, strict, output); idx = output.length; } } else if (!strict) { output[idx++] = value; } } return output; };
-
_.compact _.difference _.without(补)
- NaN和Number.NaN是一样的;只有Number.isNaN(NaN)才返回true,isNaN()会将参数现转换为数字类型;
isNaN = function(obj) { Number.isNaN(Number(obj)); } Number.isNaN = Number.isNaN || function(obj) { return typeof obj === "number" && isNaN(obj); } _.isNaN = function(obj) { return _.isNumber(obj) && isNaN(obj); };
- 类数组转为数组
Array.prototype.slice.call(obj) 或 Array.from(obj)// 优化(传递arguments给任何参数,将导致Chrome和Node中使用的V8引擎跳过对其的优化,这也将使性能相当慢) var args = new Array(arguments.length); for(var i = 0; i < args.length; ++i) { args[i] = arguments[i]; }
- 数组乱序
/* 1 O(n^2)*/ function shuffle(arr) { var result = []; while (arr.length) { var index = ~~(Math.random() * arr.length); // 两次按位取反,无论正负,去掉小数点后面的数 result.push(arr[index]); arr.splice(index, 1); } return result; } /* 2 O(nlogn)*/ function shuffle(arr) { return arr.sort(function(a, b) { return Math.random() - 0.5; }); } /* 3 Fisher–Yates Shuffle 遍历数组 将其与之前的元素交换 O(n)*/ function shuffle(arr){ var len = arr.length; var shuffled = Array(len); for(var i=0, rand; i < len; i++){ rand = ~~(Math.random()*(i+1)); if(rand !== i){ shuffled[i] = shuffled[rand]; } shuffled[rand] = arr[i]; } return shuffled; }
- Group(补)
- bind polyfill
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply(this instanceof fNOP ? this : oThis, // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的 aArgs.concat(Array.prototype.slice.call(arguments))); }; // 维护原型关系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }; }
- 节流
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
- 防抖
_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArguments(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };