Looking for another eye on making the following Javascript more efficient.
The following JSON is produced from a Resteasy service:
var testing = {
"com:klistret:cmdb:ci:pojo:successful":true,
"com:klistret:cmdb:ci:pojo:count":1,
"com:klistret:cmdb:ci:pojo:elements":{
"com:klistret:cmdb:ci:pojo:id":123,
"com:klistret:cmdb:ci:pojo:name":"Mars",
"com:klistret:cmdb:ci:pojo:type":{
"com:klistret:cmdb:ci:pojo:id":1,
"com:klistret:cmdb:ci:pojo:name":"Environment"
},
"com:klistret:cmdb:ci:pojo:configuration":{
"#www:w3:org:2001:XMLSchemainstance:type":"Environment",
"#Watermark":"past",
"com:klistret:cmdb:ci:commons:Name":"Mars"
}
}
};
Extended the Extjs JSONReader to handle key depths higher than 2 in the createAccessor method. Wondering if there is a way to make the code more efficient? The function below will be called like function(testing, "com:klistret:cmdb:ci:pojo:configuration.#Watermark") where the com:klistret:cmdb:ci:pojo:elements property is the root.
createAccessor : function(){
var re = /[\[\.]/;
return function(expr) {
if(Ext.isEmpty(expr)){
return Ext.emptyFn;
}
if(Ext.isFunction(expr)){
return expr;
}
# THIS FUNCTION I WANT TO BE EFFICIENT
return function(obj){
while (String(expr).search(re) !== -1) {
var i = String(expr).search(re);
var key = expr.substring(0, i);
if (obj.hasOwnProperty(key)) {
obj = obj[key];
}
expr = expr.substring(i+1, expr.length);
}
return obj[expr];
};
};
}()
This is what I use. I only allow dot annotation, mind:
Ext.override(Ext.data.JsonReader, {
createAccessor: function() {
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
} else if (Ext.isFunction(expr)) {
return expr;
} else {
return function(obj) {
var parts = (expr || '').split('.'),
result = obj,
part,
match;
while (parts.length > 0 && result) {
part = parts.shift();
match = part.match(/^(.+?)(\[(\d+)\])?$/);
result = result[match[1]];
if (result && match[3]) {
result = result[match[3]];
}
}
return result;
}
}
};
}()
});
A basic optimization would be to avoid scanning the string twice with search, which is pretty slow.
The best you could do is replace all the string scanning and substring extraction with a single call to expr.split('.'), which would support accessors of the form aaa.bbb.ccc.ddd and turn them into an array like ['aaa','bbb','ccc','ddd']. The other two characters you seem to support ([ and ]) wouldn't work.
Alternately, you could do an initial match for /[^\].[]+/g over your entire string and keep the matches to obtain a similar array, but this would possibly be slower than the previous solution.
Related
Here is my class Sample.
A Sample instance can:
have a number of tags such as Tag1, Tag2, etc.
be queried with method isTagged to find out whether it has been tagged or not tagged (ie. !Tag1)
function Sample(){
// [..]
this.tags = [];
// [..]
}
Sample.prototype.tag = function(tags){
// [..]
this.tags[tags] = true;
// [..]
};
// if an array is passed, isTagged will return true at the first match ie. not all need to match, just one
Sample.prototype.isTagged = function(tag){
if(tag){
if(Array.isArray(tag)){
let tLength = tag.length;
while(tLength--){
if(isTaggedSingleNoChecks(this, tag[tLength])){
return true;
}
}
return false;
}
else{
return isTaggedSingleNoChecks(this, tag);
}
}
return false;
};
function isTaggedSingleNoChecks(sample, tag){
const isNegated = tag.charAt(0) == "!";
if(isNegated){
tag = tag.replace(/^[!]/, "");
return sample.tags[tag]!==true;
}
else{
return sample.tags[tag]===true;
}
}
// showing usage
var sample = new Sample();
sample.tag('Tag1');
sample.tag('Tag2');
console.log(sample.isTagged('Tag1'));
console.log(sample.isTagged('Tag3'));
console.log(sample.isTagged('!Tag2'));
This all works great however my application recursively queries isTagged millions of times on thousands of instances of Sample, and my profiling is showing this to be a performance bottleneck.
Any suggestions on how to improve performance?
Before you start optimizing this, how about simplifying the code first and getting rid of the most obvious oddities (objects instead of Sets, useless regexes etc)
class Sample {
constructor() {
this.tags = new Set();
}
tag(...tags) {
for (let t of tags)
this.tags.add(t);
}
isTagged(...tags) {
return tags.some(t =>
(t[0] === '!')
? !this.tags.has(t.slice(1))
: this.tags.has(t)
)
}
}
If this is still too slow, then you have to resort to a global object-tag inverted index, for example:
class SetMap extends Map {
get(key) {
if (!this.has(key))
this.set(key, new Set)
return super.get(key)
}
}
let tagIndex = new SetMap()
class Sample {
tag(...tags) {
for (let t of tags) {
tagIndex.get(t).add(this)
}
}
isTagged(...tags) {
return tags.some(t => tagIndex.get(t).has(this))
}
}
Of course, some more work will be involved for untagging (tag removal) and, especially, proper serialization.
The index won't immediately speed up isTagged per se, but will greatly optimize queries "find objects that are tagged by X and/or Y".
I am looking for a way to effectively (as in: with as little ressources as possible) replace orrurances of strings by other strings in Javascript.
The focus is on computing time, more than memory consumption.
Search terms and replacements are given as a object used as dictionary
var replacements = {
search : 'replace',
another : 'replacement',
'and one' : 'more'
}
Currently I'm iterating over the keys and building a regexp (with set g flag) out of them, then look up every match in the dictionary and replace it:
String.prototype.mapReplace = function (map, replaceFullOnly = false) {
var regexp = [];
for (var key in map) {
regexp.push(RegExp.escape(key));
}
regexp = regexp.join('|');
if (replaceFullOnly) {
regexp = '\\b(?:' + regexp + ')\\b';
}
regexp = new RegExp(regexp, 'gi');
return this.replace(regexp, function (match) {
return map[match.toLowerCase()];
});
}
This works, however I need to compile a new regular expresison every time. My question is: can somebody come up with an effective way to cache the regular expresisons and, if the same map (same as in "same keys", neither "same object" nor "same values" nor "same order of keys") is given again, the regular expresiosn is re-used?
One obvious way would be sorting, serializing and hashing keys, use it as a key to store the regular expresison and re-use stored regular expressions if existant on future calls. However, I tihnk this will most likely require more time than compiling a new regular expresiosn every time...
Ideas/input?
Edit: RegExp.escape() is a function that escapes special characters in strings for use in regular expressions:
RegExp.escape= function(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
Usage information:
replacements are done a lot, as this i used in a chat system
changes to the replacement map are infrequent, however it depends on how chat operators use the feature. Automated scripts adding and removing replacement rules automatically and frequently are possible. However, changes to the replacement map will always be less frequent than applying the replacement map to strings.
one or multiple replacement maps might be in use simultaniously and independent of each other.
This is what I came up with:
var ReplacementMap = function (map, replaceFullOnly, ignoreCase) {
var regexp = null;
var update = function (search, replacement) {
if (!isDefined(replacement)) {
if (!(search in map)) return;
delete map[search];
} else {
if (map[search] == replacement) return;
map[search] = replacement;
}
invalidateRegexp();
}
var buildRegexp = function () {
if (regexp != null) return;
regexp = [];
for (var key in map) {
regexp.push(RegExp.escape(key));
}
regexp = regexp.join('|');
if (replaceFullOnly) {
regexp = '\\b(?:' + regexp + ')\\b';
}
regexp = new RegExp(regexp,'g' + (ignoreCase ? 'i' : ''));
}
var invalidateRegexp = function () {
regexp = null;
}
Object.defineProperties(this, {
fullOnly : {
set : value => {
if (replaceFullOnly == value) return;
replaceFullOnly = !!value;
invalidateRegexp();
},
get : () => replaceFullOnly
},
ignoreCase : {
set : value => {
if (ignoreCase == value) return;
ignoreCase = !!value;
invalidateRegexp();
},
get : () => ignoreCase
}
});
this.set = function set (search, replacement) {
if (Array.isArray(search)) {
if (Array.isArray(search[0])) {
search.forEach(function (search) {
set(search);
});
} else {
update(search[0], search.length > 1 ? search[1] : undefined);
}
} else if (search instanceof Object
&& search !== null
&& !String.isString(search)) {
for (key in search) {
update(key, search[key]);
}
} else update(search, replacement);
}
this.get = function (search) {
return search in map ? map[search] : undefined;
}
this.remove = function(search) {
update(search);
}
this.apply = function (string) {
buildRegexp();
return string.replace(regexp, function (match) {
return map[match.toLowerCase()];
});
}
this[Symbol.iterator] = function* () {
for (let key of Object.keys(map)) {
yield {[key] : map[key]};
}
return;
}
if (isDefined(replaceFullOnly)) {
replaceFullOnly = !!replaceFullOnly;
} else {
replaceFullOnly = true;
}
if (isDefined(ignoreCase)) {
ignoreCase = !!ignoreCase;
} else {
ignoreCase = true;
}
if (isDefined(map)) {
let entries = map;
map = Object.create(null);
this.set(entries);
} else {
map = Object.create(null);
}
}
Usage:
// ---- CREATE MAP ----
// Empty Map
var m0 = new ReplacementMap();
// Map initialized with one replacement: foo => bar
var m1_1 = new ReplacementMap('foo','bar');
var m1_2 = new ReplacementMap(['foo','bar']);
var m1_3 = new ReplacementMap({foo : 'bar'});
// Map initialized with two replacements: foo => bar, fooz => baz
var m2_1 = new ReplacementMap([['foo','bar'], ['fooz', 'baz']]);
var m2_2 = new ReplacementMap({foo : 'bar', fooz : 'baz'});
var m2_3 = new ReplacementMap([{foo : 'bar'}, {fooz : 'baz'}]);
// ---- ADD/MODIFY ENTRIES ----
var m0.set(...) // ... parameters work the same as in the constructor
// ---- REMOVE ENTRIES ----
var m2_1.delete('foo') // removes replacement rule for foo => bar
var m2_1.delete('test') // fails silently
// ---- READ ENTRIES ----
var m2_1.get('foo') // returns "bar"
var m2_1.get('test') // returns undefined;
for (rule of m2_1) {
alert(JSON.stringify(rule));
}
// alerts "{'foo':'bar'}" and "{'fooz':'baz'}"
// ---- APPLY ON STRING ----
alert(m2_1.apply("foo bar")) // bar bar
// change behaviour:
m2_1.fullOnly = true; // replace foo with bar, but not foobar with barbar
// default: true;
m2_1.ignoreCase = true; // ignore case. default: true
Let's say we have this JavaScript object:
var object = {
innerObject:{
deepObject:{
value:'Here am I'
}
}
};
How can we check if value property exists?
I can see only two ways:
First one:
if(object && object.innerObject && object.innerObject.deepObject && object.innerObject.deepObject.value) {
console.log('We found it!');
}
Second one:
if(object.hasOwnProperty('innerObject') && object.innerObject.hasOwnProperty('deepObject') && object.innerObject.deepObject.hasOwnProperty('value')) {
console.log('We found it too!');
}
But is there a way to do a deep check? Let's say, something like:
object['innerObject.deepObject.value']
or
object.hasOwnProperty('innerObject.deepObject.value')
There isn't a built-in way for this kind of check, but you can implement it easily. Create a function, pass a string representing the property path, split the path by ., and iterate over this path:
Object.prototype.hasOwnNestedProperty = function(propertyPath) {
if (!propertyPath)
return false;
var properties = propertyPath.split('.');
var obj = this;
for (var i = 0; i < properties.length; i++) {
var prop = properties[i];
if (!obj || !obj.hasOwnProperty(prop)) {
return false;
} else {
obj = obj[prop];
}
}
return true;
};
// Usage:
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
}
console.log(obj.hasOwnNestedProperty('innerObject.deepObject.value'));
You could make a recursive method to do this.
The method would iterate (recursively) on all 'object' properties of the object you pass in and return true as soon as it finds one that contains the property you pass in. If no object contains such property, it returns false.
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
};
function hasOwnDeepProperty(obj, prop) {
if (typeof obj === 'object' && obj !== null) { // only performs property checks on objects (taking care of the corner case for null as well)
if (obj.hasOwnProperty(prop)) { // if this object already contains the property, we are done
return true;
}
for (var p in obj) { // otherwise iterate on all the properties of this object.
if (obj.hasOwnProperty(p) && // and as soon as you find the property you are looking for, return true
hasOwnDeepProperty(obj[p], prop)) {
return true;
}
}
}
return false;
}
console.log(hasOwnDeepProperty(obj, 'value')); // true
console.log(hasOwnDeepProperty(obj, 'another')); // false
Alternative recursive function:
Loops over all object keys. For any key it checks if it is an object, and if so, calls itself recursively.
Otherwise, it returns an array with true, false, false for any key with the name propName.
The .reduce then rolls up the array through an or statement.
function deepCheck(obj,propName) {
if obj.hasOwnProperty(propName) { // Performance improvement (thanks to #nem's solution)
return true;
}
return Object.keys(obj) // Turns keys of object into array of strings
.map(prop => { // Loop over the array
if (typeof obj[prop] == 'object') { // If property is object,
return deepCheck(obj[prop],propName); // call recursively
} else {
return (prop == propName); // Return true or false
}
}) // The result is an array like [false, false, true, false]
.reduce(function(previousValue, currentValue, index, array) {
return previousValue || currentValue;
} // Do an 'or', or comparison of everything in the array.
// It returns true if at least one value is true.
)
}
deepCheck(object,'value'); // === true
PS: nem035's answer showed how it could be more performant: his solution breaks off at the first found 'value.'
My approach would be using try/catch blocks. Because I don't like to pass deep property paths in strings. I'm a lazy guy who likes autocompletion :)
JavaScript objects are evaluated on runtime. So if you return your object statement in a callback function, that statement is not going to be evaluated until callback function is invoked.
So this function just wraps the callback function inside a try catch statement. If it catches the exception returns false.
var obj = {
innerObject: {
deepObject: {
value: 'Here am I'
}
}
};
const validate = (cb) => {
try {
return cb();
} catch (e) {
return false;
}
}
if (validate(() => obj.innerObject.deepObject.value)) {
// Is going to work
}
if (validate(() => obj.x.y.z)) {
// Is not going to work
}
When it comes to performance, it's hard to say which approach is better.
On my tests if the object properties exist and the statement is successful I noticed using try/catch can be 2x 3x times faster than splitting string to keys and checking if keys exist in the object.
But if the property doesn't exist at some point, prototype approach returns the result almost 7x times faster.
See the test yourself: https://jsfiddle.net/yatki/382qoy13/2/
You can also check the library I wrote here: https://github.com/yatki/try-to-validate
I use try-catch:
var object = {
innerObject:{
deepObject:{
value:'Here am I'
}
}
};
var object2 = {
a: 10
}
let exist = false, exist2 = false;
try {
exist = !!object.innerObject.deepObject.value
exist2 = !!object2.innerObject.deepObject.value
}
catch(e) {
}
console.log(exist);
console.log(exist2);
Try this nice and easy solution:
public hasOwnDeepProperty(obj, path)
{
for (var i = 0, path = path.split('.'), len = path.length; i < len; i++)
{
obj = obj[path[i]];
if (!obj) return false;
};
return true;
}
In case you are writing JavaScript for Node.js, then there is an assert module with a 'deepEqual' method:
const assert = require('assert');
assert.deepEqual(testedObject, {
innerObject:{
deepObject:{
value:'Here am I'
}
}
});
I have created a very simple function for this using the recursive and happy flow coding strategy. It is also nice to add it to the Object.prototype (with enumerate:false!!) in order to have it available for all objects.
function objectHasOwnNestedProperty(obj, keys)
{
if (!obj || typeof obj !== 'object')
{
return false;
}
if(typeof keys === 'string')
{
keys = keys.split('.');
}
if(!Array.isArray(keys))
{
return false;
}
if(keys.length == 0)
{
return Object.keys(obj).length > 0;
}
var first_key = keys.shift();
if(!obj.hasOwnProperty(first_key))
{
return false;
}
if(keys.length == 0)
{
return true;
}
return objectHasOwnNestedProperty(obj[first_key],keys);
}
Object.defineProperty(Object.prototype, 'hasOwnNestedProperty',
{
value: function () { return objectHasOwnNestedProperty(this, ...arguments); },
enumerable: false
});
The basic idea is to check if it starts with an underscore and if there is split the string and return whatever comes after the underscore. This function will be run many times, but for different strings, it is unlikely i will need to retrieve the information more than once for each stirng.
A simple function which will return an object with the data I need:
var parseElementName = function(i) {
var sliced = [i.slice(0, 1), i.slice(1, i.length)];
var obj = {
isClass: null,
name: ''
}
if(sliced[0] === '_') {
obj.name = sliced[1];
obj.isClass = true;
} else {
obj.name = i;
obj.isClass = false;
}
return obj
}
Called with parseElementName(i);
Object with prototyped function
var parsedElement = function(i) {
this.className =
this.isClass = null;
if(this.setElementName(i))
return true
}
parsedElement.prototype.setElementName = function(i) {
var sliced = [i.slice(0, 1), i.slice(1, i.length)];
if(sliced[0] === '_') {
this.className = sliced[1];
this.isClass = true
} else {
this.className = i;
this.isClass = false
}
}
Called with var parsed_element = new parsedElement();
then parsed_element.className or parsedElement.isClass
Which approach is recommended?
I like the object prototype approach best, but I have a few notes about your code:
Use semicolons at the end of each line
Class names should be capitalized. So it should be ParsedElement
I wouldn't call it className, because it is confusing when it is not a class, I would rename it name
The two ways have different outcomes - that constructor+prototype approach will yield an instance which has a setElementName method. Will you ever need this to change the fields of an existing object? It's a simple parser function, so I would assume no. In that case, you should go with returning the object literal:
function parseElementName(i) {
var isClass = i.charAt(0) == '_';
return {
isClass: isClass,
name = isClass ? i.slice(1) : i
};
}
If you really need that method later, consider #MaxMeier's and #HMR's points.
Sorry for the title but I don't know how to explain it.
The function takes an URI, eg: /foo/bar/1293. The object will, in case it exists, be stored in an object looking like {foo: { bar: { 1293: 'content...' }}}. The function iterates through the directories in the URI and checks that the path isn't undefined and meanwhile builds up a string with the code that later on gets called using eval(). The string containing the code will look something like delete memory["foo"]["bar"]["1293"]
Is there any other way I can accomplish this? Maybe store the saved content in something other than
an ordinary object?
remove : function(uri) {
if(uri == '/') {
this.flush();
return true;
}
else {
var parts = trimSlashes(uri).split('/'),
memRef = memory,
found = true,
evalCode = 'delete memory';
parts.forEach(function(dir, i) {
if( memRef[dir] !== undefined ) {
memRef = memRef[dir];
evalCode += '["'+dir+'"]';
}
else {
found = false;
return false;
}
if(i == (parts.length - 1)) {
try {
eval( evalCode );
} catch(e) {
console.log(e);
found = false;
}
}
});
return found;
}
}
No need for eval here. Just drill down like you are and delete the property at the end:
parts.forEach(function(dir, i) {
if( memRef[dir] !== undefined ) {
if(i == (parts.length - 1)) {
// delete it on the last iteration
delete memRef[dir];
} else {
// drill down
memRef = memRef[dir];
}
} else {
found = false;
return false;
}
});
You just need a helper function which takes a Array and a object and does:
function delete_helper(obj, path) {
for(var i = 0, l=path.length-1; i<l; i++) {
obj = obj[path[i]];
}
delete obj[path.length-1];
}
and instead of building up a code string, append the names to a Array and then call this instead of the eval. This code assumes that the checks to whether the path exists have already been done as they would be in that usage.