util: improve typed array formatting
Pretty-print typed arrays like regular arrays. Speeds up formatting by almost 300% because it no longer stringifies the array indices. Pretty-print ArrayBuffer and DataView as well by including byteLength, byteOffset and buffer properties in the stringified representation. PR-URL: https://github.com/nodejs/node/pull/3793 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
This commit is contained in:
parent
8b57b31299
commit
34a35919e1
77
lib/util.js
77
lib/util.js
@ -284,10 +284,17 @@ function formatValue(ctx, value, recurseTimes) {
|
|||||||
formatted = formatPrimitiveNoColor(ctx, raw);
|
formatted = formatPrimitiveNoColor(ctx, raw);
|
||||||
return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
|
return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
|
||||||
}
|
}
|
||||||
|
// Fast path for ArrayBuffer. Can't do the same for DataView because it
|
||||||
|
// has a non-primitive .buffer property that we need to recurse for.
|
||||||
|
if (value instanceof ArrayBuffer) {
|
||||||
|
return `${getConstructorOf(value).name}` +
|
||||||
|
` { byteLength: ${formatNumber(ctx, value.byteLength)} }`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var constructor = getConstructorOf(value);
|
var constructor = getConstructorOf(value);
|
||||||
var base = '', empty = false, braces, formatter;
|
var base = '', empty = false, braces;
|
||||||
|
var formatter = formatObject;
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// We can't use `constructor === Array` because this could
|
// We can't use `constructor === Array` because this could
|
||||||
@ -314,6 +321,28 @@ function formatValue(ctx, value, recurseTimes) {
|
|||||||
keys.unshift('size');
|
keys.unshift('size');
|
||||||
empty = value.size === 0;
|
empty = value.size === 0;
|
||||||
formatter = formatMap;
|
formatter = formatMap;
|
||||||
|
} else if (value instanceof ArrayBuffer) {
|
||||||
|
braces = ['{', '}'];
|
||||||
|
keys.unshift('byteLength');
|
||||||
|
visibleKeys.byteLength = true;
|
||||||
|
} else if (value instanceof DataView) {
|
||||||
|
braces = ['{', '}'];
|
||||||
|
// .buffer goes last, it's not a primitive like the others.
|
||||||
|
keys.unshift('byteLength', 'byteOffset', 'buffer');
|
||||||
|
visibleKeys.byteLength = true;
|
||||||
|
visibleKeys.byteOffset = true;
|
||||||
|
visibleKeys.buffer = true;
|
||||||
|
} else if (isTypedArray(value)) {
|
||||||
|
braces = ['[', ']'];
|
||||||
|
formatter = formatTypedArray;
|
||||||
|
if (ctx.showHidden) {
|
||||||
|
// .buffer goes last, it's not a primitive like the others.
|
||||||
|
keys.unshift('BYTES_PER_ELEMENT',
|
||||||
|
'length',
|
||||||
|
'byteLength',
|
||||||
|
'byteOffset',
|
||||||
|
'buffer');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only create a mirror if the object superficially looks like a Promise.
|
// Only create a mirror if the object superficially looks like a Promise.
|
||||||
var promiseInternals = value instanceof Promise && inspectPromise(value);
|
var promiseInternals = value instanceof Promise && inspectPromise(value);
|
||||||
@ -336,7 +365,6 @@ function formatValue(ctx, value, recurseTimes) {
|
|||||||
constructor = null;
|
constructor = null;
|
||||||
braces = ['{', '}'];
|
braces = ['{', '}'];
|
||||||
empty = true; // No other data than keys.
|
empty = true; // No other data than keys.
|
||||||
formatter = formatObject;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,6 +436,15 @@ function formatValue(ctx, value, recurseTimes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatNumber(ctx, value) {
|
||||||
|
// Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
|
||||||
|
// so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
|
||||||
|
if (value === 0 && 1 / value < 0)
|
||||||
|
return ctx.stylize('-0', 'number');
|
||||||
|
return ctx.stylize('' + value, 'number');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function formatPrimitive(ctx, value) {
|
function formatPrimitive(ctx, value) {
|
||||||
if (value === undefined)
|
if (value === undefined)
|
||||||
return ctx.stylize('undefined', 'undefined');
|
return ctx.stylize('undefined', 'undefined');
|
||||||
@ -424,13 +461,8 @@ function formatPrimitive(ctx, value) {
|
|||||||
.replace(/\\"/g, '"') + '\'';
|
.replace(/\\"/g, '"') + '\'';
|
||||||
return ctx.stylize(simple, 'string');
|
return ctx.stylize(simple, 'string');
|
||||||
}
|
}
|
||||||
if (type === 'number') {
|
if (type === 'number')
|
||||||
// Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
|
return formatNumber(ctx, value);
|
||||||
// so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
|
|
||||||
if (value === 0 && 1 / value < 0)
|
|
||||||
return ctx.stylize('-0', 'number');
|
|
||||||
return ctx.stylize('' + value, 'number');
|
|
||||||
}
|
|
||||||
if (type === 'boolean')
|
if (type === 'boolean')
|
||||||
return ctx.stylize('' + value, 'boolean');
|
return ctx.stylize('' + value, 'boolean');
|
||||||
// es6 symbol primitive
|
// es6 symbol primitive
|
||||||
@ -480,6 +512,20 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatTypedArray(ctx, value, recurseTimes, visibleKeys, keys) {
|
||||||
|
var output = new Array(value.length);
|
||||||
|
for (var i = 0, l = value.length; i < l; ++i)
|
||||||
|
output[i] = formatNumber(ctx, value[i]);
|
||||||
|
for (const key of keys) {
|
||||||
|
if (typeof key === 'symbol' || !key.match(/^\d+$/)) {
|
||||||
|
output.push(
|
||||||
|
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function formatSet(ctx, value, recurseTimes, visibleKeys, keys) {
|
function formatSet(ctx, value, recurseTimes, visibleKeys, keys) {
|
||||||
var output = [];
|
var output = [];
|
||||||
value.forEach(function(v) {
|
value.forEach(function(v) {
|
||||||
@ -626,6 +672,19 @@ function reduceToSingleString(output, base, braces) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isTypedArray(value) {
|
||||||
|
return value instanceof Float32Array ||
|
||||||
|
value instanceof Float64Array ||
|
||||||
|
value instanceof Int16Array ||
|
||||||
|
value instanceof Int32Array ||
|
||||||
|
value instanceof Int8Array ||
|
||||||
|
value instanceof Uint16Array ||
|
||||||
|
value instanceof Uint32Array ||
|
||||||
|
value instanceof Uint8Array ||
|
||||||
|
value instanceof Uint8ClampedArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// NOTE: These type checking functions intentionally don't use `instanceof`
|
// NOTE: These type checking functions intentionally don't use `instanceof`
|
||||||
// because it is fragile and can be easily faked with `Object.create()`.
|
// because it is fragile and can be easily faked with `Object.create()`.
|
||||||
exports.isArray = Array.isArray;
|
exports.isArray = Array.isArray;
|
||||||
|
@ -41,6 +41,59 @@ assert.equal(util.inspect(Object.create({},
|
|||||||
'{ visible: 1 }'
|
'{ visible: 1 }'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const showHidden of [true, false]) {
|
||||||
|
const ab = new ArrayBuffer(4);
|
||||||
|
const dv = new DataView(ab, 1, 2);
|
||||||
|
assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }');
|
||||||
|
assert.equal(util.inspect(new DataView(ab, 1, 2), showHidden),
|
||||||
|
'DataView {\n' +
|
||||||
|
' byteLength: 2,\n' +
|
||||||
|
' byteOffset: 1,\n' +
|
||||||
|
' buffer: ArrayBuffer { byteLength: 4 } }');
|
||||||
|
assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }');
|
||||||
|
assert.equal(util.inspect(dv, showHidden),
|
||||||
|
'DataView {\n' +
|
||||||
|
' byteLength: 2,\n' +
|
||||||
|
' byteOffset: 1,\n' +
|
||||||
|
' buffer: ArrayBuffer { byteLength: 4 } }');
|
||||||
|
ab.x = 42;
|
||||||
|
dv.y = 1337;
|
||||||
|
assert.equal(util.inspect(ab, showHidden),
|
||||||
|
'ArrayBuffer { byteLength: 4, x: 42 }');
|
||||||
|
assert.equal(util.inspect(dv, showHidden),
|
||||||
|
'DataView {\n' +
|
||||||
|
' byteLength: 2,\n' +
|
||||||
|
' byteOffset: 1,\n' +
|
||||||
|
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
|
||||||
|
' y: 1337 }');
|
||||||
|
}
|
||||||
|
|
||||||
|
[ Float32Array,
|
||||||
|
Float64Array,
|
||||||
|
Int16Array,
|
||||||
|
Int32Array,
|
||||||
|
Int8Array,
|
||||||
|
Uint16Array,
|
||||||
|
Uint32Array,
|
||||||
|
Uint8Array,
|
||||||
|
Uint8ClampedArray ].forEach(constructor => {
|
||||||
|
const length = 2;
|
||||||
|
const byteLength = length * constructor.BYTES_PER_ELEMENT;
|
||||||
|
const array = new constructor(new ArrayBuffer(byteLength), 0, length);
|
||||||
|
array[0] = 65;
|
||||||
|
array[1] = 97;
|
||||||
|
assert.equal(util.inspect(array, true),
|
||||||
|
`${constructor.name} [\n` +
|
||||||
|
` 65,\n` +
|
||||||
|
` 97,\n` +
|
||||||
|
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
|
||||||
|
` [length]: ${length},\n` +
|
||||||
|
` [byteLength]: ${byteLength},\n` +
|
||||||
|
` [byteOffset]: 0,\n` +
|
||||||
|
` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`);
|
||||||
|
assert.equal(util.inspect(array, false), `${constructor.name} [ 65, 97 ]`);
|
||||||
|
});
|
||||||
|
|
||||||
// Due to the hash seed randomization it's not deterministic the order that
|
// Due to the hash seed randomization it's not deterministic the order that
|
||||||
// the following ways this hash is displayed.
|
// the following ways this hash is displayed.
|
||||||
// See http://codereview.chromium.org/9124004/
|
// See http://codereview.chromium.org/9124004/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user