Skip to content

lib: use Object.create(null) directly #11930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion benchmark/es/map-bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const common = require('../common.js');
const assert = require('assert');

const bench = common.createBenchmark(main, {
method: ['object', 'nullProtoObject', 'fakeMap', 'map'],
method: [
'object', 'nullProtoObject', 'nullProtoLiteralObject', 'storageObject',
'fakeMap', 'map'
],
millions: [1]
});

Expand Down Expand Up @@ -36,6 +39,37 @@ function runNullProtoObject(n) {
bench.end(n / 1e6);
}

function runNullProtoLiteralObject(n) {
const m = { __proto__: null };
var i = 0;
bench.start();
for (; i < n; i++) {
m['i' + i] = i;
m['s' + i] = String(i);
assert.strictEqual(String(m['i' + i]), m['s' + i]);
m['i' + i] = undefined;
m['s' + i] = undefined;
}
bench.end(n / 1e6);
}

function StorageObject() {}
StorageObject.prototype = Object.create(null);

function runStorageObject(n) {
const m = new StorageObject();
var i = 0;
bench.start();
for (; i < n; i++) {
m['i' + i] = i;
m['s' + i] = String(i);
assert.strictEqual(String(m['i' + i]), m['s' + i]);
m['i' + i] = undefined;
m['s' + i] = undefined;
}
bench.end(n / 1e6);
}

function fakeMap() {
const m = {};
return {
Expand Down Expand Up @@ -84,6 +118,12 @@ function main(conf) {
case 'nullProtoObject':
runNullProtoObject(n);
break;
case 'nullProtoLiteralObject':
runNullProtoLiteralObject(n);
break;
case 'storageObject':
runStorageObject(n);
break;
case 'fakeMap':
runFakeMap(n);
break;
Expand Down
5 changes: 2 additions & 3 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const common = require('_http_common');
const checkIsHttpToken = common._checkIsHttpToken;
const checkInvalidHeaderChar = common._checkInvalidHeaderChar;
const outHeadersKey = require('internal/http').outHeadersKey;
const StorageObject = require('internal/querystring').StorageObject;

const CRLF = common.CRLF;
const debug = common.debug;
Expand Down Expand Up @@ -143,7 +142,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
get: function() {
const headers = this[outHeadersKey];
if (headers) {
const out = new StorageObject();
const out = Object.create(null);
const keys = Object.keys(headers);
for (var i = 0; i < keys.length; ++i) {
const key = keys[i];
Expand Down Expand Up @@ -552,7 +551,7 @@ OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
// Returns a shallow copy of the current outgoing headers.
OutgoingMessage.prototype.getHeaders = function getHeaders() {
const headers = this[outHeadersKey];
const ret = new StorageObject();
const ret = Object.create(null);
if (headers) {
const keys = Object.keys(headers);
for (var i = 0; i < keys.length; ++i) {
Expand Down
20 changes: 7 additions & 13 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@

var domain;

// This constructor is used to store event handlers. Instantiating this is
// faster than explicitly calling `Object.create(null)` to get a "clean" empty
// object (tested with v8 v4.9).
function EventHandlers() {}
EventHandlers.prototype = Object.create(null);

function EventEmitter() {
EventEmitter.init.call(this);
}
Expand Down Expand Up @@ -71,7 +65,7 @@ EventEmitter.init = function() {
}

if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = new EventHandlers();
this._events = Object.create(null);
this._eventsCount = 0;
}

Expand Down Expand Up @@ -241,7 +235,7 @@ function _addListener(target, type, listener, prepend) {

events = target._events;
if (!events) {
events = target._events = new EventHandlers();
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
Expand Down Expand Up @@ -356,7 +350,7 @@ EventEmitter.prototype.removeListener =

if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = new EventHandlers();
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
Expand All @@ -379,7 +373,7 @@ EventEmitter.prototype.removeListener =
if (list.length === 1) {
list[0] = undefined;
if (--this._eventsCount === 0) {
this._events = new EventHandlers();
this._events = Object.create(null);
return this;
} else {
delete events[type];
Expand Down Expand Up @@ -408,11 +402,11 @@ EventEmitter.prototype.removeAllListeners =
// not listening for removeListener, no need to emit
if (!events.removeListener) {
if (arguments.length === 0) {
this._events = new EventHandlers();
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type]) {
if (--this._eventsCount === 0)
this._events = new EventHandlers();
this._events = Object.create(null);
else
delete events[type];
}
Expand All @@ -428,7 +422,7 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = new EventHandlers();
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
Expand Down
11 changes: 5 additions & 6 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const internalUtil = require('internal/util');
const assertEncoding = internalFS.assertEncoding;
const stringToFlags = internalFS.stringToFlags;
const getPathFromURL = internalURL.getPathFromURL;
const { StorageObject } = require('internal/querystring');

Object.defineProperty(exports, 'constants', {
configurable: false,
Expand Down Expand Up @@ -1560,7 +1559,7 @@ if (isWindows) {
nextPart = function nextPart(p, i) { return p.indexOf('/', i); };
}

const emptyObj = new StorageObject();
const emptyObj = Object.create(null);
fs.realpathSync = function realpathSync(p, options) {
if (!options)
options = emptyObj;
Expand All @@ -1580,8 +1579,8 @@ fs.realpathSync = function realpathSync(p, options) {
return maybeCachedResult;
}

const seenLinks = new StorageObject();
const knownHard = new StorageObject();
const seenLinks = Object.create(null);
const knownHard = Object.create(null);
const original = p;

// current character position in p
Expand Down Expand Up @@ -1700,8 +1699,8 @@ fs.realpath = function realpath(p, options, callback) {
return;
p = pathModule.resolve(p);

const seenLinks = new StorageObject();
const knownHard = new StorageObject();
const seenLinks = Object.create(null);
const knownHard = Object.create(null);

// current character position in p
var pos;
Expand Down
8 changes: 1 addition & 7 deletions lib/internal/querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,7 @@ const isHexTable = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // ... 256
];

// Instantiating this is faster than explicitly calling `Object.create(null)`
// to get a "clean" empty object (tested with v8 v4.9).
function StorageObject() {}
StorageObject.prototype = Object.create(null);

module.exports = {
hexTable,
isHexTable,
StorageObject
isHexTable
};
25 changes: 22 additions & 3 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
const util = require('util');
const {
hexTable,
isHexTable,
StorageObject
isHexTable
} = require('internal/querystring');
const binding = process.binding('url');
const context = Symbol('context');
Expand Down Expand Up @@ -97,6 +96,26 @@ class TupleOrigin {
}
}

// This class provides the internal state of a URL object. An instance of this
// class is stored in every URL object and is accessed internally by setters
// and getters. It roughly corresponds to the concept of a URL record in the
// URL Standard, with a few differences. It is also the object transported to
// the C++ binding.
// Refs: https://url.spec.whatwg.org/#concept-url
class URLContext {
constructor() {
this.flags = 0;
this.scheme = undefined;
this.username = undefined;
this.password = undefined;
this.host = undefined;
this.port = undefined;
this.path = [];
this.query = undefined;
this.fragment = undefined;
}
}

function onParseComplete(flags, protocol, username, password,
host, port, path, query, fragment) {
if (flags & binding.URL_FLAGS_FAILED)
Expand All @@ -121,7 +140,7 @@ function onParseComplete(flags, protocol, username, password,
// Reused by URL constructor and URL#href setter.
function parse(url, input, base) {
const base_context = base ? base[context] : undefined;
url[context] = new StorageObject();
url[context] = new URLContext();
binding.parse(input.trim(), -1,
base_context, undefined,
onParseComplete.bind(url));
Expand Down
3 changes: 1 addition & 2 deletions lib/querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

const { Buffer } = require('buffer');
const {
StorageObject,
hexTable,
isHexTable
} = require('internal/querystring');
Expand Down Expand Up @@ -281,7 +280,7 @@ const defEqCodes = [61]; // =

// Parse a key/val string.
function parse(qs, sep, eq, options) {
const obj = new StorageObject();
const obj = Object.create(null);

if (typeof qs !== 'string' || qs.length === 0) {
return obj;
Expand Down
6 changes: 3 additions & 3 deletions lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

const { toASCII } = process.binding('config').hasIntl ?
process.binding('icu') : require('punycode');
const { StorageObject, hexTable } = require('internal/querystring');
const { hexTable } = require('internal/querystring');
const internalUrl = require('internal/url');
exports.parse = urlParse;
exports.resolve = urlResolve;
Expand Down Expand Up @@ -197,7 +197,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
}
} else if (parseQueryString) {
this.search = '';
this.query = new StorageObject();
this.query = Object.create(null);
}
return this;
}
Expand Down Expand Up @@ -390,7 +390,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
} else if (parseQueryString) {
// no query string, but parseQueryString still requested
this.search = '';
this.query = new StorageObject();
this.query = Object.create(null);
}

var firstIdx = (questionIdx !== -1 &&
Expand Down