Problem:
According https://javascript.info/property-descriptors
property descriptors are like objects with own properties like:
{
value:
writable:
readable:
configurable:
enumerable:
}
one can do this with normal object:
for(var n in object) {
console.log(object[n]);
}
to write out the values of the keys in that object.
I want to do the same. Get descriptors, then for each descriptor print the value.
This is one of several ways I tried to do it. For each of the Property Descriptor in descriptors, print out the value of its properties.
I imagined getting something like
"John Doe"
0
etc...
Basically, put in other words, using Descriptors to show the values stored within an object.
Own tryes:
I tryed several times, so many times and ways that I
actually forgot, had to delete to to keep my head somewhat clear from the clutter of previous tryes. It resulted in all kinds of imaginable output except of what I expected. like numbers: 0, 1, 2, 3, 4... names of the properties instead of their values, or rows of "undefined" whilst I was expecting something like
"John Doe"
0
function
function
10 etc...
I DID check stackoverflow, AND other pages as well.
http://speakingjs.com/es5/ch17.html#property_attributes
being just one of say, 3-4.
It is NOT a homework, Also I do belive its probably not the best practise in doing it this way, using descriptors, but I got frustrated and obsessed, because I feel it should be possible.
I also apologize for eventuall weirdness in code. Im new at Javascript. I hope people focus on the problem and not on other stuff. English isnt my first language. I DID try to make the code as short as possible, and replicable. Hopefully Its clear what I want to do, otherwise Ill probably just give up xD.
Code
"use strict";
function Person() {
this._name = "";
this.age = 0;
this.greet = function() {
console.log(`hi Im ${this._name} and im ${this.age} years old`);
};
this.beep = function(times) {
for(var i = 0; i < times; i++) {
console.log("beeeep");
}
};
Object.defineProperty(this, "something", {
value: 10,
writable: false,
readable: true,
configurable: false,
enumerable: true,
});
Object.defineProperty(this, "name", {
get() {
return this._name;
},
set(nam) {
this._name = nam;
}
} );
}
var obj = new Person();
console.log(obj.something);
obj.name = "John Doe";
console.log(obj.name);
console.log("##############");
var properties = Object.getOwnPropertyDescriptors(obj);
for(var property in properties) {
for(var val in property) {
console.log(property[val]);
}
}
property is a key not a value, use that key to access the related value of the object:
for(var property in properties) {
for(var val in properties[property]) {
console.log(property, val, properties[property][val]);
}
}
property in your example at the end is a string, the name of the property in the object returned by getOwnPropertyDescriptors. You want the value of that property for your second loop:
var properties = Object.getOwnPropertyDescriptors(obj);
for(var property in properties) {
var descriptor = properties[property]; // ***
for(var val in descriptor) {
// *** --------^^^^^^^^^^
console.log(property[val]);
}
}
Or in a modern environment you'd probably use Object.values or Object.entries instead:
const properties = Object.getOwnPropertyDescriptors(obj);
for (const [propName, descriptor] of Object.entries(properties)) {
console.log(`${propName}:`);
for (const [key, value] of Object.entries(descriptor)) {
console.log(` ${key}: ${value}`);
}
}
Live Example:
"use strict";
function Person() {
this._name = "";
this.age = 0;
this.greet = function() {
console.log(`hi Im ${this._name} and im ${this.age} years old`);
};
this.beep = function(times) {
for(var i = 0; i < times; i++) {
console.log("beeeep");
}
};
Object.defineProperty(this, "something", {
value: 10,
writable: false,
readable: true,
configurable: false,
enumerable: true,
});
Object.defineProperty(this, "name", {
get() {
return this._name;
},
set(nam) {
this._name = nam;
}
} );
}
var obj = new Person();
console.log(obj.something);
obj.name = "John Doe";
console.log(obj.name);
console.log("##############");
const properties = Object.getOwnPropertyDescriptors(obj);
for (const [propName, descriptor] of Object.entries(properties)) {
console.log(`${propName}:`);
for (const [key, value] of Object.entries(descriptor)) {
console.log(` ${key}: ${value}`);
}
}
.as-console-wrapper {
max-height: 100% !important;
}
Own answer, Also clearly showing what I wanted to do
Apparently its almost encouraged to answer own questions.
What threw me off was that one sort of needs to "backreference" inside the for loops. (Perhaps due to learning Java before).
To get property value one needs to mention the object that contains it.
descriptions[property]
and to get what is inside that you need in turn write
descriptions[property][whatever]
Anyways. If anyone wondered, heres the now working code, albeight with slightly different variable names. But then again, shorter and easyer to follow. Shows exactly what I was looking for :D
"use strict";
function User(name, age) {
this.name = name;
this.age = age;
this.func = function() {
console.log("hola");
};
}
var person = new User("John Doe", 22);
var descriptors = Object.getOwnPropertyDescriptors(person);
for(var property in descriptors) {
console.log(property);
for(var k in descriptors[property]) { //<-------------
console.log(k + " " + descriptors[property][k]);//<-----------
}
console.log("_______________");
}
I denoted what I previously didnt get with arrows in the comments.
Related
I have this code:
var properties = ["name", "sex", "location"];
o={};
for (var i=0, l=properties.length; i<l; i++) {
Object.defineProperty(o, properties[i],{
get: function () {return "hello";},
set: function(val){
console.log(properties[i] + ' is being set');
},
enumerable: true,
configurable: true
});
}
o.name = "something";
console.log(o.name);
It seems I can not use defineProperty to define setters and getters dynamicaly from a list because I need to write the body of the get function like:
return name;
and in the body of the set function
name = val;
so that the code works.
If i write something like
this[properties[i]] = val;
or
o[properties[i]]=val;
i end up in an infinite loop, my problem is I only have the name of the property as a string. Is eval() the only solution?
Does any of you know if it is possible to make this thing work. I did some research before and couldn't find any workaround.
Thank you in advance.
I complete my question here with something because someone wrote this:
"What u r trying to make is the action property. U can't set any value to these action properties. So u need to make another data properties to hold the corresponding data. e.g. -"
I know I can use other properties to hold the values but I want my code to run as if I would have defined them 1 by 1 like without having to store the values in other properties/variables, its stange to me that i can define them 1 by 1 and it works and when I try to define them dynamical I can't without extra properties/ variables in closures etc:
Object.defineProperty(o, "name",{
get: function () {return name;},
set: function(val){
name = val;
},
enumerable: true,
configurable: true
});
Object.defineProperty(o, "sex",{
get: function () {return sex;},
set: function(val){
sex = val;
},
enumerable: true,
configurable: true
});
Object.defineProperty(o, "location",{
get: function () {return location;},
set: function(val){
location = val;
},
enumerable: true,
configurable: true
});
Ok Guys and Girls I finally understood whats happening. The variables name, sex and location as I used them in the 1 by 1 definition were variable locations(properties defined on the window object and that's the only reason why the code worked I am clear now in my mind about the fact that I have to allocate memory to store the values somewhere else as you all explained that to me else it won't work at all, the best place imho is in a closure so that the values are private. It took me some time to understand that javascript does not have any internal value(place) when you are using a setter function and a getter function to hold that value the property is just bound to that accessor or descriptor object you set and uses that for further calculations.
Thank you all.
You could wrap the private values with an IIFE:
var properties = ["name", "sex", "location"];
var o = {};
for (var i = 0, l = properties.length; i < l; i++) {
(function (prop) {
var _val;
Object.defineProperty(o, prop, {
get: function () {
return _val;
},
set: function (val) {
_val = val;
},
enumerable: true,
configurable: true
});
})(properties[i]);
}
o.name = "something";
console.log(o.name);
How about doing something like this:
var properties = ["name", "sex", "location"];
o = {
state: {} // Saves the value of those properties dynamically.
};
for (var i = 0, l = properties.length; i < l; i++) {
Object.defineProperty(o, properties[i], {
get: function() {
return this.state[properties[i]];
},
set: function(val) {
this.state[properties[i]] = val;
console.log(this.state[properties[i]]);
},
enumerable: true,
configurable: true
});
}
o.name = "something";
console.log(o.name);
You need some place to save those properties, but you don't know it in advance, so I declared a kind of dictionary that holds the value of each property dynamically.
What u r trying to make is the action property. U can't set any value to these action properties. So u need to make another data properties to hold the corresponding data. e.g. -
var properties = ["name", "sex", "location"];
o={};
for (var i=0, l=properties.length; i<l; i++) {
Object.defineProperty(o, properties[i],{
get: function () {return this["_"+properties[i]];},
set: function(val){
this["_"+properties[i]] = val;
},
enumerable: true,
configurable: true
});
}
o.name = "something";
console.log(o.name);
I have class with property this.eyes. Code Below. As you can see in first case in data.num saved value in second reference. In my project I have no needs to array, so I need somehow to make first sample to save reference like second one. Any idea how?
// Warning! Pseudo Code
// Sample One
class Human {
constructor() {
this.eyes = null;
}
addEye() {
this.eyes = 1;
}
}
const william = new Human();
const data = { num: william.eyes }
william.addEye();
// data = { num: null }
// Warning! Pseudo Code
// Sample One
class Human {
constructor() {
this.eyes = [];
}
addEye() {
this.eyes.push(1);
}
}
const william = new Human();
const data = { num: william.eyes }
william.addEye();
// data = { num: [1] }
JavaScript has a small set of built-in types, one of those is number which is a "value type" (and as far as I can tell, your human.eyes value is always null or a number value).
JavaScript does not support references to number values, so after this data = { num: william.eyes } the data.num value will be a copy of william.eyes and not a reference as you have correctly surmised.
However, JavaScript does support non-trivial properties with custom getter/setter logic (Object.defineProperty). You could use this, in conjunction with a reference to the william object, to have the behaviour you want:
const william = new Human();
const data = {}; // new empty object
Object.defineProperty( data, 'num', {
enumerable: true,
configurable: true,
writable: true,
get: function() { return this.human.eyes; },
set: function(newValue) { this.human.eyes = newValue; }
} );
// Need to give `data` a reference to the `Human`:
data.human = william;
william.addEye();
william.addEye();
console.log( data.num ); // outputs "2"
data.num++;
console.log( william.eyes ); // outputs "3"
If I got what you trying to do here correctly , the following should work for you :
class Human {
constructor() {
this.eyes = 0;
}
addEye() {
this.eyes += 1;
}
}
Is there a way to assign properties of one object as references to the properties of another, and do so dynamically? Note that in the for loop, I've skipped any property that has the same name as the second object. I'm working on a framework that will cache JSON as objects with behaviors and allow ORM kind of behavior, where I can grab cached objects and collections as properties of other cached objects. I need to skip certain properties to avoid circular reference.
var obj1 = {
prop1: "hey",
obj2:"you",
prop2: "come over here"
}
var obj2 = {}
for(var prop in obj1){
if(prop != 'obj2'){
obj2[prop] = obj1[prop];
}
}
console.log(obj1);
console.log(obj2);
obj1.prop2 = "come on, man";
console.log(obj1);
console.log(obj2);
//obj1 is unchanged in output. I would like to be able to update it by mutating obj2's properties
fiddle: http://jsfiddle.net/6ncasLb0/1/
If this is not possible, is it possible to remove or mutate a property of a reference without mutating the original object? I know, probably not. Just a shot in the dark.
I guess the closest you can get to it, is to make sure the property you are changing it the same property you are getting on both objects, so you would need to do some work to make sure they "know" each other when they are instantiated (eg, clone from the original object)
As an example, you could use a simplified model like this, any properties marked in its creation would also update the original object, though new properties defined on the object should be fine). Note that enumrating and just referencing the properties wouldn't work, at least not with strings (objects would change when copied from 1 object to another)
;
(function(namespace) {
function addProperty(obj, property, valueholder) {
Object.defineProperty(obj, property, {
get: function() {
return valueholder[property];
},
set: function(val) {
valueholder[property] = val;
},
enumerable: true,
configurable: false
});
}
var model = namespace.model || function(options) {
if (typeof options === 'undefined') {
options = {};
}
var propHolder = options.container || {},
prop;
if (typeof options.props != null) {
for (prop in options.props) {
if (options.props.hasOwnProperty(prop)) {
addProperty(this, prop, propHolder);
propHolder[prop] = options.props[prop];
}
}
};
namespace.model.prototype.clone = function() {
var options = {
props: {},
container: propHolder
},
prop;
for (prop in this) {
if (this.hasOwnProperty(prop)) {
options.props[prop] = this[prop];
}
}
return new namespace.model(options);
};
namespace.model.prototype.toString = function() {
var prop, msg, props = [];
for (prop in propHolder) {
if (propHolder.hasOwnProperty(prop)) {
props.push(prop + ': "' + this[prop].toString() + '"');
}
}
return '[Model] {' + props.join(', ') + '}';
}
return this;
};
namespace.model = model;
}(window));
var obj1 = new model({
props: {
prop2: "come over here"
}
});
obj1.prop1 = 'Hey';
obj1.obj2 = 'You';
obj1.test = {
a: 10
};
var obj2 = obj1.clone();
console.log('-- before changes --');
console.log(obj1.toString());
console.log(obj2.toString());
obj2.prop2 = "come on, man";
obj2.prop1 = "won't change";
obj2.obj2 = "also not";
obj2.test.b = "both have this now";
console.log('-- after changes --');
console.log(obj1.toString());
console.log(obj2.toString());
console.log(obj1.test);
console.log(obj2.test);
Currently, I'm working on a project for my school whose purpose is to create an object pertaining to math; my object is similar to that of the Native Math object, and, as a result, I want to emulate certain aspects of it.
When using the console in Firefox, I have found that certain properties (e.g. E, PI, and SQRT2) cannot be edited (represented by a little lock on them). I know that there is a const declaration method, but I've tried both...
const obj = {
prop: function(x){
return x^3^4;
},
foo: "bar",
bar: "foo"
}
obj.prop = -3.14;
print(obj.prop); // prints "-3.14"
...and...
const unEditable = 2.718;
var obj = {e:unEditable};
obj.e = 3;
print(obj.e); // prints "3"
Is there a way to define properties of an object such that said properties cannot be edited by a user? By that, I mean could I assign obj a variable e with a value 2.718 so that when a person assigns obj.e a value of "Hello, world!, obj.e would still return 2.718?
Notes:
I have seen this question, which does not meet the needs of my question.
Code Fragmant
var Θ = {};
Θ.e = 2.71828;
Θ.pi = 3.14159;
Θ.fac = function(num){
if(!arguments.length) return NaN;
return (num<2)?(num<0)?Infinity:1:num*Θ.fac(num-1);
}
Θ.nroot = function(n,m){
return Θ.pow(n,1/m);
}
Conclusion
An answer based off of Wingblade's answer:
var Θ = {};
Object.defineProperties(Θ, {
pi: {
value: 3.14159,
writable: false
},
e: {
value: 2.71828,
writable: false
}
});
// rest of editable properties go after
You can use obj.defineProperty to add a property to an object in a more advanced way that offers more control over how the property will behave, for example if it is writeable or not.
More on this here: MDN Object.defineProperty()
EDIT: For defining multiple properties at once you can use Object.defineProperties() like so:
var o = {};
Object.defineProperties(o, {
"e": {
value: 2.71828,
writable: false
},
"pi": {
value: 3.14159,
writable: false
},
"fac": {
value: function(num){
if(!arguments.length) return;
return (num<2)?(num<0)?Infinity:1:num*o.fac(num-1);
},
writable: false
},
"nroot": {
value: function(n,m){
return o.pow(n,1/m);
},
writable: false
}
});
You can actually omit writeable: false for all properties, since it defaults to false when adding properties using Object.defineProperty, but it can be useful to leave it in for readability's sake (especially if you're new to this technique).
I have an object that contains a getter.
myObject {
id: "MyId",
get title () { return myRepository.title; }
}
myRepository.title = "MyTitle";
I want to obtain an object like:
myResult = {
id: "MyId",
title: "MyTitle"
}
I don't want to copy the getter, so:
myResult.title; // Returns "MyTitle"
myRepository.title = "Another title";
myResult.title; // Should still return "MyTitle"
I've try:
$.extend(): But it doesn't iterate over getters. http://bugs.jquery.com/ticket/6145
Iterating properties as suggested here, but it doesn't iterate over getters.
As I'm using angular, using Angular.forEach, as suggested here. But I only get properties and not getters.
Any idea? Thx!
Update
I was setting the getter using Object.defineProperty as:
"title": { get: function () { return myRepository.title; }},
As can be read in the doc:
enumerable true if and only if this property shows up during
enumeration of the properties on the corresponding object. Defaults to
false.
Setting enumerable: true fix the problem.
"title": { get: function () { return myRepository.title; }, enumerable: true },
$.extend does exactly what you want. (Update: You've since said you want non-enumerable properties as well, so it doesn't do what you want; see the second part of this answer below, but I'll leave the first bit for others.) The bug isn't saying that the resulting object won't have a title property, it's saying that the resulting object's title property won't be a getter, which is perfect for what you said you wanted.
Example with correct getter syntax:
// The myRepository object
const myRepository = { title: "MyTitle" };
// The object with a getter
const myObject = {
id: "MyId",
get title() { return myRepository.title; }
};
// The copy with a plain property
const copy = $.extend({}, myObject);
// View the copy (although actually, the result would look
// the same either way)
console.log(JSON.stringify(copy));
// Prove that the copy's `title` really is just a plain property:
console.log("Before: copy.title = " + copy.title);
copy.title = "foo";
console.log("After: copy.title = " + copy.title);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Syntax fixes:
Added missing variable declarations, =, and ;
Removed duplicate property title
Corrected the getter declaration syntax
If you want to include non-enumerable properties, you'll need to use Object.getOwnPropertyNames because they won't show up in a for-in loop, Object.keys, or $.extend (whether or not they're "getter" or normal properties):
// The myRepository object
const myRepository = { title: "MyTitle" };
// The object with a getter
const myObject = {
id: "MyId",
};
Object.defineProperty(myObject, "title", {
enumerable: false, // it's the default, this is just for emphasis,
get: function () {
return myRepository.title;
},
});
console.log("$.extend won't visit non-enumerable properties, so we only get id here:");
console.log(JSON.stringify($.extend({}, myObject)));
// Copy it
const copy = {};
for (const name of Object.getOwnPropertyNames(myObject)) {
copy[name] = myObject[name];
}
// View the copy (although actually, the result would look
// the same either way)
console.log("Our copy operation with Object.getOwnPropertyNames does, though:");
console.log(JSON.stringify(copy));
// Prove that the copy's `title` really is just a plain property:
console.log("Before: copy.title = " + copy.title);
copy.title = "foo";
console.log("After: copy.title = " + copy.title);
.as-console-wrapper {
max-height: 100% !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
First of all, fix your syntax, though it probably is good in your actual code:
myObject = {
id: "MyId",
get title () { return myRepository.title; }
}
Now, to the answer. :)
You can just use a for..in loop to get all the properties, then save them as-is:
var newObj = {};
for (var i in myObject) {
newObj[i] = myObject[i];
}
No jQuery, Angular, any other plugins needed!
I had the same issue but in TypeScript and the method mentioned by T.J. Crowder didnt work.
What did work was the following:
TypeScript:
function copyObjectIncludingGettersResult(originalObj: any) {
//get the properties
let copyObj = Object.getOwnPropertyNames(originalObj).reduce(function (result: any, name: any) {
result[name] = (originalObj as any)[name];
return result;
}, {});
//get the getters values
let prototype = Object.getPrototypeOf(originalObj);
copyObj = Object.getOwnPropertyNames(prototype).reduce(function (result: any, name: any) {
//ignore functions which are not getters
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
if (descriptor?.writable == null) {
result[name] = (originalObj as any)[name];
}
return result;
}, copyObj);
return copyObj;
}
Javascript version:
function copyObjectIncludingGettersResult(originalObj) {
//get the properties
let copyObj = Object.getOwnPropertyNames(originalObj).reduce(function (result, name) {
result[name] = originalObj[name];
return result;
}, {});
//get the getters values
let prototype = Object.getPrototypeOf(originalObj);
copyObj = Object.getOwnPropertyNames(prototype).reduce(function (result,name) {
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
if (descriptor?.writable == null) {
result[name] = originalObj[name];
}
return result;
}, copyObj);
return copyObj;
}