ES6 safe to use static class variable as key for Map? - javascript

In babel-preset-stage-0, we can declare static class variable as follow:
class Constants {
static COUNTRY = Object.freeze({
NAME: 'Germany',
DESCRIPTION: 'placeholder',
})
}
Is it safe to use Constants.COUNTRY as the key for a ES6 Map or Set?
eg.
const map = new Map();
map.add(Constants.COUNTRY, something);
Is it guaranteed that map.get(Constants.COUNTRY) will always return something?
Is the performance as good as using strings as key? Is it safe to use Constants.COUNTRY as eventKey (bootstrap component attribute) for NavItem too?
Is it also more appropriate to declare it as a variable instead of a class?
i.e.
const Constants = Object.freeze({
COUNTRY: Object.freeze({
NAME: 'Germany',
DESCRIPTION: 'placeholder',
})
})

Is it guaranteed that map.get(Constants.COUNTRY) will always return something?
For map.get(Constants.COUNTRY) to always return your original value, a couple things have to be true.
You have to make sure that Constants.COUNTRY can never be assigned a different value either because the .COUNTRY property was reassigned or because the Constants object was replaced with something else that had a different .COUNTRY property value.
You have to make sure that nobody can ever remove that key from the map object.
If you can assure these two things, then yes map.get(Constants.COUNTRY) will always return your desired value. But, if either of those are not necessarily true, then you are not assured of always getting your value from the map.
You can assure that Constants.COUNTRY cannot be changed by freezing the Constants object or by setting that property to be configurable to it cannot be removed or written to. To assure that the Constants object cannot be replaced, it would be best for it to be const as in your second code block.
I don't know of a way to assure that nobody can call map.delete(Constants.COUNTRY) except by keeping the map object private so foreign code cannot get to it.
If you had any reason to want to prevent enumeration of the keys in the map (to make it harder for someone to discover a key perhaps), then you could use a WeakMap instead of a Map.
Is the performance as good as using strings as key?
You'd have to test a specific Javascript implementation to be sure about performance. There is no required implementation reason that one or the other should be faster - it will just depend upon the internals of the implementation.
I created a jsPerf test case to compare string lookups to object lookups. Feedback is welcome on improving how this is tested/measured, but using the current scheme where I create 10,000 string keys and 10,000 object keys in a map and then compare accessing 1000 of each, I find varying results.
Chrome is ~20% slower to access the object keys.
Firefox is ~20% slower to access the string keys.
Edge is ~27% slower to access the string keys.
Is it also more appropriate to declare it as a variable instead of a class?
As discussed, your second const form has the advantage that Constants cannot be reassigned.

You can use WeakMap to COUNTRY as key, and something as value. A variable declared with const cannot be deleted. Along with you use of Object.freeze(), wm.get(COUNTRY) should always return something
const wm = new WeakMap;
const COUNTRY = Object.freeze({
NAME: 'Germany',
DESCRIPTION: 'placeholder',
});
wm.set(COUNTRY, "something");
// error when "use strict"
delete wm;
delete COUNTRY;
COUNTRY.NAME = 123;
console.log(
wm.get(COUNTRY)
);
console.log(
COUNTRY
);
console.log(
wm
);
If requirement is for a variable that cannot be deleted or changed you can use const see Is it possible to delete a variable declared using const? and JSON
"use strict";
// `Constants` cannot be changed or deleted
const Constants = `{
"NAME": "Germany",
"DESCRIPTION": "placeholder"
}`;
console.log(
JSON.parse(Constants)
);
// delete Constants;
/*
Error: {
"message": "Uncaught SyntaxError: Delete of an unqualified identifier in strict mode."
}
*/

Related

Destructuring assignment with rename and typing information

How do I destructure a variable into a new name while keeping typing information?
renderItem({item:'apple'})
// jsx:
function renderItem({item: region}) {
// region == 'apple'
return <div>{region}</div>;
}
The above will destructure an object with item and assign it to region.
How do I express typing information for this function signature?
Type the incoming item like so:
function renderItem({item: region}:{item:string}){}
Information about the typing of the feature is available in the TypeScript 2.1 documentation:
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html
Object rests are the dual of object spreads, in that they can extract any extra properties that don’t get picked up when destructuring an element:
When the Rest portion of the feature is used, it enhances object destructuring by enabling us to collect the rest of the properties into a new object comprised of them.
We can write the type annotation as we would for any other value. This is prefered as it can stop your function signatures getting verbose For example
interface IRenderItem {
item: String
}
function renderItem({ item: region }: IRenderItem): void {
console.log(item);
}

Sanitizing `eval` to prevent it from changing any values

This is front-end only, and not back-end. I also acknowledge that this is a bad idea. At this point I'm just curious.
I have a table of records. I would like the user to be able to enter a JavaScript conditional statement, which is then applied to the table to filter the records.
For example, to filter out records with a name that's less than 6 characters, I might enter:
record.name.length < 6
Without using an external library, the easiest way I've found to do this is with eval. However, in using eval, I of course introduce the risk of the user breaking the code (not a huge concern since this is front-end only, but still a user experience issue).
I would like to sanitize the user input so that it cannot change any values. So far, I believe I only need to do these two things to make eval "safe":
Turn any single equals signs = into double or triple equals signs
Remove or escape parentheses ( )
With these two items taken care of, is there anything else I need to do to prevent the user input from changing values?
One way of doing this which is safer than eval is using the Function constructor. As far as I know, this answer is totally safe, but it's quite possible there's some caveat I don't know or have forgotten, so everyone feel free to reply if I'm wrong.
The Function constructor allows you to construct a function from its string and a list of argument names. For example, the function
function(x, y) {
return x + y;
}
could be written as
new Function('x', 'y', 'return x + y;')
or simply
Function('x', 'y', 'return x + y;')
Note that although the function body has access to variables declared in the function definition, it cannot access variables from the local scope where the Function constructor was called; in this respect it is safer than eval.
The exception is global variables; these are accessible to the function body. Perhaps you want some of them to be accessible; for many of them, you probably don't. However, there is a way round this: declare the names of globals as arguments to the function, then call the function overriding them with fake values. For example, note that this expression returns the global Object:
(function() { return Object; })()
but this one returns 'not Object':
(function(Object) { return Object; })('not Object')
So, to create a function which does not have access to any of the globals, all you have to do is call the Function constructor on the javascript string, with arguments named after all the globals, then call the function with some innocuous value for all the globals.
Of course, there are variables (such as record) which you do want the javascript code to be able to access. The argument-name arguments to Function can be used for this too. I'll assume you have an object called myArguments which contains them, for example:
var myArguments = {
record: record
};
(Incidentally, don't call it arguments because that's a reserved word.) Now we need the list of names of arguments to the function. There are two kinds: arguments from myArguments, and globals we want to overwrite. Conveniently, in client-side javascript, all global variables are properties in a single object, window. I believe it's sufficient to use its own properties, without prototype properties.
var myArgumentNames = Object.keys(myArguments);
var globalNames = Object.keys(window);
var allArgumentNames = myArgumentNames.concat(globalNames);
Next we want the values of the arguments:
var myArgumentValues = myArgumentNames.map(function(key) {
return myArguments[key];
};
We don't need to do the values part for the globals; if we don't they'll just all be set to undefined. (Oh, and don't do Object.keys(myArguments).map(...), because there's a (small) chance that the array will come out in the wrong order, because Object.keys doesn't make any guarantees about the order of its return value. You have to use the same array, myArgumentNames.) Then call the Function constructor. Because of the large number of arguments to Function it's not practical to list them all explicitly, but we can get round this using the apply method on functions:
var myFn = Function.apply(null, allArgumentNames.concat([jsString]))
and now we just call this function with the argument list we've generated, again using the apply method. For this part, bear in mind that the jsString may contain references to this; we want to make sure this doesn't help the user to do something malicious. The value of this inside the script is the first argument to apply. Actually that's not quite true - if jsString doesn't use strict mode, then trying to set this to undefined or null will fail, and this will be the global object. You can get round this by forcing the script into strict mode (using '"use strict";\n' + jsString), or alternatively just set this to an empty object. Like this:
myFn.apply({}, myArgumentValues)
I am sharing my implementation (based on #David's answer).
Some of the keys of the Window object might break the Function.apply. This is why I've filtered the ones that break. Explanations in the code below as a comment.
// Why is windowKeys not inside function scope? No need. It won't
// be changing on each call. Creating array with +270 items for each eval
// might effect performance.
const windowKeys = Object.keys(window).filter((key) => {
// Why is window filtered?
// There are some cases that parameters given here might break the Function.apply.
// Eg. window keys as numbers: '0', (if there is iframe in the page)
// the ones that starts with numbers '0asdf',
// the ones that has dash and special characters etc.
try {
Function.apply(null, [key, "return;"]);
return true;
} catch (e) {
return false;
}
});
/**
* evaluates
* #param {string} code
* #param {object} context
* #returns
*/
const safeEval = (code, context) => {
const keys = Object.keys(context);
const allParams = keys.concat(windowKeys, [`"use strict"; return ${code}`]);
try {
const fn = Function.apply(null, allParams);
const params = keys.map((key) => context[key]);
return fn(...params);
} catch (e) {
console.log(e);
}
};
// simple expression evaluation
const res = safeEval("a + b", { a: 1, b: 2 });
console.log(res);
// try to access window
const res1 = safeEval("{a, b, window, document, this: this}", { a: 1, b: 2 });
console.log(res1);
Idk. if this approach can be exploited, if it does. I think another approach can be running eval on cross-domain iframe and get the result with window messages.

Symbol in Javascript

I saw following code in a project. can anyone explain what is going on here? What will be value of Attributes? What is happening this[Attributes] = attrs line?
const Attributes = Symbol('User#attrs');
class User {
constructor (attrs) {
this[Attributes] = attrs;
}
}
Symbol creates an un-collidable key for any object:
const first = Symbol('debug-name');
const second = Symbol('debug-name');
first !== second // true;
const anObj = {};
anObj[first] = 123;
anObj[second] = 456;
console.log(anObj) // {Symbol('debug-name'): 123, Symbol('debug-name'): 456}
Note that even though the first and second variables have the same debugging string they create different keys in anObj. Anyone who has access to first can add that key to any object and it will not collide with any other key in that object.
This can be used instead of magic strings to manage protocols:
// ES5
someObject.MY_LIB_attributes = [1, 2, 3];
// Only safe if no other library uses the key
// "MY_LIB_attributes"
// ES2015+
export const Attributes = Symbol('myLib#attributes');
import { Attributes } from 'my-lib';
someObj[Attributes] = [1, 2, 3];
// Safe as long as no other library uses
// *this* Symbol instance for some other purpose.
Edit
Since you've now clarified the question to be only about the line of code this[Attributes] = attrs, see the second part of my answer for discussion of that.
Original Answer
This is a couple of the new ES6 Javascript features.
const Attributes = Symbol('User#attrs'); creates a new Symbol object. The Symbol function and object is described here. It creates a unique identifier object that can then be used for many other uses, one of which is as a property name. There are many other references on the new Symbol feature so I won't repeat all of that here.
The class definition is the ES6 method for declaring prototyped classes. Again, there are many other references on this new syntax so there is no point in repeating all that here. There's an example below of what the equivalent ES5 code is.
This line this[Attributes] = attrs; uses the Symbol generated above to set a property on the newly created object.
The class definition is equivalent to the regular constructor
declaration like this:
function User(attrs) {
this[Attributes] = attrs;
}
Discussion of this[Attributes] = attrs
Attributes is a symbol which can be used as a property name on an object. It's a way of generating a unique key that can be used as a property name. So, this[Attributes] = attrs is setting a property on the newly constructed object and it is using the Attributes symbol as the property name. This Attributes symbol is a unique value that will not match any known string (in fact it won't even match other Symbol objects) so it's a way of making a unique property name.
It is unclear why the code does this:
this[Attributes] = attrs;
instead of just something like this:
this.attrs = attrs;
We would have to see a bit more context for how that is being used and why a plain string property could not also be used in place of the Symbol as you haven't provided enough context for us to know.
One possible use is for privacy. If Attributes is not public, then this is a way of creating a property on the object that the outside world doesn't know how to access because you have to have the current value of Attributes in order to access that properly. As you've shown the code with User and Attributes in the same scope that does not seem like it is private, but perhaps only User is exported to a public scope.
Another possible use is for uniqueness. If the User object may have lots of other properties added to it by other code, then Attributes creates a unique property name that cannot collide with other property names. This seems less likely in this case, but it is one possible use of Symbols.

Using JSON to create instances of an object

I'm trying to learn to program. I've gone through a list of tutorials sites online and I'm stuck on a thing that I think is extremely important for me to understand.
My questions:
(This is what I'd like to understand most) In my for...in loop, why is creating new "obj" objects using the "Contacts" constructor working? It seems like I would need a different name each time I loop so that I don't overwrite the object that I've created the pass before. If this is correct, how do I do this if I don't know anything about the number or value of contacts ahead of time? Additionally, why does the title of the any of the objects in the console logs not say obj? Am I confused about what it means to create an instance of an object? Are the names of these instances unimportant?
Why are all of the properties undefined? Should referencing properties from the temporary "i" variable work?
Creating objects from an unknown total of data entries seems really important. Unfortunately, places like Codecademy fall short here. You always manually create new instances of objects with hardcoded names they give you. But what would happen if there were two of the same name?
Thanks so much for any help I may get on this. Don't hold back from telling me anything else silly that I may be doing.
Here is a link to a console screenshot - http://i.imgur.com/TK4dtfV.png
var TestApp = {};
// my data... taken from wherever
TestApp.jsonContacts = {
contact1: {
name: "Ethan",
age: 24
},
contact2: {
name: "Evan",
age: 30
},
contact3: {
name: "Paul",
age: 9000
}
};
// I know this is silly, just let me pretend...
TestApp.jsonStrung = JSON.stringify(TestApp.jsonContacts);
TestApp.globalContactList = [];
// my constructor function to create instances of Contact
TestApp.Contact = function(name, age){
this.name = name;
this.age = age;
TestApp.globalContactList.push(this);
};
// where I'm taking data and creating new Contact objects
TestApp.instantiateObjects = function(){
// I know this is silly, just let me pretend...
var jsonUnstrung = JSON.parse(TestApp.jsonStrung);
// I think I'm looping through the first set of objects sitting in jsonContacts
for (var i in jsonUnstrung) {
var obj = new TestApp.Contact(i.name, i.age);
console.log(obj);
}
console.log(TestApp.globalContactList);
};
TestApp.instantiateObjects();
In my for...in loop, why is creating new "obj" objects using the "Contacts" constructor working?
A variable is just a holding place for a value. It is not the value itself. If you overwrite a variable with a new value, the previous value it held will continue to exist as long as something is holding a reference to it; it's simply that the variable won't be referring to it anymore.
It seems like I would need a different name each time I loop so that I don't overwrite the object that I've created the pass before.
No, you don't. One variable is fine, as long as you do something with the value before you assign the variable to the next value.
If this is correct, how do I do this if I don't know anything about the number or value of contacts ahead of time?
It doesn't matter how many you have.
Additionally, why does the title of the any of the objects in the console logs not say obj?
console.log() prints out the value that is passed to it. It doesn't care (doesn't know) anything about the variable that you pass to it.
Am I confused about what it means to create an instance of an object?
It seems so. Creating an instance of an object allocates some memory to store that object's values. A variable allows you to gain access to that allocated object.
Are the names of these instances unimportant?
Yes, object instances don't have names.
Why are all of the properties undefined?
i holds the property names of the object you are iterating through, so in this case, the strings "contact1", "contact2", "contact3". These strings don't have a name or age property, so your constructor is receiving two undefined values.
Should referencing properties from the temporary "i" variable work?
You need to use i as a property name to access the property values:
var obj = new TestApp.Contact(jsonUnstrung[i].name, jsonUnstrung[i].age);
In general, it's a good idea not to have side-effects like TestApp.globalContactList.push(this); in a constructor. There may be cases where doing so makes sense, but most of the time, removing that line and doing this would be preferable:
for (var i in jsonUnstrung) {
var contact = jsonUnstrung[i];
var obj = new TestApp.Contact(contact.name, contact.age);
console.log(obj);
TestApp.globalContactList.push(obj);
}

What are ECMAScript 6 WeakMaps?

After reading this description: http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps
I'm trying to get a hang of it, but I do not get the overall picture. What is it all about? It seems to be supported in Firefox 6: http://kangax.github.com/es5-compat-table/non-standard/
A weak reference is a special object containing an object-pointer, but does not keep that object alive.
One application of weak references are implemented in Weak Maps:
“The experienced JavaScript programmer will notice that this API could be implemented in JavaScript with two arrays (one for keys, one for values) shared by the 4 API methods. Such an implementation would have two main inconveniences. The first one is an O(n) search (n being the number of keys in the map). The second one is a memory leak issue. With manually written maps, the array of keys would keep references to key objects, preventing them from being garbage collected. In native WeakMaps, references to key objects are held “weakly”, which means that they do not prevent garbage collection in case there would be no other reference to the object.” Source
(See also my post when ECMAScript Harmony was first released with Firefox... )
WeakMap
WeakMaps basically allow you to have a HashTable with a key that isn't a String.
So you can set the key to be, i.e. [1] and then can say Map.get([1])
Example from the MDN:
var wm1 = new WeakMap(),
wm2 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because there is no value for o2 on wm2
wm2.get(o3); // undefined, because that is the set value
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (even if the value itself is 'undefined')
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
The reason for its existance is:
in order to fix a memory leak present in many uses of weak-key tables.
Apparently emulating weakmaps causes memory leaks. I don't know the details of those memory leaks.
WeakMap allows to use objects as keys.
It does not have any method to know the length of the map. The length is always 1.
The key can't be primitive values
A word of caution about using object as key is, since all the objects are by default singletons in JavaScript we should be creating an object reference and use it.
This is because when we create anonymous objects they are different.
if ( {} !== {} ) { console.log('Objects are singletons') };
// will print "Objects are singletons"
So in the following scenario, we can't expect to get the value
var wm = new WeakMap()
wm.set([1],'testVal');
wm.get([1]); // will be undefined
And the following snippet will work as expected.
var a = [1];
wm.set(a, 'testVal');
wm.get(a); // will return 'testVal'

Categories

Resources