Underscore.js 源码学习笔记(上)

版本 Underscore.js 1.9.1html

一共 1693 行。注释我就删了,太长了…node

 

总体是一个 (function() {...}());  这样的东西,咱们应该知道这是一个 IIFE(当即执行函数)。git

 

var root = typeof self == 'object' && self.self === self && self ||
    typeof global == 'object' && global.global === global && global ||
    this ||
    {};

获取当前运行环境根对象。github

在浏览器中为 self(=window)  在服务端中是 global 在一些虚拟机中是 this数组

 

var previousUnderscore = root._;

若是环境中已经定义了同名变量,防止对其形成覆盖,先把这个变量缓存起来。浏览器

 

var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

var push = ArrayProto.push,
  slice = ArrayProto.slice,
  toString = ObjProto.toString,
  hasOwnProperty = ObjProto.hasOwnProperty;

var nativeIsArray = Array.isArray,
  nativeKeys = Object.keys,
  nativeCreate = Object.create;

定义一些变量来存储 JS 定义的对象原型和方法,以便后续使用。缓存

 

var Ctor = function(){};

根据注释,这个裸函数是用来代理原型交换的?英文很差……后面应该能够看到用处,不急。app

 

var _ = function(obj) {
  if (obj instanceof _) return obj;
  if (!(this instanceof _)) return new _(obj);
  this._wrapped = obj;
};

_ 是一个构造函数,传入的对象若是已是 _ 实例就直接返回dom

咱们知道当咱们经过 new foo() 建立对象时会建立一个新的对象,而后将它的原型链绑定为 foo.propotype ,而后把这个对象做为 foo 调用的this,若是 foo 没有返回值的话,就返回这个对象。ide

因此经过   this instanceof _  能够判断是不是构造调用(是否加 new)若是不是的话 就手动加一个 new 调用一次。

经过foo生成一个对象,他有一个属性 _wrapped 的值是传入的obj。

 

if (typeof exports != 'undefined' && !exports.nodeType) {
  if (typeof module != 'undefined' && !module.nodeType && module.exports) {
    exports = module.exports = _;
  }
  exports._ = _;
} else {
  root._ = _;
}

由于 node 环境中会有 exports 变量,由此判断是在浏览器仍是服务端。服务端的话就导出 _ ,不然在根元素上添加 _ 变量。

 

_.VERSION = '1.9.1';

版本号

 

var optimizeCb = 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);
    };
    // The 2-argument case is omitted because we’re not using it.
    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);
  };
};

其实这个函数能够简化成

var optimizeCb = function(func, context) {
  return function() {
    return func.apply(context, arguments);
  };
};

 

因此说这就至关于实现了一个 bind 。 optimizeCb(func, context) = func.bind(context) 

那为何要分那么多状况呢?由于 apply  比 call 慢,并且某些状况下,还会慢不少

至于 void 0 是什么,void + 表达式会返回 undefined 这个算常识吧。而不使用 undefined 由于在某些老的浏览器中 undefined 能够被赋值,出于兼容性考虑。

 

var builtinIteratee;

var cb = function(value, context, argCount) {
  if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
  if (value == null) return _.identity; // _.identity 函数: value => value
  if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
  return _.property(value);
};

_.iteratee = builtinIteratee = function(value, context) {
  return cb(value, context, Infinity);
};

这个看到有点懵比……

首先 builtinIteratee 就是一个用来判断  iteratee 是否被用户改变的临时变量,没有其余用处。

 _.iteratee() 是一个函数 默认返回 cb

cb 做为操做集合的函数的回调函数使用

若是  _.iteratee 被修改就调用修改后的函数

若是  value == null 就返回  _.identity 一个传入什么就返回什么的函数

若是 value 是函数 就返回  optimizeCb(value, context, argCount) 也就是 value.bind(context) 

若是 value 是对象 且不是数组 就返回  _.matcher(value) 

以上都不符合就返回 _.property(value); 

 

_.isMatch = function(object, attrs) {
  var keys = _.keys(attrs), length = keys.length;
  if (object == null) return !length;
  var obj = Object(object);
  for (var i = 0; i < length; i++) {
    var key = keys[i];
    if (attrs[key] !== obj[key] || !(key in obj)) return false;
  }
  return true;
};

_.matcher = _.matches = function(attrs) {
  attrs = _.extendOwn({}, attrs);
  return function(obj) {
    return _.isMatch(obj, attrs);
  };
};
// e.g.
var isZhangsan = _.matcher({ firstname: 'san', lastname: 'zhang' });

console.log(isZhangsan({ firstname: 'san', lastname: 'zhang', age: 55 })); // true

console.log(isZhangsan({ firstname: 'si', lastname: 'zhang' })); // false

好了 如今知道不是正序写的了 哭唧唧 先不看  _.extendOwn 是什么鬼东西了 反正看名字确定是一个扩展对象的函数

首先看 isMatch , keys 至关于 Object.keys , isMatch 就是判断 attrs 中的 key 是否在 object 中都存在且对应的值都相等。

那么  _.matcher 就是设定 attrs 返回函数。返回的函数传入 obj 看其是否符合 attrs。

 

var shallowProperty = function(key) {
  return function(obj) {
    return obj == null ? void 0 : obj[key];
  };
};

var deepGet = function(obj, path) {
  var length = path.length;
  for (var i = 0; i < length; i++) {
    if (obj == null) return void 0;
    obj = obj[path[i]];
  }
  return length ? obj : void 0;
};

_.property = function(path) {
  if (!_.isArray(path)) {
    return shallowProperty(path);
  }
  return function(obj) {
    return deepGet(obj, path);
  };
};

若是传入的不是数组,就返回获取对象属性path的值的函数,若是传入一个数组,就返回获取对象属性[path]对应的值的函数。

 

var restArguments = function(func, startIndex) {
  startIndex = startIndex == null ? func.length - 1 : +startIndex;
  return function() {
    var length = Math.max(arguments.length - startIndex, 0),
        rest = Array(length),
        index = 0;
    for (; index < length; index++) {
      rest[index] = arguments[index + startIndex];
    }
    switch (startIndex) {
      case 0: return func.call(this, rest);
      case 1: return func.call(this, arguments[0], rest);
      case 2: return func.call(this, arguments[0], arguments[1], rest);
    }
    var args = Array(startIndex + 1);
    for (index = 0; index < startIndex; index++) {
      args[index] = arguments[index];
    }
    args[startIndex] = rest;
    return func.apply(this, args);
  };
};

至关于 ES6 的剩余参数,从 startIndex 开始的全部参数当作一个数组传入。分状况使用 call 仍是上面提到的效率问题。

使用举例:

function sum(arr) {
  return arr.reduce((previous, current) => {
    return previous + current;
  });
}

var restArgumentsWrapperSum = restArguments(sum);

console.log(restArgumentsWrapperSum(1, 2, 3));

 

// var nativeCreate = Object.create;

var baseCreate = function(prototype) {
  if (!_.isObject(prototype)) return {};
  if (nativeCreate) return nativeCreate(prototype);
  Ctor.prototype = prototype;
  var result = new Ctor;
  Ctor.prototype = null;
  return result;
};

至关于手动实现了一个  Object.create 利用了上面不知道什么用空函数 Ctor 。

 new Ctor 没有加括号,在构造调用的时候,若是不传入参数,能够不加括号。至关于  new Ctor() 。

 Object.create(foo) 就是建立一个对象 对象的 [[Prototype]] 为 foo.prototype,这里经过 new 实现。结束以后再将 Ctor 的 protottype 赋值为 null 。

 

var shallowProperty = function(key) {
  return function(obj) {
    return obj == null ? void 0 : obj[key];
  };
};
// e.g.
var getId = shallowProperty('id');

let obj = { id: 233, otherKey: 'who care' };

console.log(getId(obj)); // 233

传入一个 key 生成一个 获取对象属性 key 的值 的函数。

 

// var hasOwnProperty = ObjProto.hasOwnProperty;

var has = function(obj, path) {
  return obj != null && hasOwnProperty.call(obj, path);
}

就是使用了  Object.prototype.hasOwnProperty 判断对象 obj 是否存在属性 path

 

var deepGet = function(obj, path) {
  var length = path.length;
  for (var i = 0; i < length; i++) {
    if (obj == null) return void 0;
    obj = obj[path[i]];
  }
  return length ? obj : void 0;
};
// e.g.
var obj = {
  user: {
    name: {
      first: 'san',
      last: 'zhang',
    },
    id: 3
  }
}

console.log(deepGet(obj, ['user', 'name', 'last'])); // zhang

根据路径获取对象指定嵌套属性的值。

 

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = shallowProperty('length');
var isArrayLike = function(collection) {
  var length = getLength(collection);
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

若是一个对象有属性  length ,属性值为数字且在 [0, 2^53-1] 之间,则判断这个对象为类数组。

类数组常见的有 arguments、HTML Collection,数组也是类数组。

 

到这里是 174 行,下面就是集合相关函数了,明天再看 =。=

 

_.each = _.forEach = function(obj, iteratee, context) {
  // 首先将 iteratee 的 this 绑定到 context (若是 context 存在的话
  iteratee = optimizeCb(iteratee, context);
  var i, length;
  if (isArrayLike(obj)) {
    // 若是 obj 是类数组 对 obj[0 ... obj.lenght-1] 执行 iteratee
    for (i = 0, length = obj.length; i < length; i++) {
      iteratee(obj[i], i, obj);
    }
  } else {
    // 不然获取 obj 属性的集合 而后进行操做
    var keys = _.keys(obj);
    for (i = 0, length = keys.length; i < length; i++) {
      iteratee(obj[keys[i]], keys[i], obj);
    }
  }
  return obj;
};

这个函数至关于实现了 ES 的 forEach,传入(类)数组就遍历对每一项执行传入的函数  iteratee(item, idx, arr) ,若是传入的是对象就对每个键值对执行  iteratee(value, key, obj) 

举个使用例子

function foo() {
  console.log('类数组 --->');
  // arguments 是类数组
  _.each(arguments, function iteratee(item, idx, arr) {
    // 传入 arguments[i], i, arguments
    console.log(`item=${item}, idx=${idx}, arr=${JSON.stringify(arr)}, this=${this}`);
  }, '上下文');
  console.log('对象 --->');
  var obj = { k: 'v', kk: 'vv' };
  // _.keys(obj) => ['k', 'kk']
  _.each(obj, function iteratee(value, key, obj) {
    console.log(`value=${value}, key=${key}, obj=${JSON.stringify(obj)}, this=${this}`);
  }, '上下文');
}

foo('one', [2], { three: false });

// 类数组 --->
// item=one, idx=0, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文
// item=2, idx=1, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文
// item=[object Object], idx=2, arr={"0":"one","1":[2],"2":{"three":false}}, this=上下文
// 对象 --->
// value=v, key=k, obj={"k":"v","kk":"vv"}, this=上下文
// value=vv, key=kk, obj={"k":"v","kk":"vv"}, this=上下文

 

_.map = _.collect = function(obj, iteratee, context) {
  iteratee = cb(iteratee, context);
  var keys = !isArrayLike(obj) && _.keys(obj),
    length = (keys || obj).length,
    results = Array(length);
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index;
    results[index] = iteratee(obj[currentKey], currentKey, obj);
  }
  return results;
};

这个和上面实现和功能差很少,无非两个循环放到一块儿写了,且对每一项执行传入函数后都有了返回值,并返回这些返回值组成的数组。

 

var createReduce = function(dir) {
  var reducer = function(obj, iteratee, memo, initial) {
    var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length,
      index = dir > 0 ? 0 : length - 1;
    if (!initial) {
      // 若是没有传入初始值就把第一项当作初始值 并从第二项开始执行函数 (反向 reduce 就是倒数第一项咯
      memo = obj[keys ? keys[index] : index];
      index += dir;
    }
    for (; index >= 0 && index < length; index += dir) {
      var currentKey = keys ? keys[index] : index;
      memo = iteratee(memo, obj[currentKey], currentKey, obj);
    }
    return memo;
  };

  return function(obj, iteratee, memo, context) {
    var initial = arguments.length >= 3; // 根据参数个数 判断有没有初始值
    return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
  };
};

_.reduce = _.foldl = _.inject = createReduce(1);

_.reduceRight = _.foldr = createReduce(-1);

reduce的实现, createReduce 经过传入 +1/-1 能够返回 正向/反向 reduce ,返回函数的函数称做高阶函数 createReduce 就是。

源码却是不难理解。不过 reduce 的实现能够参考一下。手写代码什么的。。。

 

var cb = function(value, context, argCount) {
  if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
  if (value == null) return _.identity; // _.identity 函数: value => value
  if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
  return _.property(value);
};

var createPredicateIndexFinder = function(dir) {
  return function(array, predicate, context) {
    predicate = cb(predicate, context);
    var length = getLength(array);
    var index = dir > 0 ? 0 : length - 1;
    for (; index >= 0 && index < length; index += dir) {
      if (predicate(array[index], index, array)) return index;
    }
    return -1;
  };
};

_.findIndex = createPredicateIndexFinder(1);
_.findLastIndex = createPredicateIndexFinder(-1);


_.findKey = function(obj, predicate, context) {
  predicate = cb(predicate, context);
  var keys = _.keys(obj), key;
  for (var i = 0, length = keys.length; i < length; i++) {
    key = keys[i];
    if (predicate(obj[key], key, obj)) return key;
  }
};

_.find = _.detect = function(obj, predicate, context) {
  var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
  var key = keyFinder(obj, predicate, context);
  if (key !== void 0 && key !== -1) return obj[key];
};

这一段可能比较复杂,能够顺便好好理解一下cb函数。

首先 _.find(obj, predicate, context)
    若是 obj 不是类数组, keyFinder 就是 _.findKey
        进入 _.findKey 先将 predicate 赋值为 cb(predicate, context)
            若是 predicate 是函数 就得到 predicate.bind(context)
            若是 predicate 是 null 就得到 _.identity
            若是 predicate 是 对象且不是数组 就得到 _.matcher(value) 这个上面看过 就是一个判断传入对象是否符合 value 的函数
            以上都不符合 得到 _.property(value) 就是获取一个对象属性为 value 的值的函数 (obj) => obj[value] 若是 value 是数组 就是得到嵌套属性
        遍历 obj 每个属性 若是属性值符合 predicate 返回对用属性名
    若是 obj 是类数组, keyFinder 就是 _.findIndex
        进入 _.findIndex 先将 predicate 赋值为 cb(predicate, context)
            过程同上
        遍历 obj 每一项 若是该项符合 predicate 就返回下标
获取了 第一个 符合条件的属性名或下标,而后获取对应属性值。

// =========如下几个例子便于理解;=========

var obj = {
  a: '2333',
  b: 666,
  c: 10086,
  d: { propertyName: 'mdzz' }
};
var arr = ['2333', 666, 10086, { propertyName: 'mdzz' }];

/* 获取 number 类型的值 */
function predicate(item, index, arr) { return typeof item === 'number'; }

console.log( _.find(obj, predicate) ) // predicate 是函数, 获取 obj 中第一个属性值类型为数字的值 > 666
console.log( _.find(obj) ) // 没有传入 predicate 就是 _.identity 对于每个 truly 值都符合 因此返回第一个值 > '2333'
console.log( _.find(obj, { propertyName: 'mdzz' }) ) // predicate 是对象,查找符合对象的值 > { propertyName: 'mdzz' }
console.log( _.find(obj, 'propertyName') ) // predicate 字符串,获取含有对应属性的值 > { propertyName: 'mdzz' }

console.log( _.find(arr, predicate) ) // 666
console.log( _.find(arr) ) // '2333'
console.log( _.find(arr, { propertyName: 'mdzz' }) ) // { propertyName: 'mdzz' }
console.log( _.find(arr, 'propertyName') ) // { propertyName: 'mdzz' }

 

_.filter = _.select = function(obj, predicate, context) {
  var results = [];
  predicate = cb(predicate, context);
  _.each(obj, function(value, index, list) {
    if (predicate(value, index, list)) results.push(value);
  });
  return results;
};

_.each = _.forEach = function(obj, iteratee, context) {
  iteratee = optimizeCb(iteratee, context);
  var i, length;
  if (isArrayLike(obj)) {
    for (i = 0, length = obj.length; i < length; i++) {
      iteratee(obj[i], i, obj);
    }
  } else {
    var keys = _.keys(obj);
    for (i = 0, length = keys.length; i < length; i++) {
      iteratee(obj[keys[i]], keys[i], obj);
    }
  }
  return obj;
};

看到这里已经能理解 underscore 的这个逻辑了,若是是对象就 _.keys(obj) 遍历key, 若是是 isLikeArray 就遍历下标。

_.each 就是对遍历的每一项执行传入函数。_.filter 就是执行传入函数后返回 true 项的放入返回对象。

 

_.reject = function(obj, predicate, context) {
  return _.filter(obj, _.negate(cb(predicate)), context);
};

_.negate = function(predicate) {
  return function() {
    return !predicate.apply(this, arguments);
  };
};

_.negate 将传入函数返回值取反,_reject 就是反向 _filter 全部不符合 predicate 的项放入返回数组返回。

 

_.every = _.all = function(obj, predicate, context) {
  predicate = cb(predicate, context);
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length;
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index;
    if (!predicate(obj[currentKey], currentKey, obj)) return false;
  }
  return true;
};

若是数组的每一项都符合 predicate 就返回 true 不然返回 false

 

_.some = _.any = function(obj, predicate, context) {
  predicate = cb(predicate, context);
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length;
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index;
    if (predicate(obj[currentKey], currentKey, obj)) return true;
  }
  return false;
};

数组中存在符合 predicate 的项就返回 true 不然返回 false

 

_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
  if (!isArrayLike(obj)) obj = _.values(obj);
  if (typeof fromIndex != 'number' || guard) fromIndex = 0;
  return _.indexOf(obj, item, fromIndex) >= 0;
};

var createIndexFinder = function(dir, predicateFind, sortedIndex) {
  return function(array, item, idx) {
    var i = 0, length = getLength(array);
    if (typeof idx == 'number') {
      // 若是 idx 是数字类型 表明开始查找的位置
      if (dir > 0) {
        i = idx >= 0 ? idx : Math.max(idx + length, i);
      } else {
        length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
      }
    } else if (sortedIndex && idx && length) {
      // 若是 idx 不是数字类型 则表明 isSorted
      idx = sortedIndex(array, item);
      return array[idx] === item ? idx : -1;
    }
    if (item !== item) { // 判断 item 是否为 NaN
      idx = predicateFind(slice.call(array, i, length), _.isNaN);
      return idx >= 0 ? idx + i : -1;
    }
    for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
      if (array[idx] === item) return idx;
    }
    return -1;
  };
};

_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);


_.sortedIndex = function(array, obj, iteratee, context) {
  iteratee = cb(iteratee, context, 1);
  var value = iteratee(obj);
  var low = 0, high = getLength(array);
  while (low < high) {
    var mid = Math.floor((low + high) / 2);
    if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
  }
  return low;
};

又是比较长的的几个函数。

首先 sortedIndex 二分查找符合 iteratee 的下标。是对有序的数组进行查找。注意返回的是 iteratee(obj) <= iteratee(idx) 的最小下标 idx 查找不到的时候不返回 -1

indexOf 正向查找符合 iteratee 的下标 不存在返回 -1

lastIndexOf 反向向查找符合 iteratee 的下标 不存在返回 -1

对于 _.contains 判断 obj 中是否存在 item 从 fromIndex 开始查找

guard 存在的时候就必定是从 0 开始查找(不知道加这个参数是出于什么考虑

 

_.invoke = restArguments(function(obj, path, args) {
  var contextPath, func;
  if (_.isFunction(path)) {
    func = path;
  } else if (_.isArray(path)) {
    contextPath = path.slice(0, -1);
    path = path[path.length - 1];
  }
  return _.map(obj, function(context) {
    var method = func;
    if (!method) {
      if (contextPath && contextPath.length) {
        context = deepGet(context, contextPath);
      }
      if (context == null) return void 0;
      method = context[path];
    }
    return method == null ? method : method.apply(context, args);
  });
});


// e.g.
_.invoke({a: 1, b: '2'}, function (...args) {
  console.log(this, args);
}, '参数1', '参数2');
// [Number: 1] [ '参数1', '参数2' ]
// [String: '2'] [ '参数1', '参数2' ]

let obj = {
  one: {
    a: {
      b: function () {
        console.log(this, arguments);
      }
    }
  },
  two: {
    a: {
      b: function () {
        console.log('哈哈哈哈哈');
      }
    }
  }
};

_.invoke(obj, ['a', 'b']);
// { b: [Function: b] } {}
// 哈哈哈哈哈

_.invoke 首先经过 restArguments 将函数包裹起来 会将多余的参数做为一个数组 args 统一传入

判断 path 是不是一个函数 若是是数组的话 就获取数组的除最后一项的全部项做为 contextPath 而后得到最后一项为 path 从新赋值

若是 path 是函数的话 就分别以 obj 的每一项做为 this 调用 path 参数为 ...args

不然 获取到 obj 每一项中对应 path 的属性值 而后每一项做为 this 调用该属性值 参数为 ...args

 

320行 =。= 看不下去了 明天继续……

 

_.pluck = function(obj, key) {
  return _.map(obj, _.property(key));
};

// e.g.
let arr = [{id: 1}, {name: 'zs', id: 2}, {}];
console.log(_.pluck(arr, 'id')); // [ 1, 2, undefined ]

这个好理解,就是获取数组每一项指定 key 的值。

 

_.where = function(obj, attrs) {
  return _.filter(obj, _.matcher(attrs));
};
// e.g.
var attrs = { name: 'glory' };
var arr = [{name:'glory'}, {name:'saber'}, {name:'glory', id: 1}];

console.log(_.where(arr, attrs));

筛选出数组 obj (或者 _.keys(obj)) 中符合 attr 的项。

 

_.findWhere = function(obj, attrs) {
  return _.find(obj, _.matcher(attrs));
};
// e.g.
var attrs = { name: 'glory' };
var arr = [{name:'glory'}, {name:'saber'}, {name:'glory', id: 1}];
console.log(_.findWhere(arr, attrs)); // { name: 'glory' }

_.find 查找的是第一个 因此区别就是上面查找全部符合的项组成的数组,这个返回的是符合 attr 的第一项。没有就是 undefined

 

_.max = function(obj, iteratee, context) {
  var result = -Infinity, lastComputed = -Infinity,
      value, computed;
  if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
    obj = isArrayLike(obj) ? obj : _.values(obj);
    for (var i = 0, length = obj.length; i < length; i++) {
      value = obj[i];
      if (value != null && value > result) {
        result = value;
      }
    }
  } else {
    iteratee = cb(iteratee, context);
    _.each(obj, function(v, index, list) {
      computed = iteratee(v, index, list);
      if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
        result = v;
        lastComputed = computed;
      }
    });
  }
  return result;
};
// e.g.
var arr = [{x:3,y:4}, {x:4,y:4}, {x:4,y:5}];
var comp = function (obj) {
  return obj.x * 3 + obj.y * 4;
};
console.log(_.max(arr, comp)); // { x: 4, y: 5 }

若是传入比较函数(iteratee)就用该函数对每一项进行处理,返回处理值最大那一项。(注意返回的原元素而不是通过 iteratee 处理后的)

若是不传就直接比较。注意他判断了 value != null 因此 null 和 undefined 是不参与比较的。(null > -1 值为 true)

若是是传入比较函数的时候,初始值和计算值设为  -Infinity ,经过判断  computed === -Infinity && result === -Infinity 来肯定是否是初始状态,可是感受这样会有 bug 看了下讨论区确实有人提了 bug,尚未被解决。

可是鬼知道他判断 number 什么的是什么意思……(忽然发现这个代码都七个月没更新了……

 

_.min = function(obj, iteratee, context) {
  var result = Infinity, lastComputed = Infinity,
      value, computed;
  if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
    obj = isArrayLike(obj) ? obj : _.values(obj);
    for (var i = 0, length = obj.length; i < length; i++) {
      value = obj[i];
      if (value != null && value < result) {
        result = value;
      }
    }
  } else {
    iteratee = cb(iteratee, context);
    _.each(obj, function(v, index, list) {
      computed = iteratee(v, index, list);
      if (computed < lastComputed || computed === Infinity && result === Infinity) {
        result = v;
        lastComputed = computed;
      }
    });
  }
  return result;
};

和最大值的逻辑相同。

 

_.shuffle = function(obj) {
  return _.sample(obj, Infinity);
};

_.sample = function(obj, n, guard) {
  if (n == null || guard) {
    if (!isArrayLike(obj)) obj = _.values(obj);
    return obj[_.random(obj.length - 1)];
  }
  var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
  var length = getLength(sample);
  n = Math.max(Math.min(n, length), 0);
  var last = length - 1;
  for (var index = 0; index < n; index++) {
    var rand = _.random(index, last);
    var temp = sample[index];
    sample[index] = sample[rand];
    sample[rand] = temp;
  }
  return sample.slice(0, n);
};

_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

_.random = function(min, max) { // max ? [min, max] : [0, min]
  if (max == null) {
    max = min;
    min = 0;
  }
  // min + floor( [0, (max - min + 1)) ) -> min + [0, (max - min)] -> [min, max]
  return min + Math.floor(Math.random() * (max - min + 1));
};

_.clone 浅复制一个数组或对象。

 _.random(min, max) 随机得到 [min, max] 之间的整数。

_.sample(obj, n, guard) 得到数组或对象全部值中随机的 n 个值,若是 n 等于 obj.length 就至关于打乱数组了。

guard 存在的话就忽略 n 使得 sample 能够直接应用到 map 中。

 

_.sortBy = function(obj, iteratee, context) {
  var index = 0;
  iteratee = cb(iteratee, context);
  return _.pluck(_.map(obj, function(value, key, list) {
    return {
      value: value,
      index: index++,
      criteria: iteratee(value, key, list)
    };
  }).sort(function(left, right) {
    var a = left.criteria;
    var b = right.criteria;
    if (a !== b) {
      if (a > b || a === void 0) return 1;
      if (a < b || b === void 0) return -1;
    }
    return left.index - right.index;
  }), 'value');
};

虽然不难理解 可是以为挺巧妙的。

首先映射成 { value, index, comp(value, ...)(比较函数的计算值) } 对象,而后经过 sort 比较大小,若是计算值相同,会根据 index 保持原顺序。而后取 'value' 的值来获取数组原值。

 

// 返回一个函数 使用 iteratee 处理 obj 每一项后 将计算值和原值按照 behavior 进行分类
var group = function(behavior, partition) {
  return function(obj, iteratee, context) {
    var result = partition ? [[], []] : {};
    iteratee = cb(iteratee, context);
    _.each(obj, function(value, index) {
      var key = iteratee(value, index, obj);
      behavior(result, value, key);
    });
    return result;
  };
};

_.groupBy = group(function(result, value, key) {
  if (has(result, key)) result[key].push(value); else result[key] = [value];
});

_.indexBy = group(function(result, value, key) {
  result[key] = value;
});

_.countBy = group(function(result, value, key) {
  if (has(result, key)) result[key]++; else result[key] = 1;
});

_.partition = group(function(result, value, pass) {
  result[pass ? 0 : 1].push(value);
}, true);
// e.g.
function getScoreLevel(score) {
  if (score > 90) return 'A';
  if (score > 60) return 'B';
  return 'C';
}
console.log(_.groupBy([30, 40, 50, 60, 70, 80, 90, 100], getScoreLevel));
// { C: [ 30, 40, 50, 60 ], B: [ 70, 80, 90 ], A: [ 100 ] }

function isPass(score) {
  return score >= 60;
}
console.log(_.partition([30, 40, 50, 60, 70, 80, 90, 100], isPass));
// [ [ 60, 70, 80, 90, 100 ], [ 30, 40, 50 ] ]

groupBy 按照传入函数的计算值进行分类。indexBy 后面的值会覆盖前面的,因此应该是计算值惟一的时候使用。countBy 按照计算值统计个数。

partition 根据计算值分为两类。

 

var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
_.toArray = function(obj) {
  if (!obj) return [];
  if (_.isArray(obj)) return slice.call(obj);
  if (_.isString(obj)) {
    return obj.match(reStrSymbol);
  }
  if (isArrayLike(obj)) return _.map(obj, _.identity);
  return _.values(obj);
};

把一个对象转为数组。若是是 falsely 返回空数组。若是是数组返回数组副本,若是字符串就返回字符串分割成每一个字符的数组,类数组转成数组返回,对象返回值的集合。

reStrSymbol正则,能够处理各类字符。

[^\ud800-\udfff] 是普通的 BMP 字符

[\ud800-\udbff][\udc00-\udfff] 是成对的代理项对

[\ud800-\udfff] 是未成对的代理项字

 

_.size = function(obj) {
  if (obj == null) return 0;
  return isArrayLike(obj) ? obj.length : _.keys(obj).length;
};

获取(类)数组的长度或者对象属性的个数。

 

492行,我终于把集合部分看完了!!!明天继续。

 

_.first = _.head = _.take = function(array, n, guard) {
  if (array == null || array.length < 1) return n == null ? void 0 : [];
  if (n == null || guard) return array[0];
  return _.initial(array, array.length - n);
};

_.initial = function(array, n, guard) {
  return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};

initial 是获取数组除去最后 n 个元素的剩余部分,n 默认 1,若是传入 guard 也至关于 n 为 1。

first 是获取数组前 n 个元素。不传 n 或者传入 guard 则获取第一个元素。

guard 为了可以直接在 map 函数中使用。

 

// 得到数组最后 n 个元素
_.last = function(array, n, guard) {
  if (array == null || array.length < 1) return n == null ? void 0 : [];
  if (n == null || guard) return array[array.length - 1];
  return _.rest(array, Math.max(0, array.length - n));
};

// 得到数组除了前 n 个元素的剩余部分
_.rest = _.tail = _.drop = function(array, n, guard) {
  return slice.call(array, n == null || guard ? 1 : n);
};

见注释,没什么好说的。

 

_.compact = function(array) {
  return _.filter(array, Boolean);
};

返回数组中除了假值的元素。

 

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;
};

_.flatten = function(array, shallow) {
  return flatten(array, shallow, false);
};
// e.g.
let a = [ [1, 2, 3], [5, 6], 7, [ [8], [9] ] ];
console.log(_.flatten(a)); // [ 1, 2, 3, 5, 6, 7, 8, 9 ]
console.log(_.flatten(a, true)) // [ 1, 2, 3, 5, 6, 7, [ 8 ], [ 9 ] ]

从名字来看 flatten 要作的就是把一个多维数组拍平

若是传了 shallow 只会把二维数组拍平成一维数组 不然就递归所有拍平

shallow = false 的话 strict 应该不为 true,strict 表示扩展二维数组时若是一项不是数组就不加入最后扩展的结果。

output 不须要传 是在递归的时候用到的参数

 

_.without = restArguments(function(array, otherArrays) {
  return _.difference(array, otherArrays);
});

_.difference = restArguments(function(array, rest) {
  rest = flatten(rest, true, true);
  return _.filter(array, function(value){
    return !_.contains(rest, value);
  });
});
// e.g.
console.log(_.difference([1, 5, 8], [1, 2, 3], 5, [[8], [9]])); // [1, 5, 8] 中不在 [ 1, 2, 3, [ 8 ], [ 9 ] ] 的元素 => [ 5, 8 ]
console.log(_.without([1, 5, 8], 1, 2, 3)); // [1, 5, 8] 中不在 [ 1, 2, 3 ] 的元素 => [ 5, 8 ]
let obj = [{ m: 'd' }, { z: 'z' }];
console.log(_.without(obj, obj[0], { z: 'z' })); // [ { z: 'z' } ]

difference 将多余的参数做为数组 [rest] 传入 而后在拍平为一维数组,也就是说 difference 的参数是一堆参数

而后求的是第一个数组中删除后面全部数组都不包含的元素 剩下的部分

without 把多余的参数做为数组 [otherArrays] 传入 而后在传入 difference

也就是说 without 传入一个数组和一些值 判断数组中不包含在后面那些值的元素

 

_.uniq = _.unique = function(array, isSorted, iteratee, context) {
  if (!_.isBoolean(isSorted)) { // 若是 isSorted 不是布尔类型就认为没传 isSorted 默认为false
    context = iteratee;
    iteratee = isSorted;
    isSorted = false;
  }
  if (iteratee != null) iteratee = cb(iteratee, context);
  var result = [];
  var seen = [];
  for (var i = 0, length = getLength(array); i < length; i++) {
    var value = array[i],
        computed = iteratee ? iteratee(value, i, array) : value;
        // 没有传入 iteratee 就直接比较 value
    if (isSorted && !iteratee) { 
      // 若是是有序的 直接比较和前一个不相同就能够
      // 若是传入 iteratee 即便是有序的计算值也不必定有序因此忽略 isSorted 信息
      if (!i || seen !== computed) result.push(value);
      seen = computed;
    } else if (iteratee) { // 不然要判断以前没有存过该元素 传入计算函数比较计算值
      if (!_.contains(seen, computed)) {
        seen.push(computed);
        result.push(value);
      }
    } else if (!_.contains(result, value)) { // 没有传入计算函数就直接比较原值
      result.push(value);
    }
  }
  return result;
};

_.union = restArguments(function(arrays) {
  return _.uniq(flatten(arrays, true, true));
});
// e.g.
var getName = _.property('name'); // 获取一个对象的 name 属性
var staff = [ { name: 'joy', age: 19 }, { name: 'john', age: 19 }, { name: 'joy', age: 88 } ];
_.uniq(staff, getName); // [ { name: 'joy', age: 19 }, { name: 'john', age: 19 } ]
_.union([1,2], [2, 3], [3, 4]); // [ 1, 2, 3, 4 ]

_.uniq 将数组去重,若是传入计算函数就按照计算值去重 留第一个元素 不然直接比较元素

_.union 将传入的多个数组合并成一个数组而后去重

 

_.intersection = function(array) {
  var result = [];
  var argsLength = arguments.length;
  for (var i = 0, length = getLength(array); i < length; i++) {
    var item = array[i];
    if (_.contains(result, item)) continue;
    var j;
    for (j = 1; j < argsLength; j++) {
      if (!_.contains(arguments[j], item)) break;
    }
    if (j === argsLength) result.push(item);
  }
  return result;
};
// e.g.
_.intersection([1,2,3], [2, 3], [3, 4]); // [ 3 ]

取多个数组的交集。在遍历第一个数组的元素,若是后面每一个数组都包含,就加入结果数组。

 

_.unzip = function(array) {
  // array 是一个二维数组
  var length = array && _.max(array, getLength).length || 0; // 获取 array[0..len-1]中最长的数组的长度
  var result = Array(length);
  // result[i] 是一个数 由 array[0..len-1][i] 组成
  for (var index = 0; index < length; index++) {
    result[index] = _.pluck(array, index);
  }
  return result;
};

// 传入多个数组 而后经过 restArguments 合成一个二维数组传入 unzip
_.zip = restArguments(_.unzip);
// e.g.
var obj = [ ['张三', 18, '男'], ['李四', 16, '女'], ['王五', 23, '男'] ];
_.unzip(obj); // [ [ '张三', '李四', '王五' ], [ 18, 16, 23 ], [ '男', '女', '男' ] ]
_.zip([ '张三', '李四', '王五' ], [ 18, 16, 23 ], [ '男', '女', '男' ]); // [ [ '张三', 18, '男' ], [ '李四', 16, '女' ], [ '王五', 23, '男' ] ]

zip 和 unzip 功能差很少啊 就是传入多个数组,而后把下标相同的元素放到同一个数组,不过 zip 传入多个数组 unzip 传入二维数组

目前不太理解有什么用

 

_.object = function(list, values) {
  var result = {};
  for (var i = 0, length = getLength(list); i < length; i++) {
    if (values) {
      result[list[i]] = values[i];
    } else {
      result[list[i][0]] = list[i][1];
    }
  }
  return result;
};
// e.g.
_.object([ ['LENGTH', 34], ['WIDTH', 43] ]); // { LENGTH: 34, WIDTH: 43 }
_.object([ '叶修', '苏沐橙' ], ['君莫笑', '沐雨橙风']); // { '叶修': '君莫笑', '苏沐橙': '沐雨橙风' }

数组转键值对,一种是二维数组,一种是两个对应的数组。

 

_.range = function(start, stop, step) {
  if (stop == null) { // 只有一个参数做为 stop 默认 start 为 0
    stop = start || 0;
    start = 0;
  }
  if (!step) { // 默认 step 为 ±1
    step = stop < start ? -1 : 1;
  }
  // 计算 range 长度
  var length = Math.max(Math.ceil((stop - start) / step), 0);
  var range = Array(length);

  for (var idx = 0; idx < length; idx++, start += step) {
    range[idx] = start;
  }

  return range;
};
// e.g.
_.range(10); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
_.range(13, 100, 17); // [ 13, 30, 47, 64, 81, 98 ]

设定初始值,结束值和步长,生成一个数组。

 

_.chunk = function(array, count) {
  if (count == null || count < 1) return [];
  var result = [];
  var i = 0, length = array.length;
  while (i < length) {
    result.push(slice.call(array, i, i += count));
  }
  return result;
};
//e.g.
var _1To10 = _.range(1, 11); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
_.chunk(_1To10, 3); // [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ], [ 10 ] ]

将数组分块,按顺序,每 count 个分红一块。

 

755 行,下面是函数部分了。再开一篇博客写,太长了。

下接 《Underscore.js 源码学习笔记(下)

相关文章
相关标签/搜索