Say you defined an object const obj = { awesome: true } so that anywhere within your code, you can access the value of obj.awesome as well as modify its value via obj.awesome = false.
Question: How do you know where and when obj.awesome is being accessed or modified?
So why is this important? If you are using frontend framework that does 2-way binding, eg: Angular, Vue, do you know how does the framework “watch” your state object? How does the framework knows when to update your DOM when you set some property of the state object?
// setting a property in the `$scope` object triggers
// the framework to update the model and the DOM
$scope.name = 'Hello';Answer: You use Object.defineProperty().
Object.defineProperty(obj, prop, descriptor) allows us to define a property to an object with a descriptor for the property being defined.
Well, nothing special about defining a property of an object, you can do it easily with obj[prop] = value. What so special about this Object.defineProperty() is the descriptor object that you pass in. Lets' take a look at what can be configured through the descriptor:
enumerabletrueif and only if this property shows up during enumeration of the properties on the corresponding object.Defaults to
false.If you have a property's
enumerableset tofalse, meaning you will not see the property when you doObject.keys(obj)orfor (const key in obj) { ... }writabletrueif and only if the value associated with the property may be changed with an assignment operator.Defaults to
falseThis allows us to create
read-onlyproperty of an object.getA function which serves as a getter for the property, or
undefinedif there is no getter. The return value will be used as the value of the property.Defaults to
undefinedsetA function which serves as a setter for the property, or
undefinedif there is no setter. When the property is assigned to, this function is called with one argument (the value being assigned to the property).Defaults to
undefined
So, there you have it. What you need is to define the getter and setter function of the property, and they will be called when the property is accessed or being assigned to a value.
You can add a breakpoint via debugger; in your getter and setter function, to invoke the debugging feature of your development tools to look at the call stack.
Another way of looking at the call stack without using a debugger is to throw an Error in the getter and setter function.
Yes. You hear me right. Throwing an error will allow you to get the call stack when the error is thrown:
let _value;
Object.defineProperty(obj, 'awesome', {
get: () => {
try {
// intentionally throw an Error to get the call stack
throw new Error();
} catch (error) {
// stack is the stack trace,
// containing error message and the stack
const stack = error.stack;
// print the callee stack
console.log(
stack
.split('\n')
.slice(2)
.join('\n')
);
}
return _value;
},
});