underscore(1.7.0)中的JS技巧
- 原生api的使用:未防止某些对象修改了原生的api,underscore使用prototype进行操作,比如hasOwnProperty,使用Object.prototype.hasOwnProperty.call(obj, property)替代obj.hasOwnProperty(property)
- for in循环语句在IE中的bug:重写的原生api不会被遍历。如:
var a = { name: ‘tarol‘, toString: function() { return this.name; } } for(var i in a) { console.log(i); //IE仅打印name }
因为a.propertyIsEnumerable(‘toString‘)返回的是false,underscore的解决方法如下
var hasEnumBug = !({toString: null}).propertyIsEnumerable(‘toString‘); var nonEnumerableProps = [‘constructor‘, ‘valueOf‘, ‘isPrototypeOf‘, ‘toString‘, ‘propertyIsEnumerable‘, ‘hasOwnProperty‘, ‘toLocaleString‘]; if (hasEnumBug) { var nonEnumIdx = nonEnumerableProps.length; while (nonEnumIdx--) { var prop = nonEnumerableProps[nonEnumIdx]; if (_.has(obj, prop) && !_.contains(keys, prop)) { //... } } }
其中使用到的_.has基本上就是Object.prototype.hasOwnProperty.call(obj, prop)
nonEnumerableProps数组的每个元素为:
constructor: 指向构造函数
valueOf: 把包装对象转换为基础数据结构,typeof new Number(2).valueOf() // ‘number‘
isPrototypeOf: 检测对象是否是另一个对象的__proto__,如Object.prototype.isPrototypeOf({})
toString/toLocaleString: 把对象转换为(当前区域设置)字符串
propertyIsEnumerable: 属性是否可以被遍历。obj.propertyIsEnumberable(‘toString‘)
hasOwnProperty: 属性是否存在于对象中(不包括原型链) - 遍历函数传入的参数iteratee即迭代器:一般来说该参数应该为function,不过也支持其他数据类型,比如null、比如object、比如string,因为内部有个_.iteratee的函数将非function类型全部转换为function,而且会将function的this指向context,代码如下
_.iteratee = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return createCallback(value, context, argCount); if (_.isObject(value)) return _.matches(value); return _.property(value); }; _.identity = function(value) { return value; }; var createCallback = 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 2: return function(value, other) { return func.call(context, value, other); }; 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); }; }; _.matches = function(attrs) { var pairs = _.pairs(attrs), length = pairs.length; return function(obj) { if (obj == null) return !length; obj = new Object(obj); for (var i = 0; i < length; i++) { var pair = pairs[i], key = pair[0]; if (pair[1] !== obj[key] || !(key in obj)) return false; } return true; }; }; _.property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; };
_.identity: 返回传入值,可以说就是起个包装作用
createCallback: 修改this指向,并且根据参数数量定义接收不同参数的回调函数
_.matches: 其中_.pairs是将对象转换为数组,比如{key1: value1, key2: value2} --> [[key1, value1], [key2: value2]],遍历的对象包含matches传入对象的所有属性和值则返回true,否则返回false
_.property: 返回遍历的对象传入的key对应的value -
underscore中普遍使用此方法区别判断数组和对象内部需要遍历的次数
//obj.length !== +obj.length为true则是数组,否则是对象 var keys = obj.length !== +obj.length && _.keys(obj), length = (keys || obj).length;
- _indexOf的优化:此方法仅支持数组,接收参数array, item, isSorted,当isSorted为true(即表示告知此方法数组内元素为顺序排列)时使用二进制排序,代码如下
_.sortedIndex = function(array, obj, iteratee, context) { iteratee = _.iteratee(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = array.length; while (low < high) { var mid = low + high >>> 1; console.log(‘mid‘ + mid); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; };
- 常用方法用例的简化版本:_.pluck-->_.map, _.where-->_.filter, _.findWhere-->_.find
_.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; _.where = function(obj, attrs) { return _.filter(obj, _.matches(attrs)); }; _.findWhere = function(obj, attrs) { return _.find(obj, _.matches(attrs)); };
- _.shuffle使用Fisher-Yates shuffle算法:伪代码如下
for i from 0 to n − 1 do j ← random integer with 0 ≤ j ≤ i if j ≠ i a[i] ← a[j] a[j] ← source[i]
- underscore使用_.isArray判断是否是数组,使用obj.length === +obj.length判断是否是伪数组
- underscore使用类型判断实现函数重载
if (!_.isBoolean(isSorted)) { context = iteratee; iteratee = isSorted; isSorted = false; }
- uniq和union的关系:前者是去除数组中的重复项,后者是合并数组并去除重复项,所以underscore这样实现了union
_.union = function() { return _.uniq(flatten(arguments, true, true, [])); };
- _memoize的缓存机制:递归调用比较耗时,如
var fibonacci = function(n) { return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2); }; var now = new Date().getTime(), a = fibonacci(38), b= fibonacci(38), after = new Date().getTime(); console.log(after - now);
我这里大概需要1s的时间。如果使用_.memoize,那么可能只需要1ms的时间。
_.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = hasher ? hasher.apply(this, arguments) : key; if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; };
可以看到,返回的方法包含一个cache的属性,会保存中间值,一旦调用此方法,会先去cache中查找对应的值,如果有则直接返回。这是一种空间换时间的方法。
- 合理使用apply使函数间互相调用更加方便:因为apply接收的第二个参数是类数组,而arguments正好是类数组
_.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; _.defer = function(func) { return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); };
其中defer是一个0延时操作,应用场景是一个耗时操作后对此操作中受影响的单位进行再度操作。比如动态添加一个节点后改变该节点的样式,改变样式的代码就要放在这个0延时操作中,不然执行这部分代码的时候节点尚未添加到DOM中。
-
html中需要转义的字符
var escapeMap = { ‘&‘: ‘&‘, ‘<‘: ‘<‘, ‘>‘: ‘>‘, ‘"‘: ‘"‘, "‘": ‘'‘, ‘`‘: ‘`‘ };
- _.defaults和_.extend的异同:前者保留前面参数对象的属性,后者保留后面参数对象的属性,仅有一个if的差别
_.defaults = function(obj) { if (!_.isObject(obj)) return obj; for (var i = 1, length = arguments.length; i < length; i++) { var source = arguments[i]; for (var prop in source) { if (obj[prop] === void 0) obj[prop] = source[prop]; } } return obj; }; _.extend = function(obj) { if (!_.isObject(obj)) return obj; var source, prop; for (var i = 1, length = arguments.length; i < length; i++) { source = arguments[i]; for (prop in source) { obj[prop] = source[prop]; } } return obj; };
- 链式语法的实现:
_为构造函数,接收对象参数,如_({})
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
此构造函数会执行两次,第一次return new _(obj); 第二次将obj赋给生成对象的_wrapped属性
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; };
_.chain({})会返回_({})并设置其属性_chain为true,返回的对象为链间传递的对象
var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; };
链间的函数返回值需要通过result打包成_()链式对象,交给下一个函数处理。
_.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; _.mixin(_); _.each([‘pop‘, ‘push‘, ‘reverse‘, ‘shift‘, ‘sort‘, ‘splice‘, ‘unshift‘], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === ‘shift‘ || name === ‘splice‘) && obj.length === 0) delete obj[0]; return result(this, obj); }; }); _.each([‘concat‘, ‘join‘, ‘slice‘], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; });
将_下的函数全部赋给_.prototype,但返回值打包成_()链式对象。
_.prototype.value = function() { return this._wrapped; };
通过value()可以取到链式操作中最终的值。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。