I am messing around with some "classical" inheritance and I'm running into an issue. I am using Object.defineProperty() to add properties to my LivingThing "class". I want to have a default value, along with a property getter/setter.
http://jsfiddle.net/fmpeyton/329ntgcL/
I am running into the following error:
Uncaught TypeError: Invalid property. A property cannot both have accessors and be writable or have a value, #<Object>
Why am I getting this error and what would be the best approach to have a default value and a getter/setter for a property, using Object.defineProperty()?
Use a function scoped variable to back the defined property and set that variable's initial value to the default:
function LivingThing(){
self = this;
var isAlive = true;
Object.defineProperty(self, 'isAlive', {
get: function(){
return isAlive;
},
set: function(newValue){
isAlive = newValue;
},
configurable: true
});
self.kill = function(){
self.isAlive = false;
};
}
http://jsfiddle.net/329ntgcL/5/
writable isn't necessary because you have a setter. That's what's causing your error. You can either have value/writable (data descriptor) OR get/set (accessor descriptor).
As a result, when you call var l = new LivingThing, l.isAlive == true and after you call l.kill(), l.isAlive == false
Your getter/setter pair presents a computed property, you can (and should) back that up with a real property if you need storage:
self._isAlive = true;
Object.defineProperty(self, 'isAlive', {
get: function(){
return this._isAlive;
},
set: function(newValue){
this._isAlive = newValue;
},
writable: true,
configurable: true
});
Presumably, put some logic there to justify a setter/getter vs a regular property. Setters are for wrapping property access.
Without a setter and getter, you can define a default value by just assigning it to the prototype:
LivingThing.prototype.isAlive = true
var o = new LivingThing()
console.log(o.isAlive) // true
Just to be clear, changing that property on an instance of LivingThing will create a property for that instance, not change the value in its __proto__.
o.isAlive = false
console.log(new LivingThing().isAlive) // still true
Related
I mistakenly used Object.defineProperty by pass a function as its descriptor param, just like blow code:
let fakeDesc = () => {}
let obj = {
method1: function() {
console.log('this is method1');
}
};
Object.defineProperty(obj, 'method1', fakeDesc);
obj.method1();
the code evaluated result is that method1 is not overrided.
according to the MDN doc(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
If a descriptor has neither of value, writable, get and set keys, it is treated as a data descriptor.
so I think that the fakeDesc should be treated like a data descriptor, and the default value property should be undefined, so the above code would result in obj.method1 equal undefined.
How about this situation? and is there some document can explain this issue?
The passed descriptor (the third argument) is supposed to be a plain object with properties like value and writable. If you pass a function instead of an object, it gets ignored.
A data descriptor is a property that has a value, which may or may not be writable.
If you pass a function, you're not passing a data descriptor.
If you pass an object with a value property of your fakeDesc, it'll work as expected:
let fakeDesc = () => {}
let obj = {
method1: function() {
console.log('this is method1');
}
};
Object.defineProperty(obj, 'method1', { value: fakeDesc });
obj.method1();
When you pass an object without a value property, the underlying value won't change, although the descriptor on that property may change. For example, the following changes enumerable to false on method1, despite not changing the underlying value (of the this is method1 function):
let obj = {
method1: function() {
console.log('this is method1');
}
};
console.log(Object.getOwnPropertyDescriptor(obj, 'method1'));
Object.defineProperty(obj, 'method1', { enumerable: false });
console.log(Object.getOwnPropertyDescriptor(obj, 'method1'));
I suppose you could technically pass a function and have it change the descriptor, but you would have to set a property directly on the function, which is really weird:
const fakeFn = () => {};
fakeFn.enumerable = false;
let obj = {
method1: function() {
console.log('this is method1');
}
};
console.log(Object.getOwnPropertyDescriptor(obj, 'method1'));
Object.defineProperty(obj, 'method1', fakeFn);
console.log(Object.getOwnPropertyDescriptor(obj, 'method1'));
obj.method1();
When MDN says that the default values for the different keys (like configurable and enumerable) are false, and the default value for value is false, it's referring to the process when you create a property that doesn't exist on the object already. The process is defined in DefineOwnProperty.
For the following syntax
a = {
get p() {
alert(1)
}
};
alert(a.p);
It prompts me 1, than undefined.
For
a = {
set p(x) {
alert(x)
}
};
alert(a.p);
It prompts me undefined.
I do not totally understand the behaviour,
what does
a = {
get p() {
alert(1)
}
}
and
a = {
set p(x) {
alert(x)
}
};
mean?
There are two types of object properties: data properties and accessor properties. Accessor properties are accessed by getters and setters.
Your object a is intended as object with accessor property which called p.
Normally, such objects are declared in the following way:
a = {
_p: 'my value', // intended as private
get p() {
return this._p;
},
set p(x) {
this._p = x;
}
};
console.log(a.p); // gives 'my value'
a.p = 'new value';
console.log(a.p); // gives 'new value'
Another way is to use Object.defineProperty() method which lets you to set all needed properties attributes. Like this:
var a = {p: 'my value'};
Object.defineProperty(a, 'p', {
get: function() { return this.p; },
set: function(newValue) { this.p = newValue; },
enumerable: true,
configurable: true
});
because p() method returns nothing hence it returns undefined
if you do
a={get p(){alert(1); return 2;}};
alert(a.p);
it will alert 1 and then 2 since this p() method returned 2
{get p(){alert(1)}}
this is an object that has a getter p
when you use a.p it will use that getter to retrieve some value
so when you do alert(a.p); it first call the getter, so alert 1, then alert the returned value undefined.
[Edit] - You changed your original question so this answer doesn't cover everything.
p is a getter function - It is called whenever you access the p property. In your getter you have alert(1).
The getter function doesn't return anything. Thus, p is undefined. So the alert(a.p) alerts undefined.
Thus, your program does:
Get value of a.p: alert(a.p)
Calls p getter function. Which has alert(1)
p getter function returns nothing
Thus alert(a.p) alerts undefined
When using:
Object.defineProperty(obj,prop,desc){
get: function(){...
set: function(){...
}
Does the getter/setter apply to obj[prop] or does it act on obj no matter what property is specified?
I am asking because I'm trying to setup some data binding based on a nested object like:
obj[propA] = {propB:'seomthing',propC:'somethingElse'}
and when I do something like this:
var obj = {value:{propA:'testA',propB:'testB'}};
Object.defineProperty(obj.value,'propA',{
get: function(){return this.value;},
set: function(newValue){this.value=newValue;console.log('propA: ',newValue);}
});
console.log(obj.value.propA);
obj.value.propA = 'testA';
Object.defineProperty(obj.value,'propB',{
get: function(){return this.value;},
set: function(newValue){this.value=newValue;console.log('propB: ',newValue);}
});
console.log(obj.value.propB);
obj.value.propB = 'testB';
console.log('propA: ',obj.value.propA,' --propB: ',obj.value.propB);
the getter assigns the value to ALL the properties set by defineProperty within the object.
If this is the correct functionality, is there a way to have the getter/setter work only on the property defined such that in the fiddle above, propA would yield testA and propB would yield testB?
The getter and setter only apply to the named property, but this inside each one refers to the object whose property it is (you don’t have to have a backing variable for every property).
In your example, you’re always reading and modifying obj.value.value. You can create a different variable for each one by wrapping each in an IIFE, for example:
(function () {
var value;
Object.defineProperty(obj.value, 'propA', {
get: function () { return value; },
set: function (newValue) { value = newValue; },
});
})();
Updated fiddle
I know how to use JS getters and setters for object properties like so
var myObject = {
value : 0,
get property() {
return this.value;
},
set property(v) {
this.value = v;
}
}
so that calling myObject.property = 2 will set myObject.value, but what I'm wondering is if there is some way to call myObject = 2 and still set myObject.value rather than changing myObject from an object into a number.
It's probably not possible, but javascript is an incredibly flexible language and I thought I'd pose the question to the community before I discarded the idea.
It is possible indeed. Only for global variables though.
Object.defineProperties(this, {
myObject: {
get: function () {
return myObjectValue;
},
set: function (value) {
myObjectValue = value;
},
enumerable: true,
configurable: true
},
myObjectValue: {
value: 0,
enumerable: false,
configurable: true,
writable: true
}
});
myObject = 5;
console.log(myObject);
console.log(delete myObject);
Now, every time you assign a value to myObject, it shall actually run the set function and assign the value to the other property instead. Now, if you wanted to minimize pollution, you could create an IIFE and use variables inside that instead to hold the values, per se.
http://jsbin.com/zopefuvi/1/edit
And here is the version with the IIFE.
http://jsbin.com/puzopawa/1/edit
Just as a side note. I didn't know until reading this question that you could define getters/setters without Object.defineProperty. I was wondering at what the difference was between this 'shorthand' method, and defining getters/setters via Object.defineProperty.
I found, using the myObject example in the question:
var myObject = {
value : 0,
get property() {
return this.value;
},
set property(v) {
this.value = v;
}
}
that Object.getOwnPropertyDescriptor(myObject, 'property')
returns
get: ƒ property()
set: ƒ property(v)
enumerable: true
configurable: true
So worth mentioning is that property in this case is enumerable, meaning it will show up in loops.
Object.create is a great addition to JavaScript, because it adheres more to the prototypical nature of JS. However, I can't help but find the syntax of the 2nd parameter to the function to be too verbose, and a step back.
For example, if I want to create an object, and specify a new property in the derived object, I need to include that property value within a property object, regardless if I'm interested in the extra features or not.
So, something as simple as this:
o = Object.create({}, { p: 42 })
Now becomes:
o = Object.create({}, { p: { value: 42 } })
Obviously this is a simple example, but to me the verbosity is unnecessary, and should be optional.
Does anyone understand the decision to require a properties object? What is your opinion of the requirement of the new syntax?
Note: I understand there are easy solutions to overcome this requirement.
The syntax is done this way so that you can add parameters that control each property:
So, when you do this:
o = Object.create({}, { p: { value: 42 } })
you are saying that you want a property named p with a value of 42. The key here is that there are other parameters you can set for each property and if there wasn't this extra level of object hierarchy, you wouldn't have any way to pass those extra parameters.
So, for example, you could also do this:
o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } });
Here's you are not just specifying the value of 42, but also some options for that property. If there wasn't the extra level of object hierarchy here, then you wouldn't have a place to put those extra options.
Yes, it does seem inconvenient when you only want the simple case. But, you could easily write yourself a helper function that made the simpler syntax work:
function myCreate(proto, props, enumerable, writable, configurable) {
// last three arguments are optional - default them to true if not present
if (typeof enumerable === "undefined") {enumerable = true;}
if (typeof writable === "undefined") {writable = true;}
if (typeof configurable === "undefined") {configurable = true;}
var wrapper = {};
for (var i in props) {
wrapper[i] = {
value: props[i],
enumerable: enumerable,
configurable: configurable,
writable: writable
};
}
return(Object.create(proto, wrapper));
}
Demo here: http://jsfiddle.net/jfriend00/vVjRA/