Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't defining the accessor descriptor of a property of computedStyle work?

Tags:

I wanted to detect value changes on the display CSS property by changes on the class attribute, and I came up with the below snippet.

I know getComputedStyle returns a read-only live CSSStyleDeclaration object, but as the object updates automatically when the element's styles are changed, I've assumed it assigns its properties somehow.

But it didn't call the getter and the setter. Why does this happen and then how does it assign its properties when it's read-only?

let parent = document.querySelector(".parent");
let child = parent.querySelector(".child");
let style = getComputedStyle(child);

let display = Symbol("display");
style[display] = style.display;

Object.defineProperty(style, "display", {
  get() {
    console.log("getter");
    return style[display];
  },
  set(value) {
    console.log("setter", value);
    style[display] = value;
  }
});

let button = document.querySelector("button");
button.addEventListener("click", () => {
  child.classList.toggle("hide");
});
.child {
  height: 100px;
  background-color: #80a0c0;
}

.hide {
  display: none;
}
<button>Toggle</button>
<div class="parent">
  <div class="child"></div>
</div>
like image 374
Changdae Park Avatar asked May 16 '20 11:05

Changdae Park


1 Answers

Host-provided objects are not required to play nice. :-) (Well, there are some requirements even on host-provided objects.)

Although on Chrome and related the object claims that the display property is a simple data property (on Firefox and Legacy Edge, it's much more reasonable, an accessor property on the prototype):

let style = getComputedStyle(document.querySelector(".child"));
let kind = "own";
do {
    const descr = Object.getOwnPropertyDescriptor(style, "display");
    if (descr) {
        console.log(kind + " property:", descr);
        break;
    }
    if (kind === "own") {
        kind = "prototype";
    } else {
        kind += "'s prototype";
    }
    style = Object.getPrototypeOf(style);
} while (style);
.child {
  height: 100px;
  background-color: #80a0c0;
}

.hide {
  display: none;
}
<button>Toggle</button>
<div class="parent">
  <div class="child"></div>
</div>

...it's lying. :-) (On Chrome.) Your is successfully creating an accessor property on the object, and then the object stops reflecting the changes to the underlying display property of the element (since it's just updating the property you've named with a symbol):

let parent = document.querySelector(".parent");
let child = parent.querySelector(".child");
let style = getComputedStyle(child);

let display = Symbol("display");
style[display] = style.display;

Object.defineProperty(style, "display", {
  get() {
    console.log("getter");
    return style[display];
  },
  set(value) {
    console.log("setter", value);
    style[display] = value;
  }
});

let button = document.querySelector("button");
button.addEventListener("click", () => {
  child.classList.toggle("hide");
  console.log("style.display: " + style.display);
});
.child {
  height: 100px;
  background-color: #80a0c0;
}

.hide {
  display: none;
}
<button>Toggle</button>
<div class="parent">
  <div class="child"></div>
</div>

You can replicate that same behavior with a Proxy:

const realStyle = {
    display: "block"
};
const style = new Proxy(realStyle, {
    get(target, propName) {
        return target[propName];
    },
    set(target, propName, value, receiver) {
        if (propName in target) {
            return;
        }
        return Reflect.set(target, propName, value, receiver);
    }
});

console.log("descriptor for `display`:");
console.log(Object.getOwnPropertyDescriptor(style, "display"));

console.log("style.display = " + style.display);
console.log("Setting it to 'none'");
style.display = "none";
console.log("style.display = " + style.display);

const d = Symbol("display");
style[d] = style.display;
Object.defineProperty(style, "display", {
    get() {
        console.log("getter");
        return style[d];
    },
    set(value) {
        console.log("setter");
        style[d] = value;
    }
});

console.log("style.display = " + style.display);
console.log("Setting it to 'none'");
style.display = "none";
console.log("style.display = " + style.display);
.as-console-wrapper {
    max-height: 100% !important;
}
like image 161
T.J. Crowder Avatar answered Oct 02 '22 15:10

T.J. Crowder