I am trying to understand the Object.freeze method of ECMAscript.
My understanding was that it essentially stops changes to all the properties of an object. MDN documentation says:
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.
This does not seem to be the case, but perhaps I have misinterpreted the docs.
Here is my object, with its enumerable property exampleArray
function myObject()
{
this.exampleArray = [];
}
var obj = new myObject();
obj.exampleArray[0] = "foo";
Now if I freeze the object, I would expect the exampleArray property to be frozen too, as in it can no longer be changed in any way.
Object.freeze(obj);
obj.exampleArray[1] = "bar";
console.log(obj.exampleArray.length); // logs 2
"bar" has been added to the array, thus the frozen object has been changed. My immediate solution is to just freeze the desired property:
Object.freeze(obj.exampleArray);
obj.exampleArray[2] = "boo";
Now changing the array throws an error, as desired.
However, I am developing my application and I don't yet know what will be assigned to my object. My use case is that I have some game objects which are initialized (from an XML file) when the game starts. After this, I do not want to be able to change any of their properties accidentally.
Perhaps I am misusing the freeze method? I would like to be able to freeze the whole object, a sort of recursive freeze. The best solution I can think of here is to loop through the properties and freeze each one.
I've already searched for this question and the only answer says it's an implementation bug. I am using the newest version of Chrome. Any help is appreciated.
Object.freeze is a shallow freeze.
If you look at the description in the docs, it says:
Values cannot be changed for data properties. Accessor properties (getters and setters) work the same (and still give the illusion that you are changing the value). Note that values that are objects can still be modified, unless they are also frozen.
If you want to deep-freeze an object, here's a good recursive example
function deepFreeze(o) {
Object.freeze(o);
Object.getOwnPropertyNames(o).forEach(function(prop) {
if (o.hasOwnProperty(prop)
&& o[prop] !== null
&& (typeof o[prop] === "object" || typeof o[prop] === "function")
&& !Object.isFrozen(o[prop])) {
deepFreeze(o[prop]);
}
});
return o;
}
function myObject() {
this.exampleArray = [];
}
var obj = deepFreeze(new myObject());
obj.exampleArray[0] = "foo";
console.log(obj); // exampleArray is unchanged
Set the property descriptors for the object to writable:false, configurable:false using Object.defineProprties; then call Object.preventExtensions on the object. See How to create static array in javascript.
Related
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I check if an object has a specific property in JavaScript?
I found the following snippet in Twitter's JavaScript files. Why do they need to call the hasOwnProperty function to see dict has the key property? The for loop is running for each 'key' in 'dict' which means 'dict' has 'key'. Am I missing a point?
function forEach(dict, f) {
for (key in dict) {
if (dict.hasOwnProperty(key))
f(key, dict[key]);
}
}
Because if you don't, it will loop through every property on the prototype chain, including ones that you don't know about (that were possibly added by somebody messing with native object prototypes).
This way you're guaranteed only the keys that are on that object instance itself.
The hasOwnProperty method lets you know if a property is directly on an instance of an object or inherited from its prototype chain.
Consider the following
function ObjWithProto() {
this.foo = 'foo_val';
}
ObjWithProto.prototype = {bar: 'bar_val'};
var dict = new ObjWithProto();
dict.foobar = 'foobar_val';
I.e., you have an Object dict with properties foo and foobar that also inherits a property bar from its prototype chain.
Now run it through (a modified version of) your code:
function forEach(dict) {
var key;
for (key in dict) {
if (dict.hasOwnProperty(key))
console.log('has', key, dict[key]);
else
console.log('not', key, dict[key]);
}
}
forEach(dict);
You will see
has foo foo_val
has foobar foobar_val
not bar bar_val
This lets you separate properties that an object has itself and those it has inherited (which are usually methods that aren't relevant to the loop).
Furthermore, if you now do dict.bar = 'new_bar_val';, the last result will change to has bar new_bar_val, letting you distinguish even between properties of the same name as those inherited.
Every object in JavaScript is a dictionary. This means that "toString" and every other method is a key of every Object:
var myObj = {};
console.log(myObj["toString"]);
But this function is inherited from Object class, so hasOwnProperty tells you if this key is owned by the dictionary or if it is inherited.
"toString" in myObj; // true
myObj.hasOwnProperty("toString") // false
blockhead is right here. For example, the Prototype.js framework used to extend native arrays with extra helper methods (I do not know the situation with current versions of a framework).
Thus straight usage of "for (key in dict)" would return all the elements of the div plus references to helper methods. Which is kind of unexpected :)
A JavaScript WeakMap does not allow you to get the key, or the length or size, by design.
Is it possible to nevertheless loop over entries in some way ?
If not .. how does the Chrome console do this ?
Is it possible to nevertheless loop over entries in some way?
No, as you say, the contents of a WeakMap are not accessible by design, and there is no iterability.
If not … how does the Chrome console do this?
The console uses the debugging API of the JS engine, which allows access to the internals of objects (also to promise states, wrapped primitives, etc.) and many more.
Things are moving and it will soon be possible to create iterable week maps thanks to weak refs.
See the iterable WeakMap example in the tc39 weakrefs proposal.
(note that it is already possible with nodejs v12.?.? using --harmony-weak-refs flag)
Running far afield with a qualifier in your (2015) question, specifically:
Is it possible to nevertheless loop over entries in some way ?
Yes.
In one ridiculous situation, it is possible to emulate and then iterate the keys and values of a WeakMap, and also to make a proper, independent copy of your WeakMap.
If the WeakMap you wish to clone was built in a very specific fashion by a constructor function, you can do it:
// Define a Constructor-Function
// that makes objects
// containing WeakMaps:
function makeWeakMapObject(){
this.wm1 = new WeakMap();
this.o1 = {};
this.o2 = {"orange":"orange"};
this.wm1.set(this.o1, 37);
this.wm1.set(this.o2, 'azerty');
}
// Construct a new object:
let constructedWeakMapObject = new makeWeakMapObject();
// Then set a new key-value pair
// on the WeakMap in your object;
// because, ya know, otherwise you'd
// just reuse the WeakMap constructor
// and wouldn't need to clone :D
constructedWeakMapObject.added = {"ya":"glad"};
constructedWeakMapObject.wm1.set(constructedWeakMapObject.added, 42);
// In preparation to clone your newly constructed object,
// get that newly constructed object's property descriptors:
let props = Object.getOwnPropertyDescriptors(constructedWeakMapObject);
// Have a gander at those props; just for fun:
console.log({"props":props});
// Attempt to clone the constructedWeakMapObject
// using its ownPropertyDescriptors
let weakClone = new cloneWeak(props);
// and then check out what you made:
console.log({"weakClone":weakClone});
// Verify that you've made an independent clone
// (even though this example is shallow)
// by altering the WeakMap in your weakClone:
weakClone.wm.delete(weakClone.o1);
// Make sure your clone was altered:
console.log(weakClone.wm.get(weakClone.o1));
// And then check to see that the
// changes to your clone
// don't appear on your constructed object:
console.log(constructedWeakMapObject);
console.log(constructedWeakMapObject.wm1.get(constructedWeakMapObject.o1));
// A support function to help you use fresh keys in your cloned WeakMap to keep it independent from your original WeakMap
function cloneObject(obj) { // use something more robust, like underscore: _.cloneDeep(obj); actually, you'll likely have to roll your own so you can make clones of functions... anywho
var clone = {};
for(var i in obj) {
if(typeof(obj[i])==="object" && obj[i] !== null)
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
// Called as a constructor function w/arguments
function cloneWeak(inco){ // a bit wonky, at least in the middle
this.wm = new WeakMap();
let tempMap;
for(key in inco){
// Build keys on 'this' that match the incoming keys
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
this[key] = cloneObject(inco[key].value);
}
// Reference the incoming map from your temp map
// (this makes the following loop possible)
else{tempMap = inco[key].value;}
}
this.fakeForHack = {}; // no idea why this works
this.wm.set(this.fakeForHack, "ok"); // no idea why this works... without it, the WeakMap entry for made.wm1.get(made.added) won't transfer -> ???
for(key in inco){
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
// Set values for 'this' WeakMap:
this.wm.set(this[key], tempMap.get(inco[key].value));
}
}
}
It's kinda ugly, it's brittle, and it only solves a ridiculous edge-case; you're welcome!
I've defined an enumerable property in the prototype object and would like it to appear when I convert a prototyped object to JSON.
My first idea was to set it in toJSON but because I don't really want to keep it in the object afterwards I'll have to more or less clone the whole object in the function and set the necessary property.
Redefining the property in the target object and just proxying with the context of the current object doesn't seem to be an option as well, since I can't really use apply or call when getting dynamic properties.
Working solutions I could come up with so far seem to require quite an amount of code and aren't flexible and concise enough, so I'm wondering if there are any best practices of solving this task.
Here is an example which could seem a bit synthetic but still, I believe, conveys the idea:
function ProjectFolder() {
this.files = [];
Object.defineProperty(this, 'size', {enumerable: true, get: function() {
return this.files.length;
}});
}
function GithubProjectFolder() {
this.files = ['.gitignore', 'README.md'];
}
GithubProjectFolder.prototype = new ProjectFolder();
var project1 = new ProjectFolder();
JSON.stringify(project1);
// output: {"files":[],"size":0}
// size is present
var project = new GithubProjectFolder();
JSON.stringify(project);
// output: {"files":[".gitignore","README.md"]}
// size is absent
I'll have to more or less clone the whole object in the function and set the necessary property.
Yes, and there's nothing wrong with that. That's how .toJSON is supposed to work:
ProjectFolder.prototype.toJSON = function toJSON() {
var obj = {};
for (var p in this) // all enumerable properties, including inherited ones
obj[p] = this[p];
return obj;
};
However, there are two other points I'd like to make:
The size of a folder doesn't really need to be stored separately in the JSON when it already is encoded in the length of the files array. This redundant data seems to be superfluous, and can confuse deserialisation. Unless something requires this property to be present, I'd recommend to simply omit it.
In ProjectFolders, the .size is an own property of each instance - in GithubProjectFolders it is not. This suggest that you're doing inheritance wrong. Better:
function GithubProjectFolder() {
ProjectFolder.call(this);
this.files.puhs('.gitignore', 'README.md');
}
GithubProjectFolder.prototype = Object.create(ProjectFolder.prototype);
If you'd fix that alone, the size will appear in the serialisation of your project.
I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify
Just had a quick question about why a certain order of assignment works and another doesn't.
I wanted to create a simple "inherit" / "copy" function (just for testing it) that copies properties from one object to another:
var cat = { tail:"yes", hairy:"yes, hairy" };
var dog = { sick:"extremely ill"};
function inherit(obj1, obj2) {
for (var p in obj1)
{
obj2[p] = obj1[p]; // this works, but "obj1[p] = obj2[p];" doesn't. Why??
}
}
inherit(cat, dog);
console.log(dog.tail);
You are looping over all the properties of obj1, so all those properties exist on obj1.
If you try to copy from obj2 then you are trying to copy properties that don't exist (on that object), so you cause an error.
You're reading the properties var p in obj1, so those indexes only necessarily exist in obj1. As such, trying to assign obj1[p] = obj2[p]; won't work as expected, since there is no guarantee (and in your particular example this is certainly the case) that obj[p] is defined. This assignment will simply assign undefined to indexes in obj1 that don't exist in obj2 and copy the values whose indexes do exist in obj2.
You need to loop over each object's properties separately (i.e. two loops), although this also isn't a good idea, since any values with the same index on both object will result in one being wiped out. What are you actually trying to achieve by this? It seems a very dangerous/volatile thing to do.