Performance
Whenever possible avoid strings. Why ?
JS strings are immutable
Passing a string to a function or assigning it to a variable requires the string to be copied[*1]. This adds a lot of processing overhead (memory processing, (assignment, and GC), and string iteration) which can be avoided.
[*1] See update at bottom of answer.
For example using your function
function process(keywords, categories) {
keywords.forEach(obj =>
categories.forEach(({name, options} =>
obj[name] = options.every(({method, regex}) =>
method === 'include' ? obj.URL.includes(regex) : !obj.URL.includes(regex))
)
);
}
The two inner loops create new strings for each iteration. name
in the outer loop categories.forEach((
and method
and regex
in the loop options.every(({
As these string are all stored as references in objects there is no need to copy the strings to new variables. Just use the references directly as follows...
function process(keywords, categories) {
keywords.forEach(obj =>
categories.forEach(cat =>
obj[cat.name] = cat.options.every(opt =>
opt.include ? obj.URL.includes(opt.regex) : !obj.URL.includes(opt.regex))
)
);
}
Avoid state strings
Using string
to store simple states is much slower than using simpler types like boolean
or number
.
For example you use the expression opt.method === "include"
to check the type of test to do on URL
. The negative (false
) for opt.method === "include"
(method = "exclude"
) is quicker as the compare fails on the first character "e" !== "i"
. However the match needs to iterate each of the 7 characters to find true
.
JS has no clue to help check the match (the strings include and exclude are the same length).
As there are only two states, include or exclude, you can use a boolean state.
Example the option objects can be
options: [
{ include: true, regex: 'facebook' }, // includes
{ include: false, regex: 'twitter' } // excludes
]
And then the inner test can be a constant (and fast) complexity
opt.include ? obj.URL.includes(opt.regex) : !obj.URL.includes(opt.regex))
If you can not create the boolean for categories
as stored (in JSON). Process the options once outside the function process
function optimzeCats(categories) {
categories.forEach(cat => cat.options.forEach(opt =>
opt.include = opt.method === 'include'
));
}
Having to do so will of course reduce the gain gained.
Reduce scope searches
Using node (V8) means that there is an additional overhead each (scope step) you need to use a variable outside the current scope.
In your code the outer loop keywords.forEach(obj
puts the variable obj
2 scope steps above its use in the inner loop. obj.URL.includes(
As (I assume) the number of keywords greatly outnumbers the number of cats changing the scope distance to obj
will give another worthwhile performance gain. This can be done by swapping the order of the first two outer loops.
function process(keywords, categories) {
categories.forEach(cat =>
keywords.forEach(obj =>
obj[cat.name] = cat.options.every(opt =>
opt.method === 'include' ? obj.URL.includes(opt.regex) : !obj.URL.includes(opt.regex))
)
);
}
Further optimizations
All of the above should give up to 15% performance gain and a unknown but worthwhile reduction in memory use. (Note only in regard to processing, as I don't know how you handle the JSON string)
There are likely many more optimizations however these will depend very much on what is being stored in both data structures and how the results are expected to be used.
Update
Correction...
After comments and then some research. Strings are not copied (duplicated) when assigned but rather a map reference (hash) to the unique string within the global context represents the string.