How are the attribute names on objects stored in Javascript? - javascript

I was always under the assumption that the keys of an object were stored as strings, and that any non-string value would be cast. So, it was under this assumption, while writing some code that had to store a small value for many thousands of keys, I converted all the keys to base 36:
// theKey is an integer
myMap[theKey.toString(36)] = theValue;
Then, I decided to see whether my assumption was actually correct, and used Chrome's profiler to check the memory usage. Roughly here are the tests I ran and the memory usage:
window.objIntegers = {};
for (i = 100000; i--) window.objIntegers[i] = 'a';
// 786kb
window.objStrings = {};
for (i = 100000; i--) window.objStrings[i.toString(36)] = 'a';
// 16.7mb!
// and the same pattern but with:
key = i + .5; // 16.7mb
key = i + ''; // 786kb
key = '0' + i; // 16.7mb
key = i + '0'; // 16.7mb
Obviously, my assumptions were off. What I'm wondering though, is how they are being stored and whether this behaviour is standard, or just some extra trickery which has been added by the Chromium/WebKit team?

This is indeed some extra trickery by V8.
A JSObject (internal C++ representation of a JS Object) has two attributes, elements and properties, where the "elements" are JS attributes with numerical indices, while the "properties" are JS attributes with string indices.
Obviously, numerical indices consume far less memory here, since the property names need not be stored.
http://code.google.com/intl/de-DE/chrome/devtools/docs/memory-analysis-101.html#primitive_objects
A typical JavaScript object posesses two arrays: one for storing named properties, another for storing numeric elements.
This can be seen from v8 source code:
http://code.google.com/p/v8/source/browse/trunk/src/objects.h#1483
// [properties]: Backing storage for properties.
...
// [elements]: The elements (properties with names that are integers).
http://code.google.com/p/v8/source/browse/trunk/src/runtime.cc#4462
MaybeObject* Runtime::SetObjectProperty(Isolate* isolate,
Handle<Object> object,
Handle<Object> key,
Handle<Object> value,
PropertyAttributes attr,
StrictModeFlag strict_mode) {
...
// Check if the given key is an array index.
uint32_t index;
if (key->ToArrayIndex(&index)) {
// In Firefox/SpiderMonkey, Safari and Opera you can access the characters
// of a string using [] notation. We need to support this too in
// JavaScript.
// In the case of a String object we just need to redirect the assignment to
// the underlying string if the index is in range. Since the underlying
// string does nothing with the assignment then we can ignore such
// assignments.
if (js_object->IsStringObjectWithCharacterAt(index)) {
return *value;
}
Handle<Object> result = JSObject::SetElement(
js_object, index, value, attr, strict_mode, set_mode);
if (result.is_null()) return Failure::Exception();
return *value;
}
if (key->IsString()) {
Handle<Object> result;
if (Handle<String>::cast(key)->AsArrayIndex(&index)) {
result = JSObject::SetElement(
js_object, index, value, attr, strict_mode, set_mode);
} else {
Handle<String> key_string = Handle<String>::cast(key);
key_string->TryFlatten();
result = JSReceiver::SetProperty(
js_object, key_string, value, attr, strict_mode);
}
if (result.is_null()) return Failure::Exception();
return *value;
}
// Call-back into JavaScript to convert the key to a string.
bool has_pending_exception = false;
Handle<Object> converted = Execution::ToString(key, &has_pending_exception);
if (has_pending_exception) return Failure::Exception();
Handle<String> name = Handle<String>::cast(converted);
if (name->AsArrayIndex(&index)) {
return js_object->SetElement(
index, *value, attr, strict_mode, true, set_mode);
} else {
return js_object->SetProperty(*name, *value, attr, strict_mode);
}
}
I won't go into the details, but note that SetObjectProperty calls either SetElement or SetProperty, depending on the key. Not sure why the check fails in your test case key = i + '0' though.

It's optimizations in Chromium. I believe it has heuristics (here's one mention of it) to determine the most efficient way to store properties internally. All that the ECMAScript spec dictates is the interface between JavaScript and the environment and says nothing about how the objects exposed to JavaScript are implemented internally.

Related

Reading N-API object into C++ primitive

I've created a simple N-API module starting from the ObjectWrap boilerplate of generator-napi-module, and successfully passed data (an array containing objects with string, number and boolean properties) to JS. However, I'm unable to parse the properties of one of the same objects passed back to the native code; specifically, creating a uint32_t value from a property (a number) of the passed object.
Suppose an array of objects is created and passed to JS:
Napi::Value ObjectWrapAddon::GetSomeList(const Napi::CallbackInfo& info){
Napi::Env env = info.Env();
native_struct_one *data = NULL;
native_struct_two opts = { TRUE,FALSE,FALSE };
int retVal = native_lib_method(&data, &opts);
if(retVal!=OK) {
return Napi::Array::New(env); // return empty array
}
Napi::Array arr = Napi::Array::New(env);
uint32_t i = 0;
do {
Napi::Object tempObj = Napi::Object::New(env);
tempObj.Set("someProp", data->someVal);
arr[i] = tempObj;
i++;
data = data->next;
} while(data);
return arr;
}
Then one of those objects is passed to a native function call:
Napi::Value ObjectWrapAddon::OtherMethod(const Napi::CallbackInfo& info){
Napi::Env env = info.Env();
Napi::Object obj = info[0].As<Napi::Object>();
uint32_t temp = obj.Get("someProp").As<Napi::Number>();
return Napi::Number::New(env, temp);
}
This builds fine, but the above OtherMethod() gives an A number was expected error at uint32_t temp = obj.Get('someProp').As<Napi::Number>().
How would I create a native (C++) value from a JS object property value?
I missed two things, which allow this to work:
I was inconsistent with strings when using Get/Set. If Napi::Object::Set is used with single quotes, single quotes must be used with Napi::Object::Get; likewise for double quotes.
Uint32Value() method needs to be used per the docs (I must have removed this in my tinkering), giving: uint32_t temp = obj.Get("someProp").As<Napi::Number>().Uint32Value();
Fixing these issues provides the expected behavior and output.

Reduce javascript object to unique identifier

I have an object that I'm storing page settings in that looks something like this:
var filters={
"brands":["brand1","brand2","brand3"],
"family":"reds",
"palettes":["palette1","palette2","palette3"],
"color":"a1b2"
};
This object is constantly being changed as the user browses the page. I looking for some fast way in the code (maybe using a built in jquery or javascript function) to reduce the current settings object to a unique identifier I can reference without using a lot of loops. Maybe something like this:
"brandsbrand1brand2brand3familyredspalettespalette1palette2palette3colora1b2"
Doesn't have to necessarily convert the object to a long string like that, as long as it is something that will be unique to a particular group of settings. And I won't need to convert this identifier back into the object later.
EDITS:
I need to give some more information.
I'm looking to store the items of the results of the filters I'm doing inside a variable that's named the same as the unique ID. So, var uniqueID1 is from the settings object that has brand1 and brand2, and contains ["filteredObject1_1","filteredObject1_2"...,"filteredObject1_500"], and var uniqueID2 is from the settings object that has brand3 and brand4, and contains ["filteredObject2_1","filteredObject2_2"...,"filteredObject2_500"]. What I'm looking to do is avoid doing really really slow filtering code more than once on a bunch of items by storing results of the filtering in unique variables.
So:
Convert settings to unique id and see if that if that variable exists.
If variable exists, just get that variable that has the already filtered items.
If variable doesn't exist, do the really slow filtering on hundreds of items and store these items in unique id variable.
Hopefully I just didn't make this more confusing. I feel like I probably made it more confusing.
You can use JSON, which is a method of stringifying objects that was designed for JavaScript.
var filters={
"brands":["brand1","brand2","brand3"],
"family":"reds",
"palettes":["palette1","palette2","palette3"],
"color":"a1b2"
};
var uniqueId = JSON.stringify(filters);
uniqueId equals the following string:
{"brands":["brand1","brand2","brand3"],"family":"reds","palettes":["palette1","palette2","palette3"],"color":"a1b2"}
This has the added benefit of being able to be turned back into an object with JSON.parse(uniqueId).
Note that with JSON.stringify, two objects with have exactly the same values will be converted into the same unique id.
EDIT:
Please let me know if I interpreted your edit correctly. However, I think this is what you want to do.
//object that will act as a cache
var cached_filters = {}
//this assumes the existence of a get_filter function that processes the filters object
function get_cached_filter(filters) {
let uniqueId = JSON.stringify(filters);
//use already cached filters
if (cached_filters[uniqueId]) {
return cached_filters[uniqueId];
//create filter and cache it
} else {
cached_filters[uniqueId] = get_filter(filters);
return cached_filters[uniqueId];
}
}
This will store an object that has keys for each filter each time you call get_cached_filter. If get_cached_filter has already been called with the same exact filter, it will use it from the cache instead of recreating it; otherwise, it will create it and save it in the cache.
You could iterate the filter object and filter with Array#filter the data.
data.filter(function (o) {
return Object.keys(filters).every(function (k) {
return Array.isArray(filters[k])
? filters[k].some(function (f) { return o[k] === f; })
: o[k] === filters[k];
});
});
If you won't need to convert this identifier back into the object later, Here you can use this simple hashing function:
function UniqueHashCode(obj){
var str = JSON.stringify(obj)
var hash = 0;
if (str.length == 0) return hash;
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
function UniqueHashCode(obj){
var str = JSON.stringify(obj)
var hash = 0;
if (str.length == 0) return hash;
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
var filters={
"brands":["brand1","brand2","brand3"],
"family":"reds",
"palettes":["palette1","palette2","palette3"],
"color":"a1b2"
};
alert(UniqueHashCode(filters));
This function create a simple and very short integer (for example 661801383) by given object.
I hope to be helpful for you:)

Can't count number of instances of specific property in JSON

My JSON is being returned by an ajax request and is stored in the data variable.
The JSON looks like this (the top line is a postcode / zip code and is different for every request):
{
"ML1 4EQ":{
"ExchangeCode":"WSMOT",
"ExchangeName":"MOTHERWELL",
"Options":{
"10":{
"Preference":"3rd Party Tail (TTB)",
"Supplier 1":9591,
"Supplier 2":3581,
"Wholesale":5200,
"RRP":6500
},
and so on for other 9 more Options.
}
}
}
I am trying to count the number of Options being returned but everything I've tried from reading other questions doesn't seem to work from returning undefined, to it only returning 3 instead of 10 (think I was counting the wrong level).
These include
var key, results = 0;
for (var k in data) { // only simple cross browser way to get the first property
var obj = data[k];
for (key in obj) {
results++;
count = results;
}
return; // no need to go further, we have counted the options in the postcode object
}
This will count the total number of options inside the first object.
var count = 0;
for(var key in data){
for(var i in data[key].Options){
count++;
}
break;
}
Fiddle
Counting the number of properties in an Object isn't wholly straightforward. There isn't a property that will directly tell you, and if you for...in over them to count them you also get inherited properties, so if anyone's defined anything on Object.prototype you'll get the wrong answer.
In ECMAScript Fifth Edition you get getOwnPropertyNames which returns an array of non-inherited property names:
var options= {'1': 'a', '2': 'b'};
Object.getOwnPropertyNames(options).length; // 2
For browsers that don't support Fifth Edition yet (primarily IE<=8) you can shim it:
if (!('getOwnPropertyNames' in Object)) {
Object.getOwnPropertyNames= function(o) {
var names= [];
for (var k in o)
if (Object.hasOwnProperty(k))
names.push(k);
return names;
};
}
However, if you have control over the format of the JSON output, I would strongly suggest turning your Options Object into a plain Array, which would seems to model your data much better. With an Array you can simply use Options.length.

Checking for duplicate Javascript objects

TL;DR version: I want to avoid adding duplicate Javascript objects to an array of similar objects, some of which might be really big. What's the best approach?
I have an application where I'm loading large amounts of JSON data into a Javascript data structure. While it's a bit more complex than this, assume that I'm loading JSON into an array of Javascript objects from a server through a series of AJAX requests, something like:
var myObjects = [];
function processObject(o) {
myObjects.push(o);
}
for (var x=0; x<1000; x++) {
$.getJSON('/new_object.json', processObject);
}
To complicate matters, the JSON:
is in an unknown schema
is of arbitrary length (probably not enormous, but could be in the 100-200 kb range)
might contain duplicates across different requests
My initial thought is to have an additional object to store a hash of each object (via JSON.stringify?) and check against it on each load, like this:
var myHashMap = {};
function processObject(o) {
var hash = JSON.stringify(o);
// is it in the hashmap?
if (!(myHashMap[hash])) {
myObjects.push(o);
// set the hashmap key for future checks
myHashMap[hash] = true;
}
// else ignore this object
}
but I'm worried about having property names in myHashMap that might be 200 kb in length. So my questions are:
Is there a better approach for this problem than the hashmap idea?
If not, is there a better way to make a hash function for a JSON object of arbitrary length and schema than JSON.stringify?
What are the possible issues with super-long property names in an object?
I'd suggest you create an MD5 hash of the JSON.stringify(o) and store that in your hashmap with a reference to your stored object as the data for the hash. And to make sure that there are no object key order differences in the JSON.stringify(), you have to create a copy of the object that orders the keys.
Then, when each new object comes in, you check it against the hash map. If you find a match in the hash map, then you compare the incoming object with the actual object that you've stored to see if they are truly duplicates (since there can be MD5 hash collisions). That way, you have a manageable hash table (with only MD5 hashes in it).
Here's code to create a canonical string representation of an object (including nested objects or objects within arrays) that handles object keys that might be in a different order if you just called JSON.stringify().
// Code to do a canonical JSON.stringify() that puts object properties
// in a consistent order
// Does not allow circular references (child containing reference to parent)
JSON.stringifyCanonical = function(obj) {
// compatible with either browser or node.js
var Set = typeof window === "object" ? window.Set : global.Set;
// poor man's Set polyfill
if (typeof Set !== "function") {
Set = function(s) {
if (s) {
this.data = s.data.slice();
} else {
this.data = [];
}
};
Set.prototype = {
add: function(item) {
this.data.push(item);
},
has: function(item) {
return this.data.indexOf(item) !== -1;
}
};
}
function orderKeys(obj, parents) {
if (typeof obj !== "object") {
throw new Error("orderKeys() expects object type");
}
var set = new Set(parents);
if (set.has(obj)) {
throw new Error("circular object in stringifyCanonical()");
}
set.add(obj);
var tempObj, item, i;
if (Array.isArray(obj)) {
// no need to re-order an array
// but need to check it for embedded objects that need to be ordered
tempObj = [];
for (i = 0; i < obj.length; i++) {
item = obj[i];
if (typeof item === "object") {
tempObj[i] = orderKeys(item, set);
} else {
tempObj[i] = item;
}
}
} else {
tempObj = {};
// get keys, sort them and build new object
Object.keys(obj).sort().forEach(function(item) {
if (typeof obj[item] === "object") {
tempObj[item] = orderKeys(obj[item], set);
} else {
tempObj[item] = obj[item];
}
});
}
return tempObj;
}
return JSON.stringify(orderKeys(obj));
}
And, the algorithm
var myHashMap = {};
function processObject(o) {
var stringifiedCandidate = JSON.stringifyCanonical(o);
var hash = CreateMD5(stringifiedCandidate);
var list = [], found = false;
// is it in the hashmap?
if (!myHashMap[hash] {
// not in the hash table, so it's a unique object
myObjects.push(o);
list.push(myObjects.length - 1); // put a reference to the object with this hash value in the list
myHashMap[hash] = list; // store the list in the hash table for future comparisons
} else {
// the hash does exist in the hash table, check for an exact object match to see if it's really a duplicate
list = myHashMap[hash]; // get the list of other object indexes with this hash value
// loop through the list
for (var i = 0; i < list.length; i++) {
if (stringifiedCandidate === JSON.stringifyCanonical(myObjects[list[i]])) {
found = true; // found an exact object match
break;
}
}
// if not found, it's not an exact duplicate, even though there was a hash match
if (!found) {
myObjects.push(o);
myHashMap[hash].push(myObjects.length - 1);
}
}
}
Test case for jsonStringifyCanonical() is here: https://jsfiddle.net/jfriend00/zfrtpqcL/
Maybe. For example if You know what kind object goes by You could write better indexing and searching system than JS objects' keys. But You could only do that with JavaScript and object keys are written in C...
Must Your hashing be lossless or not? If can than try to lose compression (MD5). I guessing You will lose some speed and gain some memory. By the way, do JSON.stringify(o) guarantees same key ordering. Because {foo: 1, bar: 2} and {bar: 2, foo: 1} is equal as objects, but not as strings.
Cost memory
One possible optimization:
Instead of using getJSON use $.get and pass "text" as dataType param. Than You can use result as Your hash and convert to object afterwards.
Actually by writing last sentence I though about another solution:
Collect all results with $.get into array
Sort it with buildin (c speed) Array.sort
Now You can easily spot and remove duplicates with one for
Again different JSON strings can make same JavaScript object.

JavaScript: Get first and only property name of object

If I want to enumerate the properties of an object and want to ignore prototypes, I would use:
var instance = { ... };
for (var prop in instance) {
if (instance.hasOwnProperty(prop)) {
...
}
}
What if instance only has one property, and I want to get that property name? Is there an easier way than doing this:
var instance = { id: "foobar" };
var singleMember = (function() {
for (var prop in instance) {
if (instance.hasOwnProperty(prop)) {
return prop;
}
}
})();
Maybe Object.keys can work for you. If its length returns 1, you can use yourObject[Object.keys[0]] to get the only property of the object. The MDN-link also shows a custom function for use in environments without the keys method1. Code like this:
var obj = {foo:'bar'},
kyz = Object.keys(obj);
if (kyz.length === 1){
alert(obj[kyz[0]]); //=> 'bar'
} else {
/* loop through obj */
}
1 Some older browsers don't support Object.keys. The MDN link supplies code to to make it work in these browsers too. See header Compatibility in the aforementioned MDN page
Shortest form:
instance[Object.keys(instance)[0]];
ES6+ function:
let first = v => v[Object.keys(v)[0]];
Use the function:
first({a:'first', b:'second'}) // return 'first'
var foo = {bar: 1};
console.log(Object.keys(foo).toString());
which will print the string
"bar"
Though my answer is downvoted, it's still worth to know that there is no such thing as order of keys in javascript object. Therefore, in theory, any code build on iterating values can be inconsistent. One approach could be creating an object and to define setter which actually provides counting, ordering and so on, and provide some methods to access this fields. This could be done in modern browsers.
So, to answer you question, in general you approach is still most closs-browser. You can iterate using lodash or any other modern framework wich will hide "hasOwnProperty" complexity from you. As of August'15 Object.keys can be accepted as cross-browser and universal. After all IE8 happened years ago. Still there are some cases when you just don't wont store all set of keys in array. But I'd go with Object.keys - it's more flexible compared to iteration.
Unfortunately, there is no, "list properties" function built in, and there certainly isn't a "getFirstProperty" (especially since there is no guarantee that any property will consistently be "first").
I think you're better off writing a function like this one:
/**
* A means to get all of the keys of a JSON-style object.
* #param obj The object to iterate
* #param count maximum length of returned list (defaults to Infinity).
*/
function getProperties( obj, count )
{
if( isNaN( count ) ) count = Infinity
var keys = []
for( var it in obj )
{
if( keys.length > count ) break;
keys.push( it );
}
return keys;
}
Then, you could access the name though:
instance = {"foo":"bar"}
// String() on an array of < 2 length returns the first value as a string
// or "" if there are no values.
var prop = String(getProperties(instance, 1));
This is an old post, but I ended up writing the following helper function based on Object.keys().
It returns the key and value of the first property.
getFirstPropertyKeyAndValue(sourceObject) {
var result = null;
var ownProperties = Object.keys(sourceObject);
if (ownProperties.length > 0) {
if (ownProperties.length > 1) {
console.warn('Getting first property of an object containing more than 1 own property may result in unexpected results. Ordering is not ensured.', sourceObject);
}
var firstPropertyName = ownProperties[0];
result = {key: firstPropertyName, value: sourceObject[firstPropertyName]};
}
return result;
}
Answers in here all good, and with the caveat that the order may be unreliable (although in practice it seems the order the properties are set tends to stay that way), this quick and dirty method also works:
var obj = {foo: 1, bar: 2};
for(var key in obj) {
//you could use key here if you like
break;
}
//key now contains your first key
or a shorter version should also do it:
for(var key in obj) break;
//key now contains your first key

Categories

Resources