I am using Map
because I want to store an object as a key.
My question is - can I access a map the same way I would access a plain object?
For example:
let m = new Map();
let obj = {foo:'bar'};
m[obj] = 'baz';
console.log(m[obj]);
is this supposed to work correctly as is, or do I need to use the get/set methods of a Map?
The reason I ask is because if I need to use get/set it forces to me to carefully refactor a lot of code.
Here is a real life example of code that may need to be refactored:
// before (broker.wsLock was plain object)
function addWsLockKey(broker, ws, key) {
let v;
if (!( v = broker.wsLock[ws])) {
v = broker.wsLock[ws] = [];
}
if (v.indexOf(key) < 0) {
v.push(key);
}
}
// after (broker.wsLock is a Map instance)
function addWsLockKey(broker, ws, key) {
let v;
if (!( v = broker.wsLock.get(ws))) {
v = [];
broker.wsLock.set(ws, v);
}
if (v.indexOf(key) < 0) {
v.push(key);
}
}
is there some way to set v on the same line as the set() call?
If you want access to the actual values of the Map object, then you have to use .get() and .set() or various iterators.
var m = new Map();
m.set("test", "foo");
console.log(m.get("test")); // "foo"
Regular property access on a Map such as:
m["test"] = "foo"
just sets a regular property on the object - it does not affect the actual map data structure.
I imagine it was done this way so that you can access the Map object properties separately from the members of the Map data structure and the two shall not be confused with one another.
In addition, regular properties will only accept a string as the property name so you can't use a regular property to index an object. But, map objects have that capability when using .set() and .get().
You asked for a "definitive" answer to this. In the ES6 specification, you can look at what .set() does and see that it operates on the [[MapData]] internal slot which is certainly different than the properties of an object. And, likewise, there is no where in that spec where it says that using normal property access would access the internal object [[MapData]]. So, you'll have to just see that normal property access is describe for an Object. A Map is an Object and there's nothing in the Map specification that says that normal property access should act any different than it does for any other object. In fact, it has to act the same for all the methods on the Map object or they wouldn't work if you happened to put an item in the Map with the same key as a method name. So, you're proof consists of this:
A simple test will show you that property access does not put anything in the Map itself, only a regular property.
The spec describes a Map as an object.
The spec describes how .get() and .set() operate on the internal slot [[MapData]].
There's nothing in the spec that says property access on a Map object should work any different than it always does.
If property access did access the MapData, then you would not be able to access methods if you happened to put a key in the Map that conflicted with a method name - that would be a mess if that was the case.
Related
I am reading this Angular doc, it says:
With any object-like expression—such as object, Array, Map, or Set—the identity of the object must change for Angular to update the class list. Updating the property without changing object identity has no effect.
So, how do I change an object't identity?
Thanks.
The wording there is slightly unfortunate. You can't really change an object's identity - the identity is that object. It's like trying to make X not X. What it means is that a new object or array needs to be created for Angular to detect it as different. For a tiny example in vanilla JS:
const isSameObject = (obj1, obj2) => obj1 === obj2;
console.log(isSameObject({}, {})); // false, different objects
const someObj = {};
const sameReferenceToSomeObj = someObj;
sameReferenceToSomeObj.newProp = 'newVal';
console.log(isSameObject(someObj, sameReferenceToSomeObj)); // true, same object
For Angular to detect a change the identity on a property, the property value must be changed to a new value, rather than having the old value mutated. The second example in the snippet above is an example of how not to do things in Angular; mutating an object doesn't change its identity (it's still the same object), so if you did that in Angular, it would see that the old object on the property is === to the new object on the property, and would not produce a visual change as a result.
Create an entirely new object instead, with the desired properties, and put that object on the component, and then Angular will be able to see that the new object is not === to the old object, and take the appropriate action as a result, eg
this.theClassList = { ...this.theClassList, newProperty: 'foo' }
and not
const newClassList = this.theClassList;
newClassList.newProperty = 'foo';
this.theClassList = newClassList;
I was asked during an interview to implement a data structure of key/value pairs where keys can be an object, I know this is possible using ES6 maps but how do they work under the hood in Javascript where keys are strictly stringified and yet achieve the same constant lookup time of a hash table / object?
Thank you.
The keys of Map are not keys of the Map object. They are arguments to the .get and .set methods.
const map = new Map;
const key = {};
map.set(key, "stuff"); // key is passed as an argument, not stringified
map[key] // key would get stringified here according to language semantics, but even then it would be undefined as maps keys aren't keys of the map object
What these methods do under the hood is implementation secific.
I was asked this on an interview, and I can't vouch for how Map is actually implemented under the hood (ended up here looking for myself). However, this is the approach I worked out with my interviewer (note, the requirement was only for DOM node keys, but I do think objects are the trickiest key to handle, and the rest could be handled trivially with additional code), and I think it is at least insightful:
class Map {
constructor() {
this.map = {}; // internal key/value object
this.trackerKey = Symbol();
}
set(key, value) {
let lookupKey = key[this.trackerKey];
if (!lookupKey) {
lookupKey = Symbol();
key[this.trackerKey] = lookupKey;
}
this.map[lookupKey] = value;
}
has(key) {
return key.hasOwnProperty(this.trackerKey);
}
get(key) {
const lookupKey = key[this.trackerKey];
return this.map[lookupKey];
}
delete(key) {
const lookupKey = key[this.trackerKey];
delete key[this.trackerKey];
delete this.map[lookupKey];
}
}
Basically, the idea is to use Symbols under the hood to keep track of the objects. trackerKey is a (Symbol) property we add to all keys that are coming in. Since it's a symbol defined inside the instance, nothing else will be able to reference it.
So when we go to set, we check if the trackerKey property exists on the object. If not, we set it to a new symbol. We then set the value of that key in our internal map to the value coming in.
has and get are fairly straightforward lookups now. We check if our tracker key exists on the key to see if it is included in our Map. For get we can simply grab our internal lookup key for the object from its trackerKey property.
For delete, we just need to delete the trackerKey property from the key object, and then delete the property in our internal map object.
Fun and insightful exercise! Hope this helps :)
So I am learning Javascript and testing this in the console.
Can anybody come up with a good explanation why something like this does not work?
var students = ['foo', 'bar', 'baz'];
function forEach2(arr1) {
for (i = 0; i < arr1.length; i++) {
console.log(arr1[i]);
}
}
students.forEach2(students)
I get : Uncaught TypeError: students.forEach2 is not a function
For me it would seem logical to work but it doesn't
why?
forEach2 is not a function of students. students is an array containing 3 string values. just use forEach2 without students. before it.
var students = ['foo', 'bar', 'baz'];
function forEach2(arr1) {
for (i = 0; i < arr1.length; i++) {
console.log(`arr1[${i}]:`, arr1[i]);
}
}
console.log("students:", students);
console.log("students has .forEach2 function ?", typeof students.forEach2 == "function");
console.log("forEach2 is a function?", typeof forEach2 == "function");
console.log("forEach2(arr1)...");
forEach2(students);
console.log("students.forEach(student)...");
//forEach already has a native implementation
students.forEach((student)=> {
return console.log("student:", student);
});
JavaScript is an object-oriented language with prototypal inheritance.
Object-oriented means you have objects with members (called properties in JavaScript) which hold values. In JavaScript, functions are “first-class citizens”, meaning you can assign them to variables just like you would other values.
When you write write a function such as ‘function x(y) { return y +1; }’, what’s really happening is 1) you are declaring a variable named “x”, and 2) you are creating a function “value” which is then assigned to that variable. If you have access to that variable (it’s within scope), you can invoke the function like ‘x(5)’. This evaluates to a new value which you could assign to another variable, and so on.
Ok, so now we have a problem. If functions are values, and values take up space (memory), then what happens when you need a bunch of objects with the same function? That’s where prototypal inheritance comes in. When we try to access a value on an object, via either the member access operator ‘.’, like ‘myObj.someValue’, or via an indexing operator ‘[]’ like ‘myObj[“someValue”] (both of which are equivalent in JavaScript, for the most part), the following occurs:
The runtime checks to see if a ‘myObj’ variable exists in the current scope. If it doesn’t? Exception!
The runtime looks at the object referenced by the variable and checks to see if it has a property with the key “someValue”.
If the object has that property, the member access expression (‘myObj.someValue’) evaluates to that property’s value, and we’re done.
If the object does not have that property, we start doing prototypal inheritance stuff. In JavaScript, all this means is that when we try to access a property that doesn’t exist, the runtime says “hey, what if this objects *prototype has a property with that key?” If it does, we use the prototype’s property’s value. If it doesn’t, we return ‘undefined’.
Notice that because prototypes are just objects, and therefore can themselves have a prototype, step 4 is recursive until we run out of prototypes on which to attempt member access.
Ok, so at this point you may be thinking “what’s a prototype and what does it have to do with my question?” It’s just an object. If any object has a property with the key “prototype”, then that property’s value IS a prototype. Specifically, it’s that object’s prototype. And so this is where your problem arises.
There is no property on your object with the key “forEach2”. Why? Because you didn’t put it there, and you didn’t put it on the object’s prototype (or any of the prototypes “up stream”.
The ‘forEach’ function of an Array exists as a property on the Array’s prototype: ‘Array.prototype.forEach = function (...) {...}’. Your function does not, and therefore you cannot use member access on an array to get that value (the function), and that’s why your code is borked.
Fortunately for you, a variable ‘forEach2’ exists in your current scope, and you can just use it without needing to do any member access! You just write ‘forEach2(students);’ and that’s all.
But what if you want to access that function anywhere you have an array? You have two options: put it on every instance of your arrays, or put it on Array’s prototype. ‘Array.prototype.forEach2 = forEach2;’ however, if you do this you will need to change your function a bit. Right now it expects the array as its first argument (‘arr1’), but its redundant to write ‘students.forEach2(students)’ because when a function is invoked immediately following member access, the function will be provided with a special variable ‘this’ which will have the value of the object for which you are accessing its member. So, in this case, you would omit the ‘arr1’ argument and instead just use the the special ‘this’ variable which is magically in scope within your function.
Array.prototype.forEach2 = function ()
{
for (var i = 0; i < this.length; i++)
{
console.log(this[i]);
}
}
I hope this clarifies some things for you, and I hope it raises a bunch more questions.
P.S: Adding things to prototypes is both powerful and considered harmful unless you know what you’re doing and have a good reason to do it (like writing polyfills)... so do it at your own peril and use responsibly.
For your example to work, simply add the following statement after definition of forEach2:
// Here you're redefining the built-in forEach function with your
// custom function
students.forEach = forEach2;
// Now this must work
students.forEach(students);
This is an explanation why is that:
An array is a particular implementation of the Object. Object is an aggregate of the property:value pairs, such as
{'some_property': '1',
'some_function': function(),
'forEach': function() {<function logic>},
...
}
Your error 'Uncaught TypeError: students.forEach2 is not a function' tells, that there is no property forEach2 (which supposed to be a function) in between the Object's properties. So, there are two methods to correct this situation: - add a property (method function) to the object, or alter the existing property with similar functionality (not necessarily though).
[1] The first method assumes you add a new property function using Array.prototype.forEach2 = function() {...}.
[2] The second - you may redefine or alter any property in the Object by just assigning a new value to that property: Object.forEach = forEach2
Because the function forEach2 is not available either within the Array.prototype or is not a direct property of that array, so, you have two alternatives to use that custom forEach:
Use a decorator to add that function to a specific array
var students = ['foo', 'bar', 'baz'];
function forEach2() {
for (i = 0; i < this.length; i++) {
console.log(this[i]);
}
}
function decorate(arr) {
arr['forEach2'] = forEach2;
return arr;
}
decorate(students).forEach2()
Add that function to the Array.prototype
var students = ['foo', 'bar', 'baz'];
Array.prototype.forEach2 = function() {
for (i = 0; i < this.length; i++) {
console.log(this[i]);
}
}
students.forEach2();
Both alternatives use the context this to get the current array.
I'm working on an AngularJS SPA and I'm using prototypes in order to add behavior to objects that are incoming through AJAX as JSON. Let's say I just got a timetable x from an AJAX call.
I've defined Timetable.prototype.SomeMethod = function() and I use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf in order to set the prototype of x to TimeTable.prototype. I have the polyfill in place too.
If I call x.SomeMethod() this works in IE > 9, FF, Chrome etc. However, IE 9 gives me a headache and says throws an error stating 'x does not have property or member SomeMethod'.
Debugging in IE shows me that the _proto_ of x has SomeMethod() in the list of functions, however, calling x.SomeMethod() gives the same error as described.
How can I make this work in IE9 ?
More comment than answer
The main problem with "extending" a random object retrieved from some other environment is that javascript doesn't really allow random property names, e.g. the random object may have a property name that shadows an inherited property. You might consider the following.
Use the random object purely as data and pass it to methods that access the data and do what you want, e.g.
function getName(obj) {
return obj.name;
}
So when calling methods you pass the object to a function that acts on the object and you are free to add and modify properties directly on the object.
Another is to create an instance with the methods you want and copy the object's properties to it, but then you still have the issue of not allowing random property names. But that can be mitigated by using names for inherited properties that are unlikely to clash, e.g. prefixed with _ or __ (which is a bit ugly), or use a naming convention like getSomething, setSomething, calcLength and so on.
So if obj represents data for a person, you might do:
// Setup
function Person(obj){
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
this[p] = obj[p];
}
}
}
Person.prototype.getName = function(){
return this.name;
};
// Object generated from JSON
var dataFred = {name:'fred'};
// Create a new Person based on data
var p = new Person(dataFred);
You might even use the data object to create instances from various consructors, e.g. a data object might represent multiple people, or a person and their address, which might create two related objects.
This is how I solved it at the end:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
if (!isIE9()) {
obj.__proto__ = proto;
} else {
/** IE9 fix - copy object methods from the protype to the new object **/
for (var prop in proto) {
obj[prop] = proto[prop];
}
}
return obj;
};
var isIE9 = function() {
return navigator.appVersion.indexOf("MSIE 9") > 0;
};
The question I have deals with an application of adding a new method to the existing String constructor. In Object Oriented Program for Javascript by Stoyan Stefanov, there is an example of using the Array constructor's .reverse() method to create one for the String constructor. Here is the example:
String.prototype.reverse = function() {
return Array.prototype.reverse.apply(this.split('')).join('');
}
I thought the .reverse() method of Array belonged directly to the object of Array. In fact, when I try to do the second bit of code with this statement:,
String.prototype.reverse = function() {
return Array.reverse.apply(this.split('')).join(''); //WITHOUT the .prototype
}
var rev = "karunesh".reverse(); //applying the code here
I get an error in the Firebug Console stating: "TypeError: missing argument 0 when calling function Array.reverse". That does not make any sense to me.
And of course, if I add back in the .prototype, it works perfectly fine.
Also, if is the case that I have to call upon prototype to access the .reverse() method from the Array object, then is it the case that I have to do that for any built-in object in Javascript?
Thanks for the help in advance!
Is it the case that I have to call upon prototype to access the .reverse() method from the Array object
No. To access a method on an object, just access it with dot notation. What you want to do is simply
return this.split('').reverse().join('');
That is just what apply (or call) does:
var arr = this.split('');
return arr.reverse.apply(arr).join('');
and finally arr.reverse === Array.prototype.reverse since that's where Array objects do inherit from. You are not accessing the reverse method on the Array constructor function object itself, you are to access the property that all Array instances share - via their prototype. Yet you hardly will ever need to use the prototype object explicitly, that's only when you're dealing with objects that are not Array instances (do not share the prototype) like arguments objects or NodeLists.
TypeError: missing argument 0 when calling function Array.reverse. That does not make any sense to me.
Array.reverse is a non-standard Array generic method which is only available in Firefox. It's purpose is to simplify the construct of applying Array prototype methods on other objects, and it does take the array-like object as it's first parameter. An example:
Array.reverse([0, 1]) // [1, 0]
which is equivalent to
Array.prototype.reverse.apply([0, 1]);
However, you were doing
Array.reverse.apply([…]/*, undefined*/)
which is calling the Array.reverse function with the array for the (irrelevant) this value and no actual argument, equivalent to
Array.prototype.reverse.apply(undefined)
and that throws the rightful exception.
Array.reverse is undefined (at least in Chrome 29) - Array.prototype.reverse is a function that will reverse the order of the "iterable" it is called on.
The key thing to note here is that Array is not a class like you would have in Java - rather it is a constructor:
[].constructor === Array;
// true
The prototype property of Array is actually what is providing the behavior to any particular instance of Array:
Object.getPrototypeOf([]) === Array.prototype;
// true
// Bad idea, just for an example
var guys = ['Tom', 'Harry', 'Richard'];
Array.prototype.exclaim = function() {
return this.join(", ") + "?!?!?!";
};
guys.exclaim();
// Tom, Harry, Richard?!?!?!
The key here is that JavaScript uses a prototype-based object-oriented pattern, rather than the classical pattern you are more likely familiar with. Instead of having "classes" which contain all the behaviors, but which are distinct from instances, JavaScript has objects, which can be the "prototypes" of other objects, providing data and behavior to the child objects.
// Totally licit OO pattern in JavaScript
var prototypeClass = {
method1: function() { console.log("Hello from method 1!"); },
method2: function() { console.log("Hello from method 2!"); },
classData: 42
};
var prototypeInstance = Object.create(prototypeClass);
prototypeInstance.method1() // Hello from method 1!
prototypeInstance.classData // 42
// And you can modify the class after
// instantiating instances and the changes
// will be picked up by the instances
prototypeClass.happyPrimes = "Don't they teach recreational mathematics anymore?";
prototypeInstance.happyPrimes // The quote from 42