JavaScript is dynamic. Cool !
I have the following constructor function :
function Preferences () {
this._preferences = {}
}
var obj = new Preferences()
I want to achieve something like this:
>>> obj.something = 'value'
>>> this._preferences['something']
'value'
That is setting the property of the obj does not actually set it's own property but that of obj._preferences. That is I want to override the default behavior.
Is it possible ?
EDIT : I want to achieve this for all property names i.e the name of the property to be set is not already known.
Object.defineProperty(Preferences.prototype, 'something', {
get: function(){
return this._preferences.something;
},
set: function(value){
this._preferences.something = value;
}
});
should do it. It defines a property, 'something', using an accessor property instead of a data property, and will call the 'get' and 'set' functions to decide what do so when .something is accessed.
SOLUTION 1
Using Proxy object you can do something like this and handle runtime defined properties
function Preferences() {
this._preferences = {};
var prefProxy = new Proxy(this, {
get: function(target, property) {
return property in target?
target[property]:
target._preferences[property];
}
set: function(target, property, value, receiver) {
if(property in target){
target[property] = value;
} else {
target._preferences[property] = value;
}
}
});
return prefProxy;
};
SOLUTION 2
I can be wrong but i think what you are asking is solved returning _preferences
function Preferences () {
this._preferences = {};
return _preferences;
}
var obj = new Preferences()
SOLUTION 3
Using getter and setter you can redirect the property to _preferences
function Preferences () {
this._preferences = {}
Object.defineProperty(Preferences.prototype, 'something', {
get: function() {
return this._preferences['something'];
},
set: function(value) {
this._preferences['something'] = value;
}
});
}
var obj = new Preferences()
Related
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)
I would like to create a TypeScript decorator that can extend the logic of a property's getter/setter. I have tried to copy the original property under a symbol and call that when I redefine the property. The problem is it turns into an infinite loop.
//Find the latest version of 'attribute' getter setter in the prototype chain
let obj = _object;
while(obj && !(Object.getOwnPropertyDescriptor(obj, 'attribute'))){
obj = Object.getPrototypeOf(obj);
}
//Copy original 'attribute' logic under a symbol
const attributeDesc = Object.getOwnPropertyDescriptor(obj, 'attribute');
let id=Symbol('__attribute');
Object.defineProperty(obj, id, attributeDesc);
//Redefine 'attribute' logic
Object.defineProperty(_object, 'attribute', {
get: () => {
//call original
const attribute = obj[id]; //It crashes the page (probably infinite loop)
//extend original logic
attribute['extend'] = 'property';
return attribute;
},
enumerable: false,
configurable: true
});
If you could explain me why it ends up this way that would help me out. I thought the new getter function reference nothing to do with the original. Please suggest me a solution to achive this in JavaScript.
Thank you for your time and answers!
I don't quite see the error. In the repro you provided, it's logical that there is one: the getter for attribute property is calling itself on the line var attributes = obj[id], so there is an infinite loop. However if you edit your code to be like the snippet you provided in the question:
class A {
get attribute() {
return { a: 1 }
}
}
var _object = new A()
let obj = _object
while (obj && !Object.getOwnPropertyDescriptor(obj, 'attribute')) {
obj = Object.getPrototypeOf(obj)
}
const attributeDesc = Object.getOwnPropertyDescriptor(obj, 'attribute')
let id = Symbol('__attribute')
Object.defineProperty(obj, id, attributeDesc)
Object.defineProperty(obj, 'attribute', {
get: function () {
var attributes = obj[id]
attributes['extend'] = 'property'
return attributes
},
enumerable: false,
configurable: true,
})
console.log('result:', obj.attribute)
There is no error and it works as expected.
You don't really need the symbol though, you could do something like
function extendAttributes(_object) {
let obj = _object
while (obj && !Object.hasOwnProperty(obj, 'attributes')) {
obj = Object.getPrototypeOf(obj)
}
if(!obj) return;
const oldContainer = {}
const attributesDescriptor = Object.getOwnPropertyDescriptor(obj, 'attributes')
Object.defineProperty(oldContainer, 'attributes', attributesDescriptor)
Object.defineProperty(obj, 'attributes', {
get() {
const attribute = oldContainer.attributes;
//extend original logic
attribute['extend'] = 'property';
return attribute;
}
})
}
class A {
get attributes() { return {a: 1} }
}
const obj = new A()
extendAttributes(obj)
console.log(obj.attributes)
Which also works like expected
Let there be an object userSingleton defined as such:
var userSingleton = new function() {
var _user = undefined;
Object.defineProperty(this, 'activeUser', {
get: function() {
console.log("Getter called, done something cool");
return _user;
},
set: function(val) {
console.log("Setter called, do something cooler");
_user = val;
}
});
}
Now if I go to use it, userSingleton.activeUser = {name: 'John Doe'}; works great! I get a "Setter called, do something cooler".
However, if I try to do userSingleton.activeUser.name = 'John Doe'; I instead get a "Getter called, done something cool" and userSingleton._user is not updated.
What's happening is it's trying to set the name property of the object returned by the getter (userSingleton.activeUser).
How do I make it call a particular function when any (unknown at definition time) property is assigned to / modified?
A revisited solution Proxy based with a deeply nested example (NB: ECMA Script 2015):
var handler = {
get: function (target, key) {
return target[key];
},
set: function (target, key, value) {
do_action(target, key, value);
if (typeof value === 'object') {
target[key] = new Proxy(value, handler);
} else {
target[key] = value;
}
}
};
function do_action(target, key, value) {
console.log("firing action on:", key, value)
}
function singletonUser() {
if (!this._singleton) {
const _user = {}
this._singleton = {
activeUser: new Proxy(_user, handler)
};
}
return this._singleton;
}
var userSingleton = singletonUser();
userSingleton.activeUser.name = 'pippo';
userSingleton.activeUser.age = 10;
// a deeply nested example
userSingleton.activeUser.github = {};
userSingleton.activeUser.github.followers = ["gino", "pino"]
I'm building a javascript library and I would like to be able to do exactly like the PHP's __get does.
My library has a attributes property which stores each model's attributes. Now, I am force to get an attribute using a .get method. But I would be able to do it with a getter. Let's says that User extends my model class.
let instance = new User({firstname: 'John', lastname: 'Doe'});
console.log(instance.get('firstname')); // gives me 'John'
I want to be able to do instance.firstname which will call the .get method passing 'firstname' as parameter. In PHP you can do it that way : http://php.net/manual/fr/language.oop5.overloading.php#object.get
Is this something possible?
Thank you all
This is easy using ES 2015 classes:
class Foo {
constructor () {
this._bar = null;
}
get bar () {
doStuff();
return this._bar;
}
set bar (val) {
doOtherStuff();
this._bar = val;
return this;
}
};
var foo = new Foo();
foo.bar = 3; // calls setter function
console.log(foo.bar); // calls getter function
here's the (simplified) output from babel:
var Foo = function () {
function Foo() {
this._bar = null;
}
_createClass(Foo, [{
key: "bar",
get: function get() {
doStuff();
return this._bar;
},
set: function set(val) {
doOtherStuff();
this._bar = val;
return this;
}
}]);
return Foo;
}();
Note that this isn't just for classes, any arbitrary object can have these:
var baz = {
get qux() {
// arbitrary code
},
set qux(val) {
// arbitrary code
}
};
Source.
EDIT
What you want is possible but only in native ES 6 environments, as Proxy cannot be polyfilled.
var getter = function(target, property, proxy) {
console.log(`Getting the ${property} property of the obj.`);
return target[property];
};
var setter = function(target, property, value, proxy) {
console.log(`Setting the ${property} property to ${value}.`);
target[property] = value;
};
var emptyObj = {};
var obj = new Proxy(emptyObj, {
get: getter,
set: setter
});
obj.a = 3; // logs 'Setting the a property to 3'
var foo = obj.a; // logs 'Getting the a property of the obj'
Quite simply assign the properties in a loop:
User = function (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
User.prototype = {
// further methods
}
Using the ES6 class syntax, - I have to admit I do not see the point of writing things this way:
class User {
constructor (attrs) {
for (var name in attrs) {
this[name] = attrs[name];
}
}
// further methods
}
Remember: the second syntax is exactly what happens with the first one, only with some sugar on top.
I am using the Typed.React library which includes a method to extend one prototype definition with that of another:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
proto[key] = clazz.prototype[key];
}
return proto;
}
If the provided class defines property methods, this function has a side effect of executing the get method e.g.
var TestObject = (function () {
function TestObject() {
this.str = "test string";
}
Object.defineProperty(TestObject.prototype, "TestProperty", {
get: function () {
console.log("exec get");
return this.str;
},
set: function (value) {
console.log("exec set");
this.str = value;
},
enumerable: true,
configurable: true
});
return TestObject;
})();
var extracted = extractPrototype(TestObject);
When extactPrototype accesses TestObject.prototype["TestProperty"], it will execute the property get method and print:
exec get
How would I duplicate a prototype with property methods without executing them?
I think you are looking for the new ES6 Object.assign function.
Of course there's a simpler fix to your problem - just don't access and set properties, copy their property descriptors:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
Object.defineProperty(proto, key, Object.getOwnPropertyDescriptor(clazz.prototype, key));
}
return proto;
}