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