Metaprogramming
- Usually we write code that fiddles with data (strings, numbers, booleans etc), which is called programming. Metaprogramming is when we write code that fiddles with other code.
Properties of objects
Javascript objects have properties
. Each property has a value
attribute.
Let's take a simple javascript object: {a: 1, b:2}
. This object has two properties. Property a
of this object has value attribute equal to 1
.
But, actually, properties of javascript objects have three more attributes:
writable
- this attribute specifies whether value of property can be set.enumerable
- this specifies whether property name comes in when we loop the properties of the object usingfor/in
loop.configurable
- this attribute specifies whetherdelete object[name of the property]
is allowed and whether the attributes themselves can be modified.
The code example below demonstrates the above concept clearly:
let obj = {x: 1}
const propertyDescriptor = Object.getOwnPropertyDescriptor(obj, "x")
console.log(propertyDescriptor)
The above code snippet shows that property x
of obj
is writable, enumerable and configurable.
Types of Properties
Properties are themselves of two types. What we've talked about until now are data properties
. The other type is called accessor properties
. These are properties that are defined using setters and getters.
Just like data properties have four attributes as explained above, these too have four attributes viz. get
, set
, enumerable
and configurable
.
let obj1 = {
x: 1,
get y() {return this.x*2}
}
console.log(obj1.x, obj1.y)
console.log(Object.getOwnPropertyDescriptor(obj1, "y"))
obj1
in above code snippet has two properties x
and y
. Property x
is data property while y
is a accessor property. The output of above code shows the values of attributes of these properties. Note that set attribute of y
is undefined cause we haven't defined a setter. We can do that like so:
let obj1 = {
x: 1,
get y() {return this.x*2},
set y(value) {this.x = value}
}
console.log(obj1.x, obj1.y)
obj1.y = 2
console.log(obj1.x, obj1.y)
Accessor properties are just used to set and get values of data properties (and perform some other operation while doing so).
Defining attributes manually
We can have fine-grained control over the attributes of properties using Object.defineProperty
. Let's use that to see what happens when turn those attributes on or off.
let obj = {}
Object.defineProperty(obj, "x", {
value: 1,
writable: false,
enumerable: true,
configurable: true
})
// testing writable attribute
obj.x = 2
console.log(obj.x)
//testing enumerable attribute
console.log(Object.keys(obj))
// testing configurable attribute
delete obj.x
console.log(obj.x)
Try fiddling with the attributes above and seeing the output of the tests to get a clear idea of how the value of these attributes alter the behavior of objects.
Proxy
Proxies can be set up on an object to override their default functioning.
const props = {a:1, b:2}
const proxy = new Proxy(
props,
{
get(_, prop) {
console.log(prop)
},
}
)
console.log(proxy.b)