underscore.js源码解析对象

Posted 很好玩的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了underscore.js源码解析对象相关的知识,希望对你有一定的参考价值。

// Object Functions
  // ----------------

  // Keys in IE < 9 that won‘t be iterated by `for key in ...` and thus missed.
  /*
    ie9以下版本中,对象中的key是不能被遍历的
  */
  var hasEnumBug = !{toString: null}.propertyIsEnumerable(‘toString‘);
  var nonEnumerableProps = [‘valueOf‘, ‘isPrototypeOf‘, ‘toString‘,
                      ‘propertyIsEnumerable‘, ‘hasOwnProperty‘, ‘toLocaleString‘];// 不能被遍历的属性

  var collectNonEnumProps = function(obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;// 这是访问实例原型的一种方法!!!!!!!!

    // Constructor is a special case.
    // Constructor是特殊的属性,单独处理一下
    var prop = ‘constructor‘;
    // _.has()只访问自身属性,不行沿作用域链查找;_.contains()是用for in 查找,所以找不到不可遍历的属性
    // ‘constructor‘在自身,但是for in 遍历不到
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {
      prop = nonEnumerableProps[nonEnumIdx];
      // prop在obj上(也有可能在原型上),并且obj[prop]与原型上的值不一样,并且用for in找不到prop
      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
        keys.push(prop);
      }
    }
  };

  // Retrieve the names of an object‘s own properties.
  // Delegates to **ECMAScript 5**‘s native `Object.keys`.
  /*
    获取对象中的所有属性
  */
  _.keys = function(obj) {
    if (!_.isObject(obj)) return [];
    if (nativeKeys) return nativeKeys(obj);// Object.keys(obj)
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);// _.has()会判断对象属性是否在本身对象里
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);// 将不可遍历的属性也加进去
    return keys;
  };

  // Retrieve all the property names of an object.
  /*
    检索object拥有的和继承的所有属性的名称
  */
  _.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve the values of an object‘s properties.
  /*
    通过_.keys()获取所有value
  */
  _.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = Array(length);
    for (var i = 0; i < length; i++) {
      values[i] = obj[keys[i]];
    }
    return values;
  };

  // Returns the results of applying the iteratee to each element of the object.
  // In contrast to _.map it returns an object.
  /*
    它类似于map,但是这用于对象。转换每个属性的值。
  */
  _.mapObject = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = _.keys(obj),
        length = keys.length,
        results = {};
    for (var index = 0; index < length; index++) {
      var currentKey = keys[index];
      results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;// 返回新对象
  };

  // Convert an object into a list of `[key, value]` pairs.
  /*
    把一个对象转变为一个[key, value]形式的数组
  */
  _.pairs = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var pairs = Array(length);
    for (var i = 0; i < length; i++) {
      pairs[i] = [keys[i], obj[keys[i]]];
    }
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  /*
    返回一个object副本,使其键(keys)和值(values)对换
  */
  _.invert = function(obj) {
    var result = {};
    var keys = _.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
      result[obj[keys[i]]] = keys[i]; // 交换键和值
    }
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`.
  /*
    返回一个对象里所有的方法名, 而且是已经排序的 — 也就是说, 对象里每个方法(属性值是一个函数)的名称
  */
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // An internal function for creating assigner functions.
  var createAssigner = function(keysFunc, defaults) {
    return function(obj) {
      var length = arguments.length;
      if (defaults) obj = Object(obj);
      if (length < 2 || obj == null) return obj;// 如果参数个数小于2,或者obj为null,返回null
      for (var index = 1; index < length; index++) {// 第二个参数开始
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          if (!defaults || obj[key] === void 0) obj[key] = source[key];// 若source中没有key属性才会复制,相同属性key不会覆盖
        }
      }
      return obj;
    };
  };

  // Extend a given object with all the properties in passed-in object(s).
  /*
    复制source对象中的所有属性覆盖到destination对象上,并且返回 destination 对象. 不会覆盖
  */
  _.extend = createAssigner(_.allKeys);

  // Assigns a given object with all the own properties in the passed-in object(s).
  // (https://developer.mozilla.org/docs/Web/javascript/Reference/Global_Objects/Object/assign)
  /*
    类似于 extend, 但只复制自己的属性覆盖到目标对象。
  */
  _.extendOwn = _.assign = createAssigner(_.keys);

  // Returns the first key on an object that passes a predicate test.
  /*
    类似findIndex(),返回符合predicate的key
  */
  _.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;
    }
  };

  // Internal pick helper function to determine if `obj` has key `key`.
  /*
    判断key是obj的自身属性(或继承属性)
  */
  var keyInObj = function(value, key, obj) {
    return key in obj;
  };

  // Return a copy of the object only containing the whitelisted properties.
  /*
    返回一个object副本,只过滤出keys(有效的键组成的数组)参数指定的属性值。或者接受一个判断函数,指定挑选哪个key。、
    restArgs()把keys及后面的参数变为一个数组
  */
  _.pick = restArgs(function(obj, keys) {
    var result = {}, iteratee = keys[0];
    if (obj == null) return result;
    if (_.isFunction(iteratee)) {// 如果此函数的第二个参数是一个函数
      if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);// 如果后面还有第三个参数,作为itetratee的参数传入,变成一个高阶函数
      keys = _.allKeys(obj);
    } else {// 第二个参数不是一个函数
      iteratee = keyInObj;// 就把上面的keyInObj传给他
      keys = flatten(keys, false, false);
      obj = Object(obj);
    }
    for (var i = 0, length = keys.length; i < length; i++) {
      var key = keys[i];
      var value = obj[key];
      if (iteratee(value, key, obj)) result[key] = value;// 如果是函数,则执行函数;不是函数,执行 key in obj
    }
    return result;
  });

  // Return a copy of the object without the blacklisted properties.
  /*
    返回一个object副本,只过滤出除去keys(有效的键组成的数组)参数指定的属性值。 或者接受一个判断函数,指定忽略哪个key。
  */
  _.omit = restArgs(function(obj, keys) {
    var iteratee = keys[0], context;
    if (_.isFunction(iteratee)) {
      iteratee = _.negate(iteratee);// 返回iteratee的否定版本
      if (keys.length > 1) context = keys[1];
    } else {
      keys = _.map(flatten(keys, false, false), String);
      iteratee = function(value, key) {
        return !_.contains(keys, key);
      };
    }
    return _.pick(obj, iteratee, context);
  });

  // Fill in a given object with default properties.
  _.defaults = createAssigner(_.allKeys, true);

  // Creates an object that inherits from the given prototype object.
  // If additional properties are provided then they will be added to the
  // created object.
  /*
    创建具有给定原型的新对象, 可选附加props 作为 own的属性。 基本上,和Object.create一样, 但是没有所有的属性描述符。
  */
  _.create = function(prototype, props) {
    var result = baseCreate(prototype);// 原型继承
    if (props) _.extendOwn(result, props);
    return result;
  };

  // Create a (shallow-cloned) duplicate of an object.
  /*
    创建 一个浅复制(浅拷贝)的克隆object。任何嵌套的对象或数组都通过引用拷贝,不会复制。
  */
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  /*
    用 object作为参数来调用函数interceptor,然后返回object。这种方法的主要意图是作为函数链式调用 的一环, 为了对此对象执行操作并返回对象本身。
  */
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Returns whether an object has a given set of `key:value` pairs.
  /*
    告诉你properties中的键和值是否包含在object中。
  */
  _.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;
  };


  // Internal recursive comparison function for `isEqual`.
  /*
    内部比较函数,判断a,b是否相等??????????????????????????????????????????????????????????????????????????????
  */
  var eq, deepEq;
  eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren‘t identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a === 1 / b;// 0 === -0
    // A strict comparison is necessary because `null == undefined`.
    // 针对null == undefined ,"==="是必须的
    if (a == null || b == null) return a === b;
    // `NaN`s are equivalent, but non-reflexive.
    // NaN
    if (a !== a) return b !== b;
    // Exhaust primitive checks
    var type = typeof a;
    if (type !== ‘function‘ && type !== ‘object‘ && typeof b != ‘object‘) return false;
    return deepEq(a, b, aStack, bStack);
  };

  // Internal recursive comparison function for `isEqual`.
  deepEq = function(a, b, aStack, bStack) {
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    // 判断a,b的类型,并且a,b类型一定相等
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
      case ‘[object RegExp]‘:
      // RegExps are coerced to strings for comparison (Note: ‘‘ + /a/i === ‘/a/i‘)
      case ‘[object String]‘:
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return ‘‘ + a === ‘‘ + b;
      case ‘[object Number]‘:
        // `NaN`s are equivalent, but non-reflexive.
        // Object(NaN) is equivalent to NaN.
        if (+a !== +a) return +b !== +b;
        // An `egal` comparison is performed for other numeric values.
        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      case ‘[object Date]‘:
      case ‘[object Boolean]‘:
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a === +b;
      case ‘[object Symbol]‘:
        return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    }

    var areArrays = className === ‘[object Array]‘;
    if (!areArrays) {// 如果不是数组
      if (typeof a != ‘object‘ || typeof b != ‘object‘) return false;

      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
      // from different frames are.
      var aCtor = a.constructor, bCtor = b.constructor;
      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
                               _.isFunction(bCtor) && bCtor instanceof bCtor)
                          && (‘constructor‘ in a && ‘constructor‘ in b)) {
        return false;
      }
    }
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

    // Initializing stack of traversed objects.
    // It‘s done here since we only need them for objects and arrays comparison.
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] === a) return bStack[length] === b;
    }

    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);

    // Recursively compare objects and arrays.
    if (areArrays) {// 如果是数组
      // Compare array lengths to determine if a deep comparison is necessary.
      length = a.length;
      if (length !== b.length) return false;// 如果长度不等,直接pass
      // Deep compare the contents, ignoring non-numeric properties.
      while (length--) {
        if (!eq(a[length], b[length], aStack, bStack)) return false;// 比较数组中的每一项
      }
    } else {// 如果不是,深度比较对象
      // Deep compare objects.
      var keys = _.keys(a), key;
      length = keys.length;
      // Ensure that both objects contain the same number of properties before comparing deep equality.
      if (_.keys(b).length !== length) return false;// 如果key的长度不等,直接pass
      while (length--) {
        // Deep compare each member
        key = keys[length];
        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return true;
  };

  // Perform a deep comparison to check if two objects are equal.
  /*
    执行两个对象之间的优化深度比较,确定他们是否应被视为相等。
  */
  _.isEqual = function(a, b) {
    return eq(a, b);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  /*
    判断是否为空
  */
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    // 有length的类型,直接判断length属性是否为0
    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
    return _.keys(obj).length === 0;
  };

  // Is a given value a DOM element?
  /*
    如果object是一个DOM元素,返回true。
  */
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5‘s native Array.isArray
  /*
    判断是否是数组类型
  */
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === ‘[object Array]‘;
  };

  // Is a given variable an object?
  /*
    判断是不是对象
  */
  _.isObject = function(obj) {
    var type = typeof obj;
    return type === ‘function‘ || type === ‘object‘ && !!obj;// !!排除null
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
  /*
    定义各个类型判断的函数
  */
  _.each([‘Arguments‘, ‘Function‘, ‘String‘, ‘Number‘, ‘Date‘, ‘RegExp‘, ‘Error‘, ‘Symbol‘, ‘Map‘, ‘WeakMap‘, ‘Set‘, ‘WeakSet‘], function(name) {
    _[‘is‘ + name] = function(obj) {
      return toString.call(obj) === ‘[object ‘ + name + ‘]‘;
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE < 9), where
  // there isn‘t any inspectable "Arguments" type.
  /*
    判断是否是arguments
  */
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return _.has(obj, ‘callee‘);
    };
  }

  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
  // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
  var nodelist = root.document && root.document.childNodes;
  if (typeof /./ != ‘function‘ && typeof Int8Array != ‘object‘ && typeof nodelist != ‘function‘) {
    _.isFunction = function(obj) {
      return typeof obj == ‘function‘ || false;
    };
  }

  // Is a given object a finite number?
  /*
    判断是否有限
  */
  _.isFinite = function(obj) {
    return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`?
  _.isNaN = function(obj) {
    return _.isNumber(obj) && isNaN(obj);
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) === ‘[object Boolean]‘;
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  /*
    判断obj是否自身而不是在原型链中拥有key
  */
  _.has = function(obj, key) {
    return obj != null && hasOwnProperty.call(obj, key);
  };

 

小结

1.访问实例原型

var constructor = obj.constructor;
var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

 

2.判断是否有限

_.isFinite = function(obj) {
return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
};

 

以上是关于underscore.js源码解析对象的主要内容,如果未能解决你的问题,请参考以下文章

underscore.js源码解析集合部分

underscore.js源码解析

underscore.js源码解析函数

underscore.js源码解析数组

underscore.js 源码分析5 基础函数和each函数的使用

underscore.js源码研究