Reputation: 119
The following expressions both evaluate to true:
'z-index' in getComputedStyle(document.body) // true
Reflect.has(getComputedStyle(document.body), 'z-index') // true
In fact, the following also evaluate to true, which suggests that 'z-index' is the name of an own property of the corresponding CSSStyleDeclaration object:
getComputedStyle(document.body).hasOwnProperty('z-index') // true
Object.hasOwn(getComputedStyle(document.body), 'z-index') // true
However, this evaluates to false
Reflect.ownKeys(getComputedStyle(document.body)).includes('z-index') // false
How is it possible for Object.hasOwn(x, y)
to evaluate to true, yet Reflect.ownKeys(x).includes(y)
to evaluate to false? This does't make any sense.
Note also that the fact that getComputedStyle(document.body) is a read-only and computed CSSStyleDeclaration object is not why this is happening. The CSSStyleDeclaration object retrieved from the style property of an HTMLElement is not read-only, but the same thing happens:
Object.hasOwn(document.body.style, 'z-index') // true
Reflect.ownKeys(document.body.style).includes('z-index') // false
MDN says (https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle)
CSS property values may be accessed using the getPropertyValue(propName) API or by indexing directly into the object such as obj['z-index'] or obj.zIndex.
I understand the convenience of allowing accessing the value by obj['z-index']
. What I want to know is how the resulting divergence in truth-value between Object.hasOwn(x,y) and Reflect.ownKeys(x).includes(y) is even possible given the rules of the Ecmascript language.
Edit: I found another stackoverflow question that asks the very same thing (how does it work element.style["background-color"]) but it received no definitive answer.
Upvotes: 1
Views: 74
Reputation: 29042
You already analyzed the situation correctly.
However, there is nothing that would disallow this, and you can create such a situation yourself too, using a Proxy. For example:
function createCaseInsensitiveObject() {
return new Proxy({}, {
getOwnPropertyDescriptor (target, property) {
return Reflect.getOwnPropertyDescriptor(target, property.toLowerCase())
},
defineProperty (target, property, descriptor) {
return Reflect.defineProperty(target, property.toLowerCase(), descriptor)
},
has (target, property) {
return Reflect.has(target, property.toLowerCase())
},
get (target, property) {
return Reflect.get(target, property.toLowerCase())
},
set (target, property, value) {
return Reflect.set(target, property.toLowerCase(), value)
},
deleteProperty (target, property) {
return Reflect.deleteProperty(target, property.toLowerCase())
}
// Note: ownKeys is NOT overridden as it
// would be impractical to return all permutations
// of casing like hElLo etc.
})
}
const obj = createCaseInsensitiveObject()
obj.Hello = 123
console.log(obj.HELLO) // 123
console.log(obj.hElLo) // 123
console.log('hElLo' in obj) // true
console.log(Reflect.ownKeys(obj)) // ['hello']
console.log(Reflect.ownKeys(obj).includes('hElLo')) // false
This explains the discrepancy: the [[Has]]
internal method returns true
and the [[GetOwnProperty]]
internal method returns a valid descriptor, but the [[OwnPropertyKeys]]
internal method returns an array that does not contain that key.
The [[OwnPropertyKeys]]
internal function isn't meant to be used in such a way that you take the returned array and check whether it contains some key that you already know. It's meant to be iterated over in order to discover keys. Meaning: In reality, you would call Object.hasOwn
to check for existence for some specific property, and use Object.keys
(or, to include non-enumerable properties, Object.getOwnPropertyNames
) just for iterating over properties. And in that case, you'd have the CSSStyleDeclaration
behave nicely (every real property returned only once, but both ways of writing a property accepted when directly accessing a property whose name you already know).
Upvotes: 4