Underscore.js 源码学习笔记

版本 Underscore.js 1.9.1

一共 1693 行,一行行看吧,希望作者是正序写的……

注释我就删了…… 太长了……

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

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

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

在浏览器中为 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(){};

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

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

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

我们知道当我们通过 new foo() 创建对象时会创建一个新的对象,然后将它的原型链绑定为 foo.propotype ,然后把这个对象作为 foo 调用的this,如果 foo 没有返回值的话,就返回这个对象。

所以通过   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 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 行,下面就是集合相关函数了,明天再看 =。=

猜你喜欢

转载自www.cnblogs.com/wenruo/p/10078450.html