underscore(1.7.0)中的JS技巧

  1. 原生api的使用:未防止某些对象修改了原生的api,underscore使用prototype进行操作,比如hasOwnProperty,使用Object.prototype.hasOwnProperty.call(obj, property)替代obj.hasOwnProperty(property)
  2. 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: 属性是否存在于对象中(不包括原型链)

  3. 遍历函数传入的参数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

  4. underscore中普遍使用此方法区别判断数组和对象内部需要遍历的次数

    //obj.length !== +obj.length为true则是数组,否则是对象
    var keys = obj.length !== +obj.length && _.keys(obj),
          length = (keys || obj).length;

     

     

  5. _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;
      };

     

  6. 常用方法用例的简化版本:_.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));
      };

     

  7. _.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]

     

  8. underscore使用_.isArray判断是否是数组,使用obj.length === +obj.length判断是否是伪数组
  9. underscore使用类型判断实现函数重载
    if (!_.isBoolean(isSorted)) {
          context = iteratee;
          iteratee = isSorted;
          isSorted = false;
        }

     

  10. uniq和union的关系:前者是去除数组中的重复项,后者是合并数组并去除重复项,所以underscore这样实现了union
    _.union = function() {
        return _.uniq(flatten(arguments, true, true, []));
      };

     

  11. _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中查找对应的值,如果有则直接返回。这是一种空间换时间的方法。

  12. 合理使用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中。

  13.  html中需要转义的字符

    var escapeMap = {
        ‘&‘: ‘&amp;‘,
        ‘<‘: ‘&lt;‘,
        ‘>‘: ‘&gt;‘,
        ‘"‘: ‘&quot;‘,
        "‘": ‘&#x27;‘,
        ‘`‘: ‘&#x60;‘
      };
  14. _.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;
      };

     

  15. 链式语法的实现:
    _为构造函数,接收对象参数,如_({})
    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()可以取到链式操作中最终的值。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。