Reputation: 555
HTML-snippet
<div id="container">
<span class="no"></span>
<span class="yes"></span>
<span class="no"></span>
<span class="no"></span>
</div>
So, a getter, that returns an element .yes
and provides lazy evaluation (I think that's the term, right?
class Something{
constructor(){
this.elem = container;
}
get childYes(){
let span = this.elem.querySelector(".yes");
this.childYes = span;//Cannot set property clientName of #<Docum> which has only a getter
return span;
}
}
var object = new Something();
console.log(object.childYes);
But if I add an empty setter it works fine:
class Something{
constructor(){
this.elem = container;
}
get childYes(){
let span = this.elem.querySelector(".yes");
this.childYes = span;
return span;
}
set childYes(a){};
}
var object = new Something();
console.log(object.childYes); // <span class="yes"></span>
I don't need a setter there, what exactly does browser want from me?
Upvotes: 1
Views: 57
Reputation: 1074545
Cannot set property clientName of
#<Docum>
which has only a getter
By doing this.childYes = ...
on a property defined as an accessor, you're trying to use a setter. To define the property on the instance, use Object.defineProperty
:
get childYes(){
let span = this.elem.querySelector(".yes");
Object.defineProperty(this, "childYes", {
value: span
});
return span;
}
class Something{
constructor(){
this.elem = container;
}
get childYes(){
console.log("Getter ran");
let span = this.elem.querySelector(".yes");
Object.defineProperty(this, "childYes", {
value: span
});
return span;
}
}
var object = new Something();
console.log(object.childYes); // Runs the getter
console.log(object.childYes); // Uses the data property
<div id="container">
<span class="no"></span>
<span class="yes"></span>
<span class="no"></span>
<span class="no"></span>
</div>
In a comment you've asked:
So, correct me if I'm wrong: After object.childYes is called, the program looks for own
.childYes
property of object first; fails; goes to the prototype; finds getter; starts to execute the getter and when the linethis.childYes = span;
appears the program looks for it "here" e.g. in prototype and fails, right?
It's not because of where the this.childYes = span;
line is, but yes, other than that it's right. When you assign to an object property, what happens depends on whether the property exists on the object or its prototypes and, if it does, whether the property is a data property or an accessor property:
In your original code, you ended up at Step 3.2 above, because the property existed as an accessor property on the prototype.
Here's an example of those various scenarios:
"use strict";
// A function to tell us if an object has a property and, if so
// what kind of property it is
function getPropertyType(obj, propName) {
const descr = Object.getOwnPropertyDescriptor(obj, propName);
if (!descr) {
return "none";
}
if (descr.hasOwnProperty("get") || descr.hasOwnProperty("set")) {
return "accessor";
}
if (descr.hasOwnProperty("value") || descr.hasOwnProperty("writable")) {
return `data (${descr.value})`;
}
return "generic"; // Unlikely, but the spec allows for it
}
// An object to use as a prototype
const proto = {
dataProperty1: "dataProperty1 value",
_readWrite: "readWriteAccessor default value",
get readWriteAccessor() {
return this._readWrite;
},
set readWriteAccessor(value) {
this._readWrite = value;
},
get readOnlyAccessor() {
return "readOnlyAccessor value";
}
};
// Create an object using `proto` as its prototype
const obj = Object.create(proto);
console.log(`obj dataProperty2: ${getPropertyType(obj, "dataProperty2")}`);
console.log(`proto dataProperty2: ${getPropertyType(proto, "dataProperty2")}`);
console.log(`--- Before obj.dataProperty1 = "dataProperty1 updated";`);
console.log(`obj dataProperty1: ${getPropertyType(obj, "dataProperty1")}`);
console.log(`proto dataProperty1: ${getPropertyType(proto, "dataProperty1")}`);
obj.dataProperty1 = "dataProperty1 updated";
console.log(`--- After obj.dataProperty1 = "dataProperty1 updated";`);
console.log(`obj dataProperty1: ${getPropertyType(obj, "dataProperty1")}`);
console.log(`proto dataProperty1: ${getPropertyType(proto, "dataProperty1")}`);
console.log(`--- Before obj.dataProperty2 = "dataProperty2 updated";`);
console.log(`obj dataProperty2: ${getPropertyType(obj, "dataProperty2")}`);
console.log(`proto dataProperty2: ${getPropertyType(proto, "dataProperty2")}`);
obj.dataProperty2 = "dataProperty2 updated";
console.log(`--- After obj.dataProperty2 = "dataProperty2 updated";`);
console.log(`obj dataProperty2: ${getPropertyType(obj, "dataProperty2")}`);
console.log(`proto dataProperty2: ${getPropertyType(proto, "dataProperty2")}`);
console.log(`--- Before obj.readWriteAccessor = "readWriteAccessor updated";`);
console.log(`obj readWriteAccessor: ${getPropertyType(obj, "readWriteAccessor")}`);
console.log(`proto readWriteAccessor: ${getPropertyType(proto, "readWriteAccessor")}`);
obj.readWriteAccessor = "readWriteAccessor updated";
console.log(`--- After obj.readWriteAccessor = "readWriteAccessor updated";`);
console.log(`obj readWriteAccessor: ${getPropertyType(obj, "readWriteAccessor")}`);
console.log(`proto readWriteAccessor: ${getPropertyType(proto, "readWriteAccessor")}`);
console.log(`obj readOnlyAccessor: ${getPropertyType(obj, "readOnlyAccessor")}`);
console.log(`proto readOnlyAccessor: ${getPropertyType(proto, "readOnlyAccessor")}`);
console.log(`--- Before obj.readOnlyAccessor = "readOnlyAccessor updated";`);
try {
obj.readOnlyAccessor = "readOnlyAccessor updated"; // Would fail silently in loose mode, but we're using strict
console.log(`Worked!`);
} catch (e) {
console.error(`Assignment failed: ${e.message}`);
}
console.log(`--- After obj.readOnlyAccessor = "readOnlyAccessor updated";`);
console.log(`obj readOnlyAccessor: ${getPropertyType(obj, "readOnlyAccessor")}`);
console.log(`proto readOnlyAccessor: ${getPropertyType(proto, "readOnlyAccessor")}`);
.as-console-wrapper {
max-height: 100% !important;
}
Upvotes: 5