首页 > 代码库 > underscore.js源码解析【对象】

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 =http://www.mamicode.com/ 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源码解析【对象】