Skip to content

Commit 4334c12

Browse files
authored
Merge pull request #2844 from jgonggrijp/typed_array_eq
2 parents 77dae23 + 792d5f8 commit 4334c12

File tree

4 files changed

+178
-21
lines changed

4 files changed

+178
-21
lines changed

modules/index.js

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ var push = ArrayProto.push,
2424
toString = ObjProto.toString,
2525
hasOwnProperty = ObjProto.hasOwnProperty;
2626

27-
// All **ECMAScript 5** native function implementations that we hope to use
27+
// Modern feature detection.
28+
var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined';
29+
30+
// All **ECMAScript 5+** native function implementations that we hope to use
2831
// are declared here.
2932
var nativeIsArray = Array.isArray,
3033
nativeKeys = Object.keys,
31-
nativeCreate = Object.create;
34+
nativeCreate = Object.create,
35+
nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;
3236

3337
// Create references to these builtin functions because we override them.
3438
var _isNaN = root.isNaN,
@@ -152,16 +156,26 @@ function deepGet(obj, path) {
152156
return length ? obj : void 0;
153157
}
154158

159+
// Common logic for isArrayLike and isBufferLike.
160+
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
161+
function createSizePropertyCheck(getSizeProperty) {
162+
return function(collection) {
163+
var sizeProperty = getSizeProperty(collection);
164+
return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX;
165+
}
166+
}
167+
155168
// Helper for collection methods to determine whether a collection
156169
// should be iterated as an array or as an object.
157170
// Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
158171
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
159-
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
160172
var getLength = shallowProperty('length');
161-
function isArrayLike(collection) {
162-
var length = getLength(collection);
163-
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
164-
}
173+
var isArrayLike = createSizePropertyCheck(getLength);
174+
175+
// Likewise to determine whether we should spend extensive checks against
176+
// `ArrayBuffer` et al.
177+
var getByteLength = shallowProperty('byteLength');
178+
var isBufferLike = createSizePropertyCheck(getByteLength);
165179

166180
// Collection Functions
167181
// --------------------
@@ -1206,10 +1220,11 @@ function deepEq(a, b, aStack, bStack) {
12061220
// Compare `[[Class]]` names.
12071221
var className = toString.call(a);
12081222
if (className !== toString.call(b)) return false;
1223+
12091224
switch (className) {
1210-
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
1225+
// These types are compared by value.
12111226
case '[object RegExp]':
1212-
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
1227+
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
12131228
case '[object String]':
12141229
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
12151230
// equivalent to `new String("5")`.
@@ -1228,6 +1243,25 @@ function deepEq(a, b, aStack, bStack) {
12281243
return +a === +b;
12291244
case '[object Symbol]':
12301245
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
1246+
case '[object ArrayBuffer]':
1247+
// Coerce to `DataView` so we can fall through to the next case.
1248+
return deepEq(new DataView(a), new DataView(b), aStack, bStack);
1249+
case '[object DataView]':
1250+
var byteLength = getByteLength(a);
1251+
if (byteLength !== getByteLength(b)) {
1252+
return false;
1253+
}
1254+
while (byteLength--) {
1255+
if (a.getUint8(byteLength) !== b.getUint8(byteLength)) {
1256+
return false;
1257+
}
1258+
}
1259+
return true;
1260+
}
1261+
1262+
if (isTypedArray(a)) {
1263+
// Coerce typed arrays to `DataView`.
1264+
return deepEq(new DataView(a.buffer), new DataView(b.buffer), aStack, bStack);
12311265
}
12321266

12331267
var areArrays = className === '[object Array]';
@@ -1325,7 +1359,7 @@ export function isObject(obj) {
13251359
return type === 'function' || type === 'object' && !!obj;
13261360
}
13271361

1328-
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
1362+
// Add some isType methods.
13291363
export var isArguments = tagTester('Arguments');
13301364
export var isFunction = tagTester('Function');
13311365
export var isString = tagTester('String');
@@ -1338,6 +1372,8 @@ export var isMap = tagTester('Map');
13381372
export var isWeakMap = tagTester('WeakMap');
13391373
export var isSet = tagTester('Set');
13401374
export var isWeakSet = tagTester('WeakSet');
1375+
export var isArrayBuffer = tagTester('ArrayBuffer');
1376+
export var isDataView = tagTester('DataView');
13411377

13421378
// Define a fallback version of the method in browsers (ahem, IE < 9), where
13431379
// there isn't any inspectable "Arguments" type.
@@ -1383,6 +1419,14 @@ export function isUndefined(obj) {
13831419
return obj === void 0;
13841420
}
13851421

1422+
// Is a given value a typed array?
1423+
var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;
1424+
export var isTypedArray = supportsArrayBuffer ? function(obj) {
1425+
// `ArrayBuffer.isView` is the most future-proof, so use it when available.
1426+
// Otherwise, fall back on the above regular expression.
1427+
return nativeIsView ? (nativeIsView(obj) && !isDataView(obj)) : isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
1428+
} : constant(false);
1429+
13861430
// Shortcut function for checking if an object has a given property directly
13871431
// on itself (in other words, not on a prototype).
13881432
export function has(obj, path) {

test/objects.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,36 @@
578578
var sameStringSymbol = Symbol('x');
579579
assert.strictEqual(_.isEqual(symbol, sameStringSymbol), false, 'Different symbols of same string are not equal');
580580
}
581+
582+
583+
584+
// typed arrays
585+
if (typeof ArrayBuffer !== 'undefined') {
586+
var u8 = new Uint8Array([1, 2]);
587+
var u8b = new Uint8Array([1, 2]);
588+
var i8 = new Int8Array([1, 2]);
589+
var u16 = new Uint16Array([1, 2]);
590+
var u16one = new Uint16Array([3]);
591+
592+
assert.ok(_.isEqual(u8, u8b), 'Identical typed array data are equal');
593+
assert.ok(_.isEqual(u8.buffer, u8b.buffer), 'Identical ArrayBuffers are equal');
594+
assert.ok(_.isEqual(new DataView(u8.buffer), new DataView(u8b.buffer)), 'Identical DataViews are equal');
595+
assert.ok(_.isEqual(new DataView(u8.buffer), new DataView(i8.buffer)), 'Identical DataViews of different typed arrays are equal');
596+
assert.ok(_.isEqual(u8.buffer, i8.buffer), 'Identical ArrayBuffers of different typed arrays are equal');
597+
598+
assert.notOk(_.isEqual({a: 1, buffer: u8.buffer}, {a: 2, buffer: u8b.buffer}), 'Unequal objects with similar buffer properties are not equals');
599+
600+
assert.notOk(_.isEqual(u8, i8), 'Different types of typed arrays with the same byte data are not equal');
601+
assert.notOk(_.isEqual(u8, u16), 'Typed arrays with different types and different byte length are not equal');
602+
assert.notOk(_.isEqual(u8, u16one), 'Typed arrays with different types, same byte length but different byte data are not equal');
603+
assert.notOk(_.isEqual(new DataView(u8.buffer), new DataView(u16.buffer)), 'Different DataViews with different length are not equal');
604+
assert.notOk(_.isEqual(new DataView(u8.buffer), new DataView(u16one.buffer)), 'Different DataViews with different byte data are not equal');
605+
assert.notOk(_.isEqual(u8.buffer, u16.buffer), 'Different ArrayBuffers with different length are not equal');
606+
assert.notOk(_.isEqual(u8.buffer, u16one.buffer), 'Different ArrayBuffers with different byte data are not equal');
607+
}
608+
609+
//assert.ok(_.isEqual(new DataView(u8.buffer)), new DataView(u8b.buffer))
610+
//assert.notOk(_.isEqual(new DataView((new Uint8Array([1,2])).buffer), new DataView((new Uint8Array([5,6,10])).buffer));
581611
});
582612

583613
QUnit.test('isEmpty', function(assert) {
@@ -881,6 +911,42 @@
881911
assert.ok(_.isError(new URIError()), 'URIErrors are Errors');
882912
});
883913

914+
if (typeof ArrayBuffer != 'undefined') {
915+
QUnit.test('isArrayBuffer, isDataView and isTypedArray', function(assert) {
916+
var buffer = new ArrayBuffer(16);
917+
var checkValues = {
918+
'null': null,
919+
'a string': '',
920+
'an array': [],
921+
'an ArrayBuffer': buffer,
922+
'a DataView': new DataView(buffer),
923+
'a TypedArray': new Uint8Array(buffer)
924+
};
925+
var types = ['an ArrayBuffer', 'a DataView', 'a TypedArray'];
926+
_.each(types, function(type) {
927+
var typeCheck = _['is' + type.split(' ')[1]];
928+
_.each(checkValues, function(value, description) {
929+
if (description === type) {
930+
assert.ok(typeCheck(value), description + ' is ' + type);
931+
} else {
932+
assert.ok(!typeCheck(value), description + ' is not ' + type);
933+
}
934+
});
935+
});
936+
});
937+
938+
QUnit.test('isTypedArray', function(assert) {
939+
var buffer = new ArrayBuffer(16);
940+
_.each([Uint8ClampedArray, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array], function(ctor) {
941+
assert.ok(_.isTypedArray(new ctor(buffer)), ctor.name + ' is a typed array');
942+
});
943+
944+
if (typeof BigInt64Array != 'undefined') {
945+
assert.ok(_.isTypedArray(new BigInt64Array(buffer)), 'BigInt64Array is a typed array');
946+
}
947+
});
948+
}
949+
884950
QUnit.test('tap', function(assert) {
885951
var intercepted = null;
886952
var interceptor = function(obj) { intercepted = obj; };

underscore.js

Lines changed: 57 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

underscore.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)