In javascript, we can add new properties to an object using dot notation
const obj = {}
obj.a = "hello"
console.log(obj) // prints { a: "hello" }
However, with dot notation it is not possible to add a property to a not yet existent object
obj.a.b = "hello" // <-- cannot set properties of undefined (setting 'b')
obj.a = { b: "hello" } // <-- OK
I would like to achieve this behaviour
const obj = {}
obj.a.b = "hello"
console.log(obj) // prints { a: { b: "hello" } }
My Idea
The only thing i could think of which could get close to this, would be using a proxy
const obj = new Proxy({}, {
set(target, key, receiver) {
// if a.b could make it here before the error is thrown, i'd handle this
// btw, this means that "key" should contain [a,b] which is not how this works.
}
})
obj.a.b = "hello"
The proxy idea cannot work and probably there is absolutely no way of changing JS's native behaviour like I'm asking but maybe I'm missing something instead?
A proxy does work. You need to use the get trap instead of set:
const obj = new Proxy({}, {
get(target, key, receiver) {
if (!(key in target)) return (target[key] = {});
return Reflect.get(target, key);
},
});
obj.a.b = "hello";
console.log(obj);
Related
Is there a way in JS to over-ride the default behavior of an object when it is de-structured?
// Normally destructing lifts properties from an object
const foo = {
a: 1,
b: 2,
};
const { a, b } = foo; // a = 1, b = 2
// I would like to have a method return the properties to be
// destructured
const bar = {
toObject: () => {
return { a, b };
},
};
const { a, b } = bar; // a = undefiner, b = undefined
I know that I could simply use const { a, b } = bar.toObject(); but that requires the consumer of the object to know how it's internals work and breaks the principle of least astonishment.
The closest thing I can think of to what I want is the toJSON magic method.
Nope. The specification requires the right hand side to resolve to a value that can be converted to an object via ToObject, which simply returns the object itself if it is passed one (i.e. no special method on the object is called to convert it to something else).
If you'd use array destructuring, that would work:
const [a, b] = {
*[Symbol.iterator]() {
yield "some"; yield "stuff";
}
};
You can make your toObject work as intended by decorating the target with a Proxy that intercepts ownKeys and get to fake an object for destructuring:
let withToObject = obj => new Proxy(obj, {
ownKeys(o) {
return Object.keys(o.toObject())
},
get(o, prop) {
return o.toObject()[prop]
}
});
let bar = withToObject({
aa: 11,
bb: 22,
cc: 33,
toObject() {
return {
a: this.aa,
b: this.bb
};
}
});
const {a, b} = bar;
console.log(a, b)
Of course, this affects not only destructuring, but also any other interaction with the object, like serialization, so you have to take measures to make these work too. For example, to support JSON, patch get like this:
get(o, prop) {
if (prop === 'toJSON')
return () => o; // or o.toObject(), whatever fits better
return o.toObject()[prop]
Is there a way I can get a property`s name inside the property itself?
I mean something like this:
let myObj = {
myProperty: {
name: <propertyName>.toString()
}
};
console.log(myObj.myProperty.name); // Prints `myProperty`
No, there isn't. There's nothing available when the object initializer is evaluated that provides that information.
Presumably if this were a one-off, you'd just repeat the name. If it's not a one-off, you could give yourself a utility function to do it:
// Define it once...
const addProp = (obj, name, value = {}) => {
obj[name] = value;
value.name = name;
return obj;
};
// Then using it...
let myObj = {};
addProp(myObj, "myProperty");
addProp(myObj, "myOtherProperty", {foo: "bar"});
console.log(myObj.myProperty.name);
console.log(myObj.myOtherProperty.name);
I have the following Object
{
a: "123"
b: "$a"
}
b should always have the value of a.
Any idea of how I can make it with JavaScript?
You could use a getter method for the property b with an object.
var obj = {
a: "123",
get b() {
return this.a;
}
};
console.log(obj.b);
Otherwise, you are looking for an ES6 feature, Proxy
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Then take the prop and check if the value contains a $ and return this value of this property.
var obj = { a: "123", b: "$a" },
p = new Proxy(obj, {
get: function(target, prop) {
return target[prop][0] === '$' ? target[target[prop].slice(1)] : target[prop];
}
});
console.log(p.b);
If you're meaning a real JavaScript object you can do this with a getter and a setter via Object.defineProperty().
Or you can create a "class"
var obj= new function()
{
this.a=1212,
this.b=this.a
}
console.log(obj.b)
I was wondering if it's possible in Javascript for an object property to have the following behaviour:
console.log(obj); // output 'Something'
console.log(obj.prop_a); // output 'A'
And also
var a = obj; // a === 'Something'
var b = obj.prop_a; /// b === 'A'
The object kind of have two versions, one when you access it directly, and one when you access one of it's children props
Thanks
Yes, you can override toString() method:
obj.toString = function() {
return "Something";
}
If you need this to work in the browser, you would have to hook the console, as well as override the toString method of the object.
You could hook the browser console, and redefine it afterwards:
var obj = {
prop_a: "A",
toString: function() {
return "Something";
}
};
var origConsole = console;
console = {
log: function(data) {
if (typeof data === "object") {
origConsole.log(data.toString());
} else {
origConsole.log(data);
}
}
}
console.log(obj);
Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?
Object.defineProperty( obj, 'prop', {
get: function () {
return val;
}
});
The result should be (for val = 'test'):
obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only
This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
var obj = {};
Object.defineProperty(obj, "prop", {
value: "test",
writable: false
});
As mentioned in the comments, the writable option defaults to false so you can omit it in this case:
Object.defineProperty(obj, "prop", {
value: "test"
});
This is ECMAScript 5 so won't work in older browsers.
In new browsers or node.js it is possible to use Proxy to create read-only object.
var obj = {
prop: 'test'
}
obj = new Proxy(obj ,{
setProperty: function(target, key, value){
if(target.hasOwnProperty(key))
return target[key];
return target[key] = value;
},
get: function(target, key){
return target[key];
},
set: function(target, key, value){
return this.setProperty(target, key, value);
},
defineProperty: function (target, key, desc) {
return this.setProperty(target, key, desc.value);
},
deleteProperty: function(target, key) {
return false;
}
});
You can still assign new properties to that object, and they would be read-only as well.
Example
obj.prop
// > 'test'
obj.prop = 'changed';
obj.prop
// > 'test'
// New value
obj.myValue = 'foo';
obj.myValue = 'bar';
obj.myValue
// > 'foo'
In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.
class SetOnlyOnce {
#innerObj = {}; // private field, not accessible from outside
getCurrentPropertyName(){
const stack = new Error().stack; // probably not really performant method
const name = stack.match(/\[as (\w+)\]/)[1];
return name;
}
getValue(){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] === undefined){
throw new Error('No global param value set for property: ' + key);
}
return this.#innerObj[key];
}
setValue(value){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] !== undefined){
throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
}
this.#innerObj[key] = value;
}
}
class GlobalParams extends SetOnlyOnce {
get couchbaseBucket() { return this.getValue()}
set couchbaseBucket(value){ this.setValue(value)}
get elasticIndex() { return this.getValue()}
set elasticIndex(value){ this.setValue(value)}
}
const _globalParams = new GlobalParams();
_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';
console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)
_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:
var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);
//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));
//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');
//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.
I hope it helps.
I tried and it Works ...
element.readOnly = "readOnly" (then .readonly-> true)
element.readOnly = "" (then .readonly-> false)