diff --git a/lodash.js b/lodash.js index 1da68ed09..02ebf5ac5 100644 --- a/lodash.js +++ b/lodash.js @@ -80,6 +80,7 @@ regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', + symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]'; var arrayBufferTag = '[object ArrayBuffer]', @@ -253,8 +254,9 @@ cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = - cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = - cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; + cloneableTags[symbolTag] = cloneableTags[uint8Tag] = + cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = + cloneableTags[uint32Tag] = true; cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false; @@ -1458,6 +1460,9 @@ var mapCtorString = Map ? fnToString.call(Map) : '', setCtorString = Set ? fnToString.call(Set) : ''; + /** Used to convert symbols to strings. */ + var symbolToString = Symbol ? Symbol.prototype.toString : undefined; + /** Used to lookup unminified function names. */ var realNames = {}; @@ -2772,7 +2777,10 @@ if (value === other) { return true; } - if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) { + if (value == null || other == null || ( + !(typeof value == 'symbol' || isObject(value)) && + !(typeof other == 'symbol' || isObjectLike(other)) + )) { return value !== value && other !== other; } return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack); @@ -3750,6 +3758,20 @@ return arrayReduce(setToArray(set), addSetEntry, new Ctor); } + /** + * Creates a clone of `symbol`. + * + * @private + * @param {Object} symbol The symbol to clone. + * @returns {Object} Returns the cloned symbol. + */ + function cloneSymbol(symbol) { + var Ctor = symbol.constructor, + result = Ctor(symbolToString.call(symbol).slice(7, -1)); + + return typeof symbol == 'object' ? Object(result) : result; + } + /** * Creates a clone of `typedArray`. * @@ -4519,6 +4541,9 @@ // Recursively compare objects (susceptible to call stack limits). return (isPartial || object.size == other.size) && equalFunc(convert(object), convert(other), customizer, bitmask | UNORDERED_COMPARE_FLAG); + + case symbolTag: + return symbolToString.call(object) == symbolToString.call(other); } return false; } @@ -4831,11 +4856,14 @@ case stringTag: return new Ctor(object); + case regexpTag: + return cloneRegExp(object); + case setTag: return cloneSet(object); - case regexpTag: - return cloneRegExp(object); + case symbolTag: + return cloneSymbol(object); } } diff --git a/test/test.js b/test/test.js index b56c77203..02e2089c7 100644 --- a/test/test.js +++ b/test/test.js @@ -2199,6 +2199,9 @@ set.add(1); set.add(2); } + if (Symbol) { + var symbol = Symbol('a'); + } var objects = { '`arguments` objects': arguments, 'arrays': ['a', ''], @@ -2217,6 +2220,7 @@ 'sets': set, 'strings': 'a', 'string objects': Object('a'), + 'symbols': symbol, 'undefined values': undefined }; @@ -2309,12 +2313,14 @@ QUnit.test('`_.' + methodName + '` should clone ' + key, function(assert) { assert.expect(2); - var isEqual = (key == 'maps' || key == 'sets') ? _.isEqual : lodashStable, + var useUnstable = /^(?:maps|sets|symbols)$/.test(key), + isEqual = useUnstable ? _.isEqual : lodashStable.isEqual, + isObject = useUnstable ? _.isObject : lodashStable.isObject, actual = func(object); assert.ok(isEqual(actual, object)); - if (lodashStable.isObject(object)) { + if (isObject(object)) { assert.notStrictEqual(actual, object); } else { assert.strictEqual(actual, object); @@ -2358,6 +2364,22 @@ assert.strictEqual(actual.lastIndex, 3); }); + QUnit.test('`_.' + methodName + '` should clone symbol objects', function(assert) { + assert.expect(3); + + if (Symbol) { + var object = Object(symbol), + actual = func(object); + + assert.strictEqual(typeof actual, 'object'); + assert.strictEqual(typeof actual.valueOf(), 'symbol'); + assert.notStrictEqual(actual, object); + } + else { + skipTest(assert, 3); + } + }); + QUnit.test('`_.' + methodName + '` should not error on DOM elements', function(assert) { assert.expect(1); @@ -8113,6 +8135,30 @@ } }); + QUnit.test('should compare symbols', function(assert) { + assert.expect(4); + + if (Symbol) { + var symbol1 = Symbol('a'), + symbol2 = Symbol('b'); + + assert.strictEqual(_.isEqual(symbol1, symbol2), false); + + symbol2 = Symbol('a'); + assert.strictEqual(_.isEqual(symbol1, symbol2), true); + + symbol1 = Symbol(undefined); + symbol2 = Symbol(''); + assert.strictEqual(_.isEqual(symbol1, symbol2), true); + + symbol1 = Symbol(null); + assert.strictEqual(_.isEqual(symbol1, symbol2), false); + } + else { + skipTest(assert, 4); + } + }); + QUnit.test('should compare typed arrays', function(assert) { assert.expect(1); @@ -9452,7 +9498,7 @@ var args = arguments; QUnit.test('should return `true` for objects', function(assert) { - assert.expect(12); + assert.expect(13); assert.strictEqual(_.isObject(args), true); assert.strictEqual(_.isObject([1, 2, 3]), true); @@ -9468,7 +9514,14 @@ if (document) { assert.strictEqual(_.isObject(body), true); - } else { + } + else { + skipTest(assert); + } + if (Symbol) { + assert.strictEqual(_.isObject(Object(Symbol())), true); + } + else { skipTest(assert); } });