diff --git a/lodash.js b/lodash.js index 718a1849f..a453812ee 100644 --- a/lodash.js +++ b/lodash.js @@ -379,15 +379,15 @@ // iterate own properties using `Object.keys` if it's fast ' <% if (isKeysFast && useHas) { %>\n' + ' var ownIndex = -1,\n' + - ' ownProps = nativeKeys(iteratee),\n' + + ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' + ' length = ownProps.length;\n\n' + ' <%= objectBranch.beforeLoop %>;\n' + ' while (++ownIndex < length) {\n' + ' index = ownProps[ownIndex];\n' + - ' if (!(skipProto && index == \'prototype\')) {\n' + - ' value = iteratee[index];\n' + - ' <%= objectBranch.inLoop %>\n' + - ' }\n' + + ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' + + ' value = iteratee[index];\n' + + ' <%= objectBranch.inLoop %>\n' + + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + ' }' + // else using a for-in loop @@ -946,7 +946,6 @@ */ var shimKeys = createIterator({ 'args': 'object', - 'exit': 'if (!(object && objectTypes[typeof object])) throw TypeError()', 'init': '[]', 'inLoop': 'result.push(index)' }); @@ -1231,7 +1230,7 @@ * // => true */ function has(object, property) { - return hasOwnProperty.call(object, property); + return object ? hasOwnProperty.call(object, property) : false; } /** @@ -1282,7 +1281,7 @@ * // => true */ function isElement(value) { - return !!(value && value.nodeType == 1); + return value ? value.nodeType == 1 : false; } /** @@ -1547,7 +1546,7 @@ // http://es5.github.com/#x8 // and avoid a V8 bug // http://code.google.com/p/v8/issues/detail?id=2291 - return value && objectTypes[typeof value]; + return value ? objectTypes[typeof value] : false; } /** @@ -1686,12 +1685,74 @@ * // => ['one', 'two', 'three'] (order is not guaranteed) */ var keys = !nativeKeys ? shimKeys : function(object) { + var type = typeof object; + // avoid iterating over the `prototype` property - return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') - ? shimKeys(object) - : nativeKeys(object); + if (type == 'function' && propertyIsEnumerable.call(object, 'prototype')) { + return shimKeys(object); + } + return object && objectTypes[type] + ? nativeKeys(object) + : []; }; + /** + * Merges enumerable properties of the source object(s) into the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The destination object. + * @param {Object} [source1, source2, ...] The source objects. + * @param {Object} [indicator] Internally used to indicate that the `stack` + * argument is an array of traversed objects instead of another source object. + * @param {Array} [stack=[]] Internally used to keep track of traversed objects + * to avoid circular references. + * @returns {Object} Returns the destination object. + * @example + * + * var stooges = [ + * { 'name': 'moe' }, + * { 'name': 'larry' } + * ]; + * + * var ages = [ + * { 'age': 40 }, + * { 'age': 50 } + * ]; + * + * _.merge(stooges, ages); + * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] + */ + var merge = createIterator(extendIteratorOptions, { + 'args': 'object, source, indicator, stack', + 'top': + 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + + 'if (!recursive) stack = [];\n' + + 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + + ' if (iteratee = arguments[argsIndex]) {', + 'inLoop': + 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + + ' found = false; stackLength = stack.length;\n' + + ' while (stackLength--) {\n' + + ' if (found = stack[stackLength].source == value) break\n' + + ' }\n' + + ' if (found) {\n' + + ' result[index] = stack[stackLength].value\n' + + ' } else {\n' + + ' destValue = (destValue = result[index]) && isArr\n' + + ' ? (isArray(destValue) ? destValue : [])\n' + + ' : (isPlainObject(destValue) ? destValue : {});\n' + + ' stack.push({ value: destValue, source: value });\n' + + ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + + ' }\n' + + '} else if (value != null) {\n' + + ' result[index] = value\n' + + '}' + }); + /** * Creates a shallow clone of `object` composed of the specified properties. * Property names may be specified as individual arguments or as arrays of @@ -1709,11 +1770,14 @@ * // => { 'name': 'moe', 'age': 40 } */ function pick(object) { + var result = {}; + if (!object) { + return result; + } var prop, index = 0, props = concat.apply(ArrayProto, arguments), - length = props.length, - result = {}; + length = props.length; // start `index` at `1` to skip `object` while (++index < length) { @@ -2025,63 +2089,6 @@ */ var map = createIterator(baseIteratorOptions, mapIteratorOptions); - /** - * Merges enumerable properties of the source object(s) into the `destination` - * object. Subsequent sources will overwrite propery assignments of previous - * sources. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @param {Object} [indicator] Internally used to indicate that the `stack` - * argument is an array of traversed objects instead of another source object. - * @param {Array} [stack=[]] Internally used to keep track of traversed objects - * to avoid circular references. - * @returns {Object} Returns the destination object. - * @example - * - * var stooges = [ - * { 'name': 'moe' }, - * { 'name': 'larry' } - * ]; - * - * var ages = [ - * { 'age': 40 }, - * { 'age': 50 } - * ]; - * - * _.merge(stooges, ages); - * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] - */ - var merge = createIterator(extendIteratorOptions, { - 'args': 'object, source, indicator, stack', - 'top': - 'var destValue, found, isArr, stackLength, recursive = indicator == isPlainObject;\n' + - 'if (!recursive) stack = [];\n' + - 'for (var argsIndex = 1, argsLength = recursive ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n' + - ' if (iteratee = arguments[argsIndex]) {', - 'inLoop': - 'if (value && ((isArr = isArray(value)) || isPlainObject(value))) {\n' + - ' found = false; stackLength = stack.length;\n' + - ' while (stackLength--) {\n' + - ' if (found = stack[stackLength].source == value) break\n' + - ' }\n' + - ' if (found) {\n' + - ' result[index] = stack[stackLength].value\n' + - ' } else {\n' + - ' destValue = (destValue = result[index]) && isArr\n' + - ' ? (isArray(destValue) ? destValue : [])\n' + - ' : (isPlainObject(destValue) ? destValue : {});\n' + - ' stack.push({ value: destValue, source: value });\n' + - ' result[index] = callee(destValue, value, isPlainObject, stack)\n' + - ' }\n' + - '} else if (value != null) {\n' + - ' result[index] = value\n' + - '}' - }); - /** * Retrieves the value of a specified property from all elements in * the `collection`. diff --git a/test/test.js b/test/test.js index e8ae615ac..9798b2bf2 100644 --- a/test/test.js +++ b/test/test.js @@ -817,6 +817,50 @@ /*--------------------------------------------------------------------------*/ + _.each([ + 'isArguments', + 'isArray', + 'isBoolean', + 'isDate', + 'isElement', + 'isEmpty', + 'isEqual', + 'isFinite', + 'isFunction', + 'isNaN', + 'isNull', + 'isNumber', + 'isObject', + 'isRegExp', + 'isString', + 'isUndefined' + ], function(methodName) { + var func = _[methodName]; + QUnit.module('lodash.' + methodName + ' result'); + + test('should return a boolean', function() { + var expected = 'boolean'; + + equal(typeof func(arguments), expected); + equal(typeof func([]), expected); + equal(typeof func(true), expected); + equal(typeof func(false), expected); + equal(typeof func(new Date), expected); + equal(typeof func(window.document && document.body), expected); + equal(typeof func({}), expected); + equal(typeof func(undefined), expected); + equal(typeof func(Infinity), expected); + equal(typeof func(_), expected); + equal(typeof func(NaN), expected); + equal(typeof func(null), expected); + equal(typeof func(0), expected); + equal(typeof func({ 'a': 1 }), expected); + equal(typeof func('a'), expected); + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.keys'); (function() { @@ -1489,88 +1533,43 @@ /*--------------------------------------------------------------------------*/ - QUnit.module('lodash "Arrays" methods'); + QUnit.module('lodash methods'); (function() { - test('should allow a falsey `array` argument', function() { - _.each([ - 'compact', - 'difference', - 'first', - 'flatten', - 'groupBy', - 'indexOf', - 'initial', - 'intersection', - 'last', - 'lastIndexOf', - 'max', - 'min', - 'range', - 'rest', - 'shuffle', - 'sortBy', - 'sortedIndex', - 'union', - 'uniq', - 'without', - 'zip', - 'zipObject' - ], function(methodName) { + test('should allow a falsey arguments', function() { + var funcs = _.without.apply(_, [_.functions(_)].concat([ + '_iteratorTemplate', + '_shimKeys', + 'after', + 'bind', + 'bindAll', + 'compose', + 'debounce', + 'defer', + 'delay', + 'functions', + 'memoize', + 'once', + 'partial', + 'tap', + 'template', + 'throttle', + 'wrap' + ])); + + _.each(funcs, function(methodName) { var func = _[methodName], pass = true; _.each(falsey, function(value, index) { try { - index ? func() : func(value); + index ? func(value) : func(); } catch(e) { pass = false; } }); - ok(pass, methodName + ' allows a falsey `array` argument'); - }); - }); - }()); - - /*--------------------------------------------------------------------------*/ - - QUnit.module('lodash "Collections" methods'); - - (function() { - test('should allow a falsey `collection` argument', function() { - _.each([ - 'contains', - 'every', - 'filter', - 'find', - 'forEach', - 'invoke', - 'map', - 'pluck', - 'reduce', - 'reduceRight', - 'reject', - 'some', - 'toArray' - ], function(methodName) { - var func = _[methodName], - identity = _.identity, - pass = true; - - _.each(falsey, function(value, index) { - try { - if (/^(?:contains|toArray)$/.test(methodName)) { - index ? func() : func(value); - } else if (index) { - func(value, identity); - } - } catch(e) { - pass = false; - } - }); - - ok(pass, methodName + ' allows a falsey `collection` argument'); + ok(pass, methodName + ' allows a falsey arguments'); }); }); }());