Javascript object descriptors - javascript

Is it possible to use getters/setters on Javascript objects like the following basic functionality mockup?
function Descriptor(x) {
this._value = x;
this.setter = function(x) {
// Set internal value after some action or modification
this._value = x + 1;
}
this.getter = function() {
// Return the internal value
return this._value;
}
}
var obj = {};
obj.a = new Descriptor();
obj.a = 5; // Would run the setter defined in the Descriptor object
obj.a == 6; // Should evaluate to true in this trivial example
// Same as above, just an example of being able to easily reuse the getter/setter
obj.b = new Descriptor();
obj.b = 10;
obj.b == 11;
Ultimately, it should operate similarly to a Python descriptor set in a class definition. The only things I can find that accomplish something like this requires that the getter/setter be hooked up during the above obj creation, and cannot be easily reused on multiple attributes or objects.

You can try ES5 Object.defineProperty:
function addDescriptor(obj, prop) {
var value = 0;
Object.defineProperty(obj, prop, {
get: function(x) {
return value;
},
set: function(x) {
value = x + 1;
}
});
}
var obj = {};
addDescriptor(obj, 'a');
obj.a = 5;
obj.a == 6; // true
addDescriptor(obj, 'b');
obj.b = 10;
obj.b == 11; // true

I don't know what your environment is (it's only tagged javascript), but ES6 Proxies provide this flexibility of get/set
copied from MDN:
var handler = {
get: function(target, name){
return name in target?
target[name] :
37;
}
};
var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37

You can either use Object.defineProperty to define getter and setter, or simply do it in an object initialiser:
var obj = {
value: 0,
get a(){
return this.value;
},
set a(x){
this.value = x + 1;
}
};
I believe you are trying to do something like this: (Another way of doing what Oriol has done in his answer.)
Object.prototype.Descriptor = function(name){
var value = 0;
Object.defineProperty(this, name, {
get: function(){ return value; },
set: function(x){ value = x + 1; }
});
};
var obj = {};
obj.Descriptor("a");
obj.Descriptor("b");
obj.a = 5;
obj.b = 10;
obj.a //6
obj.b //11
obj.value //undefined
Everything is separated, such that the function can be reused meanwhile values are kept separated from all other object's value.

Related

Javascript Accessor properties confusion

I am not sure why this code gives me an error. All I want to do is create an object that has an array as a property. I want to achieve this with a setter and getter but for some reason when I do this.array = [] inside the setArray function I get Maximum call stack size exceeded. What am I doing wrong ? What am I missing about Javascript's accessor properties.
var obj = {
set array(size) {
this.array = [];
for (var i = 0; i < size; i++){
this.array[i] = i;
}
}
};
var myObj = Object.create(obj);
myObj.array = 20;
You're assigning to a property with a setter from within the setter, which is why it recurses forever:
var obj = {
set array(size) { // <== The property is called `array`
this.array = []; // <== This calls the setter
for (var i = 0; i < size; i++){
this.array[i] = i; // <== This would fail because there's no getter
}
}
};
You have to store the value elsewhere, for instance here we create a private scope and close over a variable:
var obj = (function() {
var array = [];
return {
set array(size) {
array = []; // No `this` here
for (var i = 0; i < size; i++) {
array[i] = i; // No `this` here
}
},
showArray() {
console.log(array);
}
};
})();
var myObj = Object.create(obj);
myObj.array = 20;
myObj.showArray();
You asked about avoiding the function. You need the function to make the value of the property truly private. But if you don't want to use it for some reason, and you're not that bothered about it being truly private, you can do what Zoltán Tamási did in his answer: Use another property on the same object. His answer uses a _ prefix on the underlying property (e.g., _array), which is a very common convention in JavaScript for "leave this alone, it's 'private'" even though there are no private properties. The fact is that even in languages with truly private properties (like Java, where they're called instance fields), there's usually some form of powerful reflection mechanism you can use to get around it, so...
If you do that, one thing to consider is whether you want that private property included in JSON if you serialize. If not, just implement toJSON as well so you can control which properties are included during serialization:
var obj = {
_array: [],
foo: "a value to include in JSON",
set array(size) {
this._array = []; // Note the _
for (var i = 0; i < size; i++) {
this._array[i] = i;
}
},
showArray() {
console.log(this._array);
},
toJSON() {
return {
// Here we list only the properties we want to have
// included in the JSON
foo: this.foo
};
}
};
var myObj = Object.create(obj);
myObj.array = 20;
myObj.showArray();
console.log(JSON.stringify(myObj));
You are using the setter inside the setter itself when you write this.array = []. Introduce another member called for example _array and fill that in the setter of the array property.
var obj = {
_array: [],
set array(size) {
for (var i = 0; i < size; i++){
this._array[i] = i;
}
}
};
var myObj = Object.create(obj);
myObj.array = 20;
You had mentioned that you wanted to use setters and getters, this snippet just uses obj.array instead of showArray to view the object's array.
var obj = (function() {
var _array = [];
return {
get array(){
return _array;
},
set array(size) {
_array = [];
for (var i = 0; i < size; i++) {
_array[i] = i;
}
}
};
})();
obj = Object.create(obj);
obj.array = 20;
console.log(obj.array);

How to iterate over an object's prototype's properties

I have some code:
var obj = function() { }; // functional object
obj.foo = 'foo';
obj.prototype.bar = 'bar';
for (var prop in obj) {
console.log(prop);
}
What surprised me is that all that is logged is foo. I expected the for loop to iterate over the properties of the obj's prototype as well (namely bar), because I did not check for hasOwnProperty. What am I missing here? And is there an idiomatic way to iterate over all the properties in the prototype as well?
I tested this in Chrome and IE10.
Thanks in advance.
You're iterating over the constructor's properties, you have to create an instance. The instance is what inherits from the constructor's prototype property:
var Ctor = function() { }; // constructor function
Ctor.prototype.bar = 'bar';
var obj = new Ctor(); // instantiation
// adds own property to instance
obj.foo = 'foo';
// logs foo and bar
for (var prop in obj) {
console.log(prop);
}
If you want to maintain an inheritance hierarchy by defining all the properties even before the object instantiation, you could follow the below approach. This approach prints the prototype hierarchy chain.
Note: In this approach you don't have to create the constructor initially.
function myself() {
this.g = "";
this.h = [];
this.i = {};
myself.prototype = new parent();
myself.prototype.constructor = myself;
}
function parent() {
this.d = "";
this.e = [];
this.f = {};
parent.prototype = new grandParent();
parent.prototype.constructor = parent;
}
function grandParent() {
this.a = "";
this.b = [];
this.c = {};
}
var data = new myself();
var jsonData = {};
do {
for(var key in data) {
if(data.hasOwnProperty(key) && data.propertyIsEnumerable(key)) {
jsonData[key] = data[key];
}
}
data = Object.getPrototypeOf(data).constructor.prototype;
Object.defineProperties(data, {
'constructor': {
enumerable: false
}
});
} while (data.constructor.name !== "grandParent")
console.log(jsonData);

Extending an object by reference

I have an extend method that extends objects like you would expect it would in JavaScript:
var objA = {a: true};
var objB = extend({b:false}, objA);
console.log(objB); // {a: true, b: false}
However, I would like to extend the source's (objA) properties onto the target (objB) object by reference so that any changes made to the source are reflected in the target after the fact, like so:
var objA = {a: true};
var objB = extend({b: false}, objA);
console.log(objB); // {a: true, b:false}
objA.a = false;
console.log(objB); // {a: false, b:false}
For instance, when you modify an object (which is always assigned by reference) things are working the way I would like them to:
var objA = {A:{a: true}};
var objB = _.extend({b: false}, objA);
console.log(objB) // {A:{a:true}, b:false};
objA.A.a = false;
console.log(objB); // {A:{a:false}, b:false};
So in other words, when changes are made to objA's non-object literal properties (or any value that isn't assigned by reference) I would like those changes to be reflected in objB.
I'm fairly sure this isn't exactly possible without an additional helper method that would depend on some sort of object watch method that is triggered whenever an object changes.
Thoughts?
Code from my extend method:
(l).extend = (l).merge = function () {
var args = (l).fn.__args(arguments, [{
deep: 'bool'
}, {
'*': 'obj:object'
}
]),
target = (l)._object || args.obj,
keys = [],
obj, objs, copy, key, i, o;
// Collect potential objects to merge
objs = (l).filter(args, function (value, index) {
if (index !== "obj" &&
(l).isPlainObject(value) && !((l).isEqual(target, value))) {
return value;
}
});
// When target object is not selected globally
if (!args.obj) {
target = objs.shift();
}
// When target object is not selected globally and only a single object
// is passed extend the library itself
if (!(l)._global && !objs.length) {
target = this;
objs[0] = args.obj;
}
// When a boolean is passed go deep
if (args.deep) {
// Build property reference used to prevent never ending loops
(l).each(objs, function (index, value) {
keys.push((l).keys(value));
keys = (l).flatten(keys);
});
// Add properties to all nested objects
(l).deep(target, function (depth, index, obj, ref) {
if ((l).indexOf(keys, index) === -1) {
for (i = 0; i < objs.length; i++) {
for (key in objs[i]) {
if ((l).isPlainObject(obj)) {
copy = objs[i][key];
obj[key] = copy;
}
}
}
}
}, "*");
}
// Merge first level properties after going deep
for (i = 0; i < objs.length; i++) {
if ((obj = objs[i]) !== null) {
for (key in obj) {
copy = obj[key];
if (target === copy) {
continue;
}
target[key] = copy;
}
}
}
return (l).fn.__chain(target);
};
What are you mentioned here is exactly the prototypal inheritance of JavaScript:
var objA = {a: false};
var objB = Object.create(objA);
objB.b = false;
console.log(objB.a, objB.b); // true,
objA.a = false;
console.log(objB.a, objB.b); // false, false
Of course if objB overrides the a property, you will lose that link, because JavaScript will found the property a in the object objB, therefore won't look up in the prototype's chain.
Using the ECMAScript 5 methods, you could have your extend function like that:
function extend(properties, proto) {
var object = Object.create(proto);
var descriptor = {};
Object.getOwnPropertyNames(properties).forEach(function(name) {
descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
});
return Object.defineProperties(object, descriptor);
}
var objA = {a: true};
var objB = extend({b: false}, objA);
console.log(objB.a, objB.b); // true, false
objA.a = false;
console.log(objB.a, objB.b); // false, false
Object.extend= function (properties,obj) {
function F() { };
F.prototype = obj;
var newObj = new F();
for (var prop in properties){
newObj[prop] = properties[prop];
}
return newObj;
}

in javascript, when constructing an object, unable to use other objects' methods

when constructing an object using methods from other objects as an attribute name get Syntax Error: Unexpected token . - cannot find correct syntax
var R = function(a) { this.arg = a; };
R.prototype.name = function() { return this.arg; }
var r1 = new R('abc');
var name1 = r1.name(); // => "abc"
var o1 = { 'abc': r1 } // this works with constant
var o2 = { name1: r1 } // does not work with variable (see answer)
var o3 = { r1.name(): r1 } // this fails - syntax
var o4 = { 'abc': r1.name() } // this works
have tried { (r1.name()): r1 }, but that fails as well.
please note that strings and integers are evaluated as barewords whereas methods and variables are not:
var o5 = { e.1: 123 } // fails
var o6 = { 'e.1': 123 } // succeeds
var o7 = { 1: 123 } // succeeds
var o8 = { '1': 123 } // same as o7
var o2 = { name1: r1 } // this works with variable
This has the same meaning as:
var o2 = { 'name1': r1 }
In other words, it's still treating name1 as a literal string. The key in an object literal must be a constant or a so-called "bareword" -- this is a restriction of the language. Variables and function invocations cannot be used as keys in object literals (but they can be used as values). Using variables will appear to work, but the variable name will be used as the key and not the variable's value.
You will have to do something like:
var o2 = {};
o2[name1] = r1;
var o3 = {};
o3[r1.name()] = r1;
var o4 = { 'abc', r1.name() }
this one should be:
var o4 = { 'abc': r1.name() }
You could create your own property setter
function setProperty (prop,val,obj) {
if (typeof prop === "undefined" || typeof val === "undefined") return undefined;
if (typeof obj === "undefined") obj = {};
obj[prop] = val;
return obj;
}
// use 1
var o1 = setProperty(r1.name(),r1);
// use 2
var o2 = {};
setProperty(r1.name(),r1,o2);

Set length property of JavaScript object

Let's say I have a JavaScript object:
function a(){
var A = [];
this.length = function(){
return A.length;
};
this.add = function(x){
A.push(x);
};
this.remove = function(){
return A.pop();
};
};
I can use it like so:
var x = new a();
x.add(3);
x.add(4);
alert(x.length()); // 2
alert(x.remove()); // 4
alert(x.length()); // 1
I was trying to make .length not a function, so I could access it like this: x.length, but I've had no luck in getting this to work.
I tried this, but it outputs 0, because that's the length of A at the time:
function a(){
var A = [];
this.length = A.length;
//rest of the function...
};
I also tried this, and it also outputs 0:
function a(){
var A = [];
this.length = function(){
return A.length;
}();
//rest of the function...
};
How do I get x.length to output the correct length of the array inside in the object?
You could use the valueOf hack:
this.length = {
'valueOf': function (){
return A.length;
},
'toString': function (){
return A.length;
}
};
Now you can access the length as x.length. (Although, maybe it's just me, but to me, something about this method feels very roundabout, and it's easy enough to go with a sturdier solution and, for example, update the length property after every modification.)
If you want A to stay 'private', you need to update the public length property on every operation which modifies A's length so that you don't need a method which checks when asked. I would do so via 'private' method.
Code:
var a = function(){
var instance, A, updateLength;
instance = this;
A = [];
this.length = 0;
updateLength = function()
{
instance.length = A.length;
}
this.add = function(x){
A.push(x);
updateLength();
};
this.remove = function(){
var popped = A.pop();
updateLength();
return popped;
};
};
Demo:
http://jsfiddle.net/JAAulde/VT4bb/
Because when you call a.length, you're returning a function. In order to return the output you have to actually invoke the function, i.e.: a.length().
As an aside, if you don't want to have the length property be a function but the actual value, you will need to modify your object to return the property.
function a() {
var A = [];
this.length = 0;
this.add = function(x) {
A.push(x);
this.length = A.length;
};
this.remove = function() {
var removed = A.pop();
this.length = A.length;
return removed;
};
};
While what everyone has said is true about ES3, that length must be a function (otherwise it's value will remain static, unless you hack it to be otherwise), you can have what you want in ES5 (try this in chrome for example):
function a(){
var A = [],
newA = {
get length(){ return A.length;}
};
newA.add = function(x){
A.push(x);
};
newA.remove = function(){
return A.pop();
};
return newA;
}
var x = a();
x.add(3);
x.add(4);
alert(x.length); // 2
alert(x.remove()); // 4
alert(x.length); // 1
You should probably use Object.create instead of the function a, although I've left it as a function to look like your original.
I don't think you can access it as a variable as a variable to my knoledge cannot return the value of a method, unless you will hijack the array object and start hacking in an update of your variable when the push/pop methods are called (ugly!). In order to make your method version work I think you should do the following:
function a(){
this.A = [];
this.length = function(){
return this.A.length;
};
this.add = function(x){
this.A.push(x);
};
this.remove = function(){
return this.A.pop();
};
};
These days you can use defineProperty:
let x = {}
Object.defineProperty(x, 'length', {
get() {
return Object.keys(this).length
},
})
x.length // 0
x.foo = 'bar'
x.length // 1
Or in your specific case:
Object.defineProperty(x, 'length', {
get() {
return A.length
}
})
function a(){
this.A = [];
this.length = function(){
return this.A.length;
};
this.add = function(x){
this.A.push(x);
};
this.remove = function(){
return this.A.pop();
};
};

Categories

Resources