Iterate over an object and replace properties with setters and getters - javascript

I want to be able to have a function that will replace all of an objects properties with a setter and getter.
function myFunction(obj){
var private={};
for(var i in obj){
if(obj.hasOwnProperty(Ii){
private[i]=obj[i]
delete obj[i]
Object.defineProperty(obj, i, {
set:(function(i){return function(val){private[i]=val}})(i),
set:(function(i){return function(){return private[i]}})(i)
})}
}
return obj
}
Would this work or should I use a different method to iterate over the object, could this style of storing the data in a seperate object also cause issues.

Storing the data in a separate object is fine.
I would simplify the code a bit by using Object.keys, assuming that you didn't really mean to replace inherited properties (for-in enumerates both "own" and inherited properties; Object.keys just gives you an array of "own" enumerable properties):
function myFunction(obj){
var private = {};
Object.keys(obj).forEach(function(key) {
private[key] = obj[key];
// No need for the `delete`
Object.defineProperty(obj, key, {
set: function(value) {
private[key] = value;
},
get: function() {
return private[key];
},
enumerable: true,
configurable: true // Or leave it off if you don't want the
// property reconfigured
});
});
return obj;
}
Notes:
By using the forEach callback, we make the get/set function closures over key and so can avoid creating temporary wrapper functions.
enumerable defaults to false; I'm assuming you don't want to change the enumerability of the properties, so I've added enumerable: true
You had set twice, rather than get and set

Related

How does shallow cloning that is carried out with Object.create work with descriptors, setters/getters?

According to this source:
To make a “real copy” (a clone) we can use... for the
so-called “shallow copy” (nested objects are copied by reference)
or a “deep cloning” function.
So, as far I get that shallow cloning assumes that if there are other inner objects inside, then they will be copied by reference. There's no cloning for them.
Well, how does copying happen for all internal properties of an object, such as descriptors, getters/setters. Are they copied by reference?
Consider the ES3-based steps taken when shallow-cloning a standard object that has just one, primitive property:
Make a new, empty object.
Add a key to that empty object.
Assign a value to that key.
Return the object.
Those steps can be accomplished using a hand-rolled function, like this one:
function es3ShallowClone(incomingObj){
var cloneOfObj = {}; // 1
for(var key in incomingObj)
cloneOfObj[key] = incomingObj[key]; // 2 and 3
return cloneOfObj; // 4
}
Using that es3ShallowClone function, you'd make clones thus:
var obj = {a:"b"};
var newObj = es3ShallowClone(obj);
Object.assign( {}, obj ) is (since the release of ES5, in 2009) a built-in way to produce the same output that is produced by es3ShallowClone:
let newAssignedObj = Object.assign( {}, obj );
Object.assign copies only enumerable properties, and, Object.assign doesn't transfer prototypes.
///////////////////////////////////////////////
For more 'power:'
If you wish to clone an otherwise standard object that has a non-enumerable property, you'll need to use Object.getOwnPropertyDescriptors and Object.defineProperty; possibly in concert with Object.create.
The ES5-based steps taken when manually shallow-cloning an otherwise standard object that has a non-enumerable property:
Make a new, empty object.
Get an array containing the descriptor from the incoming object.
"Shallow-clone" a descriptor, then apply the descriptor clone your empty object.
Return the object.
For example:
function es5ShallowClone(incomingObj){
let cloneOfObj = {}; // 1
let descriptors = Object.getOwnPropertyDescriptors(incomingObj); // 2
for(var key in descriptors)
Object.defineProperty( cloneOfObj, key, descriptors[key] ); // 3
return cloneOfObj; // 4
}
First, let's make an example object that has a non-enumerable property:
Make a descriptor:
let someDescriptor = {
'a': {
value: 'b',
writable: true,
configurable:true,
enumerable:false
};
Create an object and assign the descriptor to it:
let obj2 = Object.create( {}, someDescriptor );
Then just clone it:
let newObj2 = es5ShallowClone(obj2);
In place of es5ShallowClone, you could instead write:
let newObjToo = Object.create( {}, Object.getOwnPropertyDescriptors(obj2) );
///////////////////////////////////////////////
For even more 'power,' you'll want to transfer prototypes as well; note that my use of the word “transfer” is a bit clumsy since the prototype doesn't leave the original object... both objects end up referencing the same prototype.
The only change we need make to our es5ShallowClone function is to step 1; so that it creates an object based on the prototype of the incoming object:
function es5ShallowCloneWithPrototype(incomingObj){
let cloneOfObj = new incomingObj.constructor(); // 1
let descriptors = Object.getOwnPropertyDescriptors(incomingObj); // 2
for(var key in descriptors)
Object.defineProperty( cloneOfObj, key, descriptors[key] ); // 3
return cloneOfObj; // 4
}
First, we'll define a constructor that can make an object that has a non-enumerable property and a prototype:
function objConstructor(){
let someDescriptor = {
'a': {
value: 'b',
writable: true,
configurable:true,
enumerable:false
};
}
let returnObj = Object.create( {}, someDescriptor );
}
objConstructor.prototype.extraInfo = “javascript rocks”;
Then we'll use that constructor function to make a fancy new object:
let constructedObj = new objConstructor();
Now, we can clone that constructedObj, in all of its glory, thus:
let newCon = es5ShallowCloneWithPrototype(constructedObj);
Or, we could clone our constructedObj, in all it's glory, using the built-in magic brought to us by ES5:
let newCon2 = Object.create(
Object.getPrototypeOf(constructedObj),
Object.getOwnPropertyDescriptors(constructedObj)
);
///////////////////////////////////////////////
Hopefully that little overview helps clarify that descriptors aren't treated the same way that a regular ol object would be treated during a cloning process.
To have a closer look at the info that is available to a cloning function in ES3 or since ES5, and to see how getters and object-values are presented during enumeration, check out following codepen link, and open your browser's console... you may want to clear the console and then click the run button again to see the best representation of the captured info. PS: using ES3-style cloning, the names of setters are added to your clone and hold the value of undefined. When using ES5-style cloning, getters and setters can cause bugs if those getters or setters reference values in non-cloner accessible scopes.)
https://codepen.io/Ed_Johnsen/pen/GRmjajr
All good things.

JS Prototype methods act differently while accessing in a loop

I am extending the array object with a new method called custommethod as follows and looping through the values inside an array. However, the looping through the indices also prints the name of the property which is extended via Array.prototype.<method>.
Array.prototype.custommethod = function() {
console.log("Hello from Custom Method");
}
Object.defineProperty(Array.prototype, "anothercustommethod", {
value: function() {
console.log("Hello from Another Custom Method");
}
});
Loop through the array
> x = [1,2]
[ 1, 2 ]
> for (i in x) { console.log(i); }
0
1
custommethod
Why doesn't the anothercustommethod get printed in this loop?
Is using the Object.defineProperty() a safer way to create Array.prototype?
I am curious as to how the for loop in javascript actually works. Does it use Object.keys() internally? If so how does it print custommethod which is inside __proto__ property but not anothercustommethod which is also inside __proto__ property?
Why doesn't the anothercustommethod get printed in this loop?
for in iterates over those properties, which data descriptor enumerable is set to true.
In the Documentation
enumerable
true if and only if this property shows up during
enumeration of the properties on the corresponding object. Defaults to
false.
When using defineProperty, you can also pass an extra property - enumerable.
By default it is set to false.
Array.prototype.custommethod = function() {
console.log("Hello from Custom Method");
}
Object.defineProperty(Array.prototype, "anothercustommethod", {
value: function() {
console.log("Hello from Another Custom Method");
},
enumerable: true
});
const x = [1,2]
for (i in x) { console.log(i); }
Is using the Object.defineProperty() a safer way to create Array.prototype?
There is nothing about safe. Try to change the build in prototypes rarely
you can check the property by this API:
Object.getOwnPropertyDescriptor(Array.prototype,'custommethod');//{value: ƒ, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(Array.prototype,'anothercustommethod');//{value: ƒ, writable: false, enumerable: false, configurable: false}
see? the first one got:enumerable: true,the second one got:enumerable: false,
also other values is different too that is because the default settings is different when set by different API
acutally ,want to be more safe,you can use this API:
Object.getOwnPropertyNames
Object.getOwnPropertyNames(Array.prototype).filter(v=>v.endsWith('custommethod'));//["custommethod", "anothercustommethod"]
if there is symbol,you still need to
Object.getOwnPropertySymbols(Array.prototype);//[Symbol(Symbol.iterator), Symbol(Symbol.unscopables)]
Object.keys works like for in,they won't traverse property whose enumerable is false;
when you try to traverse property with other API,should think about these things

Why can ES6 Symbol properties be made enumerable by Object.defineProperty?

In ES6 properties can be defined as symbol properties:
var symbol = Symbol();
var object = {};
object[symbol] = 'value';
MDN defines enumerable properties as 'those which can be iterated by a for..in loop' (1). Symbol properties are never iterated by a for...in loop, therefore they can be considered non-enumerable (2).
Does it make any sense, then, that you can do this:
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
and that querying object for it's descriptor does indeed confirm that this property is enumerable:
Object.getOwnPropertyDescriptor(object, symbol)
// -> { enumerable: true }
Why? What use is this?
(1) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
(2) for...in uses [[Enumerate]], which only includes string keys. Probably the definition on MDN should be changed now that we have symbol properties.
Yes, there's a reason for allowing Symbol properties to be enumerable: Object.assign:
let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol();
let original = {};
original[s1] = "value1"; // Enumerable
Object.defineProperty(original, s2, { // Enumerable
enumerable: true,
value: "value2"
});
Object.defineProperty(original, s3, { // Non-enumerable
value: "value3"
});
let copy = {};
Object.assign(copy, original);
console.log("copy[s1] is " + copy[s1]); // value1, because it was enumerable
console.log("copy[s2] is " + copy[s2]); // value2, because it was enumerable
console.log("copy[s3] is " + copy[s3]); // undefined, because it wasn't enumerable
Live Copy on Babel's REPL.
Just for clarity:
MDN defines enumerable properties as 'those which can be iterated by a for..in loop' (1).
That's simply wrong for ES6 (ES2015). It was a reasonable, if simplistic, definition in ES5 and earlier, no it's no longer even simplistically correct because of Symbols. I've fixed the article.
This is a CW because it was the outgrowth of the comments on the question.
This is because the rules for enumeration include a clause requiring string keys. Bear in mind that enumeration and asking for keys are different operations with entirely different rules.
Looking at the section for for ... in/for ... of head evaluation (13.7.5.12), it states that the iteration is done using:
If iterationKind is enumerate, then
c. Return obj.[[Enumerate]]().
The description of [[Enumerate]] (9.1.11) very clearly states that it:
Return an Iterator object (25.1.1.2) whose next method iterates over all the String-valued keys of enumerable properties of O.
The check for enumerable properties comes later in the body, and the pseudo-code example makes this even more clear:
function* enumerate(obj) {
let visited=new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") { // type check happens first
let desc = Reflect.getOwnPropertyDescriptor(obj,key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key; // enumerable check later
}
}
}
...
}
(comments mine)
Clearly, properties with non-string keys will not be enumerated. Using this example:
var symbol = Symbol();
var object = {};
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
Object.defineProperty(object, 'foo', {
value: 'bar',
enumerable: true
});
Object.defineProperty(object, 'bar', {
value: 'baz',
enumerable: false
});
Object.defineProperty(object, () => {}, {
value: 'bin',
enumerable: true
});
for (let f in object) {
console.log(f, '=', object[f]);
}
for (let k of Object.getOwnPropertyNames(object)) {
console.log(k);
}
you can verify that in Babel and Traceur.
However, you'll see two interesting things:
getOwnPropertyNames includes non-enumerable properties. This makes sense, as it follows completely different rules.
for...in includes non-string properties under both transpilers. This does not seem to match the spec, but does match ES5's behavior.

get all methods and properties of an object

If I use (on a text frame):
b.selection().fit(FitOptions.frameToContent);
Then it works as expected, this means that the selected object has a fit method.
If I use:
for (var property in b.selection()) {
b.println(property);
}
On the same selection then it doesn't print a fit method.
If I use this:
function getMethods(obj) {
var result = [];
for (var id in obj) {
try {
if (typeof(obj[id]) == "function") {
result.push(id + ": " + obj[id].toString());
}
} catch (err) {
result.push(id + ": inaccessible");
}
}
return result;
}
b.println(getMethods(b.selection()));
Then I don't get the fit method either. And I would really like to know all methods and properties of the selected object. So how do i get them?
try obj.reflect.methods to get all methods
When method fit() exists and doesn't shine up in the for-in-loop it is a non-enumerable property.
There are different ways to access the properties of an object:
var obj = b.selection();
for (var p in obj) {
console.log(p); // --> all enumerable properties of obj AND its prototype
}
Object.keys(obj).forEach(function(p) {
console.log(p); // --> all enumerable OWN properties of obj, NOT of its prototype
});
Object.getOwnPropertyNames(obj).forEach(function(p) {
console.log(p); // all enumerable AND non-enumerable OWN properties of obj, NOT of its prototype
});
If you don't find .fit() on one of this ways its not enumerable AND not OWN property of obj, but sits somewhere in the prototype of obj. Then you can do:
var prot = Object.getPrototypeOf(obj);
Object.getOwnPropertyNames(prot).forEach(function(pp) {
console.log(pp); // --> all enumerable AND non-enumerable properties of obj's prototype
});
Often objects have a bit longer prototype-chain and the property sits somewhere deeper on it. Then you just repeat the last snippet as often as needed:
var prot2 = Object.getPrototypeOf(prot);
Object.getOwnPropertyNames(prot2).forEach( /*...*/ );
To make it complete: Let's say you have found .fit on the obj's prototype prot. Then you can inspect it:
console.log(Object.getOwnPropertyDescriptor(prot.fit));
That outputs an object which shows the value of prot.fit and whether it's enumerable, writable, and configurable or not. The Object.methods and some more FIND HERE.
Or just use b.inspect(obj). Prints out all properties and values of an object in a recursive manner to the console. See http://basiljs.ch/reference/#inspect

Copying attributes in Javascript

Javascript - The Definitive Guide (6ed) shows the following code:
Originally we have this function
/*
* Copy the enumerable properties of p to o, and return o.
* If o and p have a property by the same name, o's property is overwritten.
* This function does not handle getters and setters or copy attributes.
*/
function extend(o, p) {
for(prop in p) { // For all props in p.
o[prop] = p[prop]; // Add the property to o.
}
return o;
}
Then the author decided to rewrite it and extend the copy ability (able to copy accessor properties, for example):
/*
* Add a nonenumerable extend() method to Object.prototype.
* This method extends the object on which it is called by copying properties
* from the object passed as its argument. All property attributes are
* copied, not just the property value. All own properties (even non-
* enumerable ones) of the argument object are copied unless a property
* with the same name already exists in the target object.
*/
Object.defineProperty(Object.prototype,
"extend", // Define Object.prototype.extend
{
writable: true,
enumerable: false, // Make it nonenumerable
configurable: true,
value: function(o) { // Its value is this function
// Get all own props, even nonenumerable ones
var names = Object.getOwnPropertyNames(o);
// Loop through them
for(var i = 0; i < names.length; i++) {
// Skip props already in this object
if (names[i] in this) continue;
// Get property description from o
var desc = Object.getOwnPropertyDescriptor(o,names[i]);
// Use it to create property on this
Object.defineProperty(this, names[i], desc);
}
}
});
I don't understand why we are extending Object.prototype, and now how do we use this to copy all the attributes in Object y to Object x? How do I use Object.prototype.extend ?
I decided to test if I can do something quicker. I don't understand why the following custom code doesn't work.
function extend(o){
var p = new Object();
for( prop in o)
Object.defineProperty(p,prop,Object.getOwnPropertyDescriptor(o,prop));
return p;
}
// Let's perform a simple test
var o = {};
Object.defineProperty(o, "x", { value : 1,
writable: true,
enumerable: false,
configurable: true});
o.x; // => 1
Object.keys(o) // => []
var k = new Object(extend(o)); // Good, k.x => 1
// More test
Object.defineProperty(o, "x", { writable: false });
Object.defineProperty(o, "x", { value: 2 });
Object.defineProperty(o, "x", { get: function() { return 0; } });
o.x // => 0
// Perform the creation again
var k = new Object(extend(o)); // bad. k.x is 1, not 0
// so the getter property didn't copy to k !!!
Sorry I am pretty new to Javascript. Thanks for the help in advance. All these questions are related to the transformation / rewrite of the function "extend"
I edited the my test code. Sorry!
The purpose of defining this in the prototype is so that every Object can call it as a member function, like this:
yourobject.extend(anotherObject);
Most coders find that more elegant than having to pass both objects as parameters, like this:
extend(yourObject, anotherObject);
Modifying prototypes is a great way to add useful "methods" to objects.
side note: I would not recommend using the author's extend code. It doesn't properly check hasOwnProperty.
You seem to misunderstand the use of prototype. Prototyping is JavaScript's way of dealing with inheritance. The extend function is terminology loaned from languages like C++ and Java and is not native to JavaScript.
Here's a guide on prototype: http://mckoss.com/jscript/object.htm
I have several years experience writing JavaScript and I'd recommend not using prototyping at all. JavaScript has other ways of dealing with data modeling that I find more elegant. It's still useful to understand how prototyping works though because it deepens your understanding of Objects.

Categories