Let's say I define a couple of classes. Then I create object of each class. I want to save the objects in a map keyed by their classes, so that I can retrieve the object based on its class.
(I'm using ES6 syntax, however the question may remain the same for legacy Javascript with class replaced by function)
// Alternative 1
class Apple {
}
class Banana {
}
let fruitInBag = {};
fruitInBag[Apple] = new Apple();
fruitInBag[Banana] = new Banana();
Alternatively I could also write following with the same outcome
// Alternative 2
class Apple {
}
class Banana {
}
const FRUIT_TYPE_APPLE = 1;
const FRUIT_TYPE_BANANA = 2;
let fruitInBag = {};
fruitInBag[FRUIT_TYPE_APPLE] = new Apple();
fruitInBag[FRUIT_TYPE_BANANA] = new Banana();
The second alternative is awkward, because I've to define and maintain the constants separate from class definitions. Therefore I would prefer the first one. But is the first approach inefficient? Is the Object smart enough to implement the first alternative efficiently?
The Keys in a Javascript object are always strings. No Integers and no functions (Classes). Use a ES6 Map for this!
I updated the code snippet in the question to correctly reflect the problem. After having done that and taking the suggestions from the comments and deleted answer, I did some analysis in Chrome devtools as follows.
As you can see from the picture, I defined a class in both ES6 (class Apple) and ES5 (function FnApple) manner. Then I kept them in regular ES5 object (mapA) and later ES6 map (mapB). In all the cases the browser stringifies the class definition completely and uses that string as a key. This works, because complete stringification of class makes it distinct. (I was afraid the stringification will the a dump [Object] string).
However this also means that using the class or function object as key will make the map key of indeterminately long size. In real world scenario when classes have long definitions, the key can go upto few KBs. Given this, it makes sense that one should use independently defined integers for keys in maps.
Update
In case of ES6 map, my test above is incorrect. I assigned the key-value to the mapB object using [] syntax, which makes it store the key-value as member of object like ES5 (because even ES6 Map instance is an object). The right way to set key-value on ES6 map is to use get(),set() API. After doing that I see that the type of keys is indeed function and not String for ES6 Map.
Related
We can create the object in JavaScript through three ways :-
1.) var obj = {name:'John', age:30, gender:'male'};//using object literal
2.) var a = new Test();// using constructor function
3.) var obj = Object.create(null);// using Object.create() method
But my question is, when we have to use object literal, constructor function and Object.create() method. And also the difference between these three.
Like in which case or which kind of requirement we can use one of these according to that requirement.
And give me some real project example.
The most basic and clearest way to make an object is with the object literal syntax.
However that is not always practical, say if you want to make many objects with the same property keys just with different values, it is a lot quicker and less verbose to use the constructor function.
And regarding Object.create(), the main usage for that is to inherit the prototype of another object and not made to create its own values (even though it is possible.)
I'm reading Eloquent JavaScript's "The Iterator Interface" section and I have trouble understanding why we need to define MatrixIterator function to iterate Matrix's contents. I usually use for inside of another for to iterate the contents of a two-dimensional matrix, similar to this. Is it because the [Symbol.iterator]'s of each object in JS is used whenever you call the shorthand of for loop which is for...of?
Furthermore, am I correct to assume that the standard [Symbol.iterator] cannot automatically iterate a two-dimensional objects and thus, the need to create MatrixIterator and assigning it as:
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
Are we allowed to do this instead: Matrix.prototype[Symbol.iterator] = MatrixIterator;?
Off-topic: I feel like I should know some other things more in-depth as I feel rather confused about these concepts. Could someone also elaborate what interface means? The book did mention it as:
Different pieces of such a program interact with each other through interfaces, limited sets of functions or bindings that provide useful functionality at a more abstract level, hiding their precise implementation.
and
Separating interface from implementation is a great idea. It is usually called encapsulation.
but it did not mention what implementation is.
the shorthand of for loop which is for...of?
No, for … of is not a shorthand for a normal for (…; …; …) loop. It's a completely separate mechanism.
Why do we need to define MatrixIterator function to iterate Matrix's contents?. Is it because the [Symbol.iterator]'s of each object in JS is used whenever you use for...of?
Yes. We define the MatrixIterator as it conforms to the iterator interface, as is expected to be returned by a Symbol.iterator method to be usable in such a loop.
Of course there are alternative ways to achieve this, we don't necessarily need to make an extra MatrixIterator class. A generator function is usually the easiest.
I usually use for inside of another for to iterate the contents of a two-dimensional matrix
Sure, but that's quite some syntactic overhead - two loops, two counters, double indentation. It could be much simpler, and we don't want to repeat this pattern everywhere when we want to iterate a matrix. The iterable interface allows this.
am I correct to assume that the standard [Symbol.iterator] cannot automatically iterate a two-dimensional objects
There is no standard Symbol.iterator method. Every type needs to define it itself. Array.prototype does, for example, but it works only on arrays not on our Matrix class.
Are we allowed to do this instead: Matrix.prototype[Symbol.iterator] = MatrixIterator;?
That doesn't work, as MatrixIterator a) is a constructor that needs to be invoked with new b) takes the matrix instance that it should iterate as an argument
I have a routine where I receive some data from an api. I'd like to store this data in an object, but after that i want to "lock" this object and not allow any change to the properties or their values after that point. Is that possible? (If possible using only ES5).
If you wish for an object to not be able to be modified you can use Object.freeze.
The Object.freeze() method freezes an object: that is, prevents new
properties from being added to it; prevents existing properties from
being removed; and prevents existing properties, or their
enumerability, configurability, or writability, from being changed, it
also prevents the prototype from being changed. The method returns
the object in a frozen state.
If you simply want to prevent a variable from being reassigned you can use const (ES6), however note that:
The const declaration creates a read-only reference to a value. It
does not mean the value it holds is immutable, just that the variable
identifier cannot be reassigned.
E.g, the following is perfectly valid
const a = { x: 7 }
a.x = 9
console.log(a.x) // 9
However, trying to reassign a variable declared with const will throw a TypeError:
const a = 5
a = 7
For Immutable object we can use below approches
Object.freeze()
To enforce object immutability while update the object make sure to
use Object.assign({},a,{foo:'bar'}) rather than a.foo='bar'
we can use spread(...) operator.
See example:
var person={name:'pavan',age:26}
var newPerson={...person,name:'raju'}
console.log(newPerson ===person) //false
console.log(person) //{name:'pavan',age:26}
console.log(newPerson) //{name:'raju',age:26}
You can use Object.freeze() method which freezes an object. A frozen object can no longer be changed and prevents :
New properties from being added to it
Existing properties from being removed
Changing the enumerability, configurability, or writability of existing properties
The values of existing properties from being changed.
Demo :
const obj = {
name: 'Alpha',
age: 30,
hobbies: ['reading', 'writing']
};
Object.freeze(obj);
obj.name = 'Beta';
obj.hobbies[0] = 'painting';
console.log(obj.name); // Alpha (It works fine with the properties which contains primitive values)
console.log(obj.hobbies[0]); // painting (It does not work for nested objects/array)
If you see in above code snippet, It works fine with the properties which contains primitive values but it is updating the values of nested object/array.
Hence, You can try as const If you are working with typescript in your requirement. It will freeze the whole object along with the nested properties with object/array.
No. There's no good way to enforce const-ness is ES5.
If you separate you code into pure and impure parts (as with functional core, imperative shell), you can get by in the pure parts using objects and arrays and not, as a matter of discipline, mutating them.
I've done this for years and have a library which aids this style of development. I can't imagine needing the insurance policy of having to Object.freeze everything. Now, eventually, when records and tuples is adopted into the spec you can just use them instead!
Ref: MDN Maps
Use maps over objects when keys are unknown until run time, and when
all keys are the same type and all values are the same type.
Use objects when there is logic that operates on individual elements.
Question:
What is an applicable example of using Maps over objects? in particular, "when would keys be unknown until runtime?"
var myMap = new Map();
var keyObj = {},
keyFunc = function () { return 'hey'},
keyString = "a string";
// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");
console.log(myMap.get(keyFunc));
What is an applicable example of using Maps over objects?
I think you've given one good example already: You at least need to use Maps when you are using objects (including Function objects) as keys.
in particular, "when would keys be unknown until runtime?"
Whenever they are not known at compile time. In short, you should always use a Map when you need a key-value collection. A good indicator that you need a collection is when you add and remove values dynamically from the collection, and especially when you don't know those values beforehand (e.g. they're read from a database, input by the user, etc).
In contrast, you should be using objects when you know which and how many properties the object has while writing the code - when their shape is static. As #Felix has put it: when you need a record. A good indicator for needing that is when the fields have different types, and when you never need to use bracket notation (or expect a limited set of property names in it).
I think that with ES2015's Map only two reasons are left to use plain objects:
You don't want to iterate over the properties of an object type at all
or you do but the property order doesn't matter and you can distinguish the program from the data level when iterating
When is the property order unimportant?
if you have only a single value and some functions that should be associated explicitly with it (like Promise - which is a proxy for a future value - and then/catch)
if you have a struct/record-like data structure with a static set of properties known at "compile time" (usually structs/records aren't iterable)
In all other cases you might consider using Map, because it preserves property order and separates the program (all properties assigned to the Map object) from the data level (all entries in the Map itself).
What are the drawbacks of Map?
you lose the concise object literal syntax
you need custom replacers for JSON.stringyfy
you lose destructuring, which is more useful with static data structures anyway
Use maps over objects when keys are unknown until run time, and when all keys are the same type and all values are the same type.
I have no idea why someone would write something so obviously wrong. I have to say, people are finding more and more wrong and/or questionable content on MDN these days.
Nothing in that sentence is correct. The main reason to use maps is when you want object-valued keys. The idea that the values should be the same type is absurd--although they might be, of course. The idea that one shouldn't use objects when keys are unknown until run time is equally absurd.
One of the difference between Map and Object is:
Map can use complex data type as its key. like this:
const fn = function() {}
const m = new Map([[document.body, 'stackoverflow'], [fn, 'redis']]);
m.get(document.body) // 'stackoverflow'
m.get(fn) //'redis'
watch out: For complex data type, If you want to get the value, you must pass the same reference as the key.
Object, it only accept simple data type(number, string) as its key.
const a = {};
a[document.body] = 'stackoverflow';
console.log(a) //{[object HTMLBodyElement]: "stackoverflow"}
Objects are similar to Maps in that both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. Because of this (and because there were no built-in alternatives), Objects have been used as Maps historically; however, there are important differences that make using a Map preferable in certain cases:
The keys of an Object are Strings and Symbols, whereas they can be
any value for a Map, including functions, objects, and any primitive.
The keys in Map are ordered while keys added to object are not. Thus,
when iterating over it, a Map object returns keys in order of
insertion.
You can get the size of a Map easily with the size property, while
the number of properties in an Object must be determined manually.
A Map is an iterable and can thus be directly iterated, whereas
iterating over an Object requires obtaining its keys in some fashion
and iterating over them.
An Object has a prototype, so there are default keys in the map that
could collide with your keys if you're not careful. As of ES5 this
can be bypassed by using map = Object.create(null), but this is
seldom done.
A Map may perform better in scenarios involving frequent addition and
removal of key pairs.
MDN
This question is a duplicate of but until it's closed, here's my answer from over there:
In addition to the other answers, I've found that Maps are more unwieldy and verbose to operate with than objects.
obj[key] += x
// vs.
map.set(map.get(key) + x)
This is important, because shorter code is faster to read, more directly expressive, and better kept in the programmer's head.
Another aspect: because set() returns the map, not the value, it's impossible to chain assignments.
foo = obj[key] = x; // Does what you expect
foo = map.set(key, x) // foo !== x; foo === map
Debugging maps is also more painful. Below, you can't actually see what keys are in the map. You'd have to write code to do that.
Objects can be evaluated by any IDE:
I'm the author of a graph datastructure library for JavaScript. I'm currently ES6-ifying the library.
I also want to make it more usable for ES6 programmers, which means implementing the iterable protocol. But a graph has multiple things to iterate over, and multiple ways to iterate over them (vertices, edges, successors, predecessors, vertices in topological order, etc.)
I have an idea of how to design this interface, but I'd like to follow existing conventions if they exist. Here's an example of how I might do the 'vertices' part:
class JsGraph {
// ...
get vertices() {
return {
_graph: this,
get length() { return this._graph._vertexCount },
*[Symbol.iterator]() {
var keys = Object.keys(this._graph._vertices);
for (let i = 0; i < keys.length; ++i) {
yield [keys[i], this._graph._vertices[keys[i]]];
}
}
};
}
// ...
}
If there are any existng convention I should probably be following (or any problems with this code), your feedback would be much appreciated.
You might get some inspiration from the Map and Set ES6 data structures. They do provide multiple methods to get different iterators: .values(), .keys(), and .entries(). Their ##iterator method defaults to entries or values respectively.
I don't know of any other existing conventions, I guess these will have to form yet.
any problems with this code
First, I'm not sure whether the getter vertices that returns such an object is a good idea. I'd rather do
get vertexCount() { … }
vertices*() { … }
but that'll basically come down to preference. Your current code is not as efficient because it creates two functions at every .vertices access, you could improve that using prototypes if you see the need (class VertexIterator {…}?).
Second, iterators on mutable data are a hazzle to implement, as it may change while being looped over. You should be aware of that and choose a strategy (making your structures immutable is probably out of question). For example, the MapIterator's next method is specified to "redetermine the number of elements each time it is evaluated.". Similarly, for object enumerations (for in) there is the rule that deleted properties must never appear (if not already enumerated before they were deleted), and of course no property may be enumerated twice. However, they explicitly are allowed to be inconsistent about keys that are added during the execution, no guarantees are given on whether those appear in the enumeration or not.