Overriding innerHTML property access - javascript

I am looking for a way to enable setter method on a DOM element. So basically I want to intercept all calls to this specific property. I've tried with defining get/set properties on a specific DOM element, but it isn't of much help:
(function(object, property) {
var __value;
Object.defineProperty(object, property, {
// Create a new getter for the property
get: function () {
console.log('access gettter');
return __value;
},
// Create a new setter for the property
set: function (val) {
__value = val;
}
})
})(document.body, 'innerHTML');
The result of this is that calling:
document.body.innerHTML = 'testValue';
is:
document.body.innerHTML
access gettter
"testValue"
However this doesn't have any impact on the page, I mean body of the page isn't changed, just the value of a private variable inside the closure is returned. Is there a way to achieve this functionality in JavaScript ?

Yes, this can be done. Here is the code in working form:
(function(object, property) {
var ownObjectProto = Object.getPrototypeOf(object);
// exit if bad property
if (!object[property]) {
console.error(property + " is not a property of " + object.toString());
return;
}
while (!Object.getOwnPropertyDescriptor(ownObjectProto, property)) {
ownObjectProto = Object.getPrototypeOf(ownObjectProto);
}
var ownProperty = Object.getOwnPropertyDescriptor(ownObjectProto, property);
Object.defineProperty(object, property, {
// Create a new getter for the property
get: function () {
console.log('access gettter');
return ownProperty.get.call(this);
},
// Create a new setter for the property
set: function (val) {
return ownProperty.set.call(this, val);
}
})
})(document.body, 'innerHTML');
You are not actually replacing the set and get functions here, but you are able to augment them. Seriously though, you shouldn't use this unless you have a very particular need for it, and you really know what you are doing. If not used properly, you could really screw up the functionality of plug-ins on your page or any other number of unintended consequences.
I also have a playground fiddle here: jsfiddle

Related

Is there a way to dynamically add getter to a class in javascript/typescript?

I need some guidance or expert knowledge on JavaScript capabilities.
I'm studying TypeScript ATM, specifically decorators functionality.
Is there a way to dynamically add a getter method to a Prototype object so that it is executed in place of the plain property access on an instance.
Here's some code for example:
class Car {
#decorate
color: string = 'red';
drive(): {
return 'Driving';
}
}
function decorate(target, key): void {
//would be cool to add a getter and update
//the prototype in target to contain such getter
//I know this won't work, but to get the idea.
target[key] = get function() {
console.log(`Accessing property: ${key}`);
return eval(`this.${key}`)
}
}
Then, when I would create and object and try to access .color
const car = new Car();
car.color;
ideally I would see at the console
Accessing property: color
You can use Proxy in JavaScript. As MDN states, it allows you to create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties. Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs, and so on.
class Car {
color = 'red'
drive() {
return 'Driving'
}
}
const proxy = new Proxy(new Car(), {
get(target, key) {
console.log(`Accessing property: ${key}`);
return Reflect.get(target, key)
}
})
proxy.color // prints "Accessing property: color" and returns value of color.
In plain javascript you could use Object.defineProperty to dinamicaly add getters and setters to object.
That was my inital comment.
If you will to decorate only certain fields of the object then MDN example would be the easiest way to this properly:
const o = {a: 0};
Object.defineProperty(o, 'b', { get() { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)

Purpose of getters and setters in Javascript classes

I have been learning how to use classes in JavaScript, and something that always confused me was the how getters and setters work. I think I now finally understand them, is the below explanation correct?
They are no different to normal methods, and simply provide an alternative syntax.
A getter is simply an alternative to a method which cannot have a parameter, and means you don't have to use () to call, e.g.:
get myGetter() { return { msg: "hello" } };
...
classInstance.myGetter.msg; // "hello"
Is equivalent to:
myGetter() { return { msg: "hello" } };
...
classInstance.myGetter().msg; // "hello"
A setter is simply an alternative for a method that does take a parameter, e.g.:
set mySetter(value) { this.value = value };
...
classInstance.mySetter = "hello";
Is equivalent to:
mySetter(value) { this.value = value };
...
classInstance.mySetter("hello");
Functionally, that explanation is mostly correct, however they also have a more semantic meaning. Getters/setters are very useful for updating things that depend on a value or calculating a value, but they shouldn't be used for triggering actions. For example, this is a wrong usage of a getter:
const alerter = new Alerter;
// [...]
alerter.alert = "Hi there!"; // Alerts "Hi there!"
This is a good one:
const player = new Player;
// [...]
player.health--; // Also updates the health bar
It's also worth noting that, while in most circumstances, they behave like methods, they aren't methods at all! They are part of properties.
In JS, properties can have data descriptors and accessor descriptors. Data descriptors are "normal" properties. They have a value and you can get/set it.
const obj = {
prop: 1;
};
console.log(obj.prop); // Get; logs 1
obj.prop = 2; // Set
Accessor descriptors don't hold a value, and allow for setting what happens when the property is get and set.
const obj = {};
Object.defineProperty(obj, "prop", {
get() {
console.log("Getter was called");
return 1;
},
set(v) {
console.log("Setter was called with the value %o.", v)
}
});
/* Alternative syntax:
class Example {
get prop() {
console.log("Getter was called");
return 1;
}
set prop(v) {
console.log("Setter was called with the value %o.", v)
}
}
const obj = new Example;
*/
console.log(obj.prop); // Get; logs 1
obj.prop = 2; // Set
That code logs:
Getter was called
1
Setter was called with the value 2.
There is a huge difference between getters/setters and normal properties, in their most simple form you could think of them as an alternative syntax. however getters/setters provide more convenient solutions for certain use cases - though eventually getters/setters and methods are properties, getters/setters has accessor descriptors while methods has data descriptors.
I'm gonna list some few use cases on top of my head
getters/setters enable you to trigger custom functionality when reading/setting a property without having to create two different methods
let xThatShouldBeHidden = 1;
const object = {
get x() {
return xThatShouldBeHidden
},
set x(newX) {
if (newX === 0) {
throw new Error('You can not set x to 0')
}
xThatShouldBeHidden = newX
}
}
Triggering custom functionality is a cool feature, it enables you to do optimizations while still abstracting that behind simple syntax.
Imagine you you have array of items that has values, and later you want to get the weight of the item (value / sum of all values of items)
const items = [{val: 2}, {val:4}]
one way to do it would be which required you to loop twice even if eventually the weight was read from only one item
const totalSum = items.reduce((acc,cur), acc + cur.val,0));
const itemsWithWeights = items.map(item => ({...item, weight: item.val / totalSum});
now with getters we do it in one loop plus number of actual reads
const getItemsWithWeightsGetter = () => {
let totalSum;
return items.map(item => ({
...item,
get weight() {
if (totalSum === undefined) {
totalSum = items.reduce((acc, cur) => acc + cur.val, 0);
}
return item.val / totalSum;
},
}));
};
const itemsWithWeightsGetter = getItemsWithWeightsGetter();
another use case is the example i just shared above, when you provide just a getter that makes the value read only, making code throws when you try to set the value - in strict mode only
The difference is, you can have a getter/setter pair with the same name. For example, when working with DOM, there is the innerHTML getter/setter pair.
const element = document.querySelector("div")
console.log(element.innerHTML) // Outputs HTML as string
element.innerHTML = "Hello!" // Sets the HTML of element to "Hello!"

Disable property mutation in JS

I was creating a component and was trying to break my implementation. The idea is to not allow user to manipulate the exposed properties.
The implementation was like this:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data; },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, setting the property is handled but user is still able to mutate it using .push. This is because I'm returning a reference.
I have handled this case like:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data.slice(); },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, I'm returning a new array to solve this. Not sure how it will affect performance wise.
Question: Is there an alternate way to not allow user to mutate properties that are of type object?
I have tried using writable: false, but it gives me error when I use it with get.
Note: I want this array to mutable within class but not from outside.
Your problem here is that you are effectively blocking attempts to modify MyClass. However, other objects members of MyClass are still JavaScript objects. That way you're doing it (returning a new Array for every call to get) is one of the best ways, though of course, depending of how frequently you call get or the length of the array might have performance drawbacks.
Of course, if you could use ES6, you could extend the native Array to create a ReadOnlyArray class. You can actually do this in ES5, too, but you lose the ability to use square brackets to retrieve the value from a specific index in the array.
Another option, if you can avoid Internet Explorer, is to use Proxies (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
With a proxy, you can trap calls to get properties of an object, and decide what to return or to do.
In the example below, we create a Proxy for an array. As you see in the handler, we define a get function. This function will be called whenever the value of a property of the target object is accessed. This includes accessing indexes or methods, as calling a method is basically retrieving the value of a property (the function) and then calling it.
As you see, if the property is an integer number, we return that position in the array. If the property is 'length' then we return the length of the array. In any other case, we return a void function.
The advantage of this is that the proxyArray still behaves like an array. You can use square brackets to get to its indexes and use the property length. But if you try to do something like proxyArray.push(23) nothing happens.
Of course, in a final solution, you might want decide what to do based on which
method is being called. You might want methods like map, filter and so on to work.
And finally, the last advantage of this approach is that you keep a reference to the original array, so you can still modify it and its values are accessible through the proxy.
var handler = {
get: function(target, property, receiver) {
var regexp = /[\d]+/;
if (regexp.exec(property)) { // indexes:
return target[property];
}
if (property === 'length') {
return target.length;
}
if (typeof (target[property]) === 'function') {
// return a function that does nothing:
return function() {};
}
}
};
// this is the original array that we keep private
var array = [1, 2, 3];
// this is the 'visible' array:
var proxyArray = new Proxy(array, handler);
console.log(proxyArray[1]);
console.log(proxyArray.length);
console.log(proxyArray.push(32)); // does nothing
console.log(proxyArray[3]); // undefined
// but if we modify the old array:
array.push(23);
console.log(array);
// the proxy is modified
console.log(proxyArray[3]); // 32
Of course, the poblem is that proxyArray is not really an array, so, depending on how you plan to use it, this might be a problem.
What you want isn't really doable in JavaScript, as far as I'm aware. The best you can hope for is to hide the data from the user as best you can. The best way to do that would be with a WeakMap
let privateData = new WeakMap();
class MyClass {
constructor() {
privateData.set(this, {
data: []
});
}
addEntry(entry) {
privateData.get(this).data.push(entry);
}
getData() {
return privateData.get(this).data.concat();
}
}
So long as you never export privateData don't export from the module, or wrap within an IIFE etc.) then your MyClass instances will be able to access the data but external forces can't (other than through methods you create)
var myInstance = new MyClass();
myInstance.getData(); // -> []
myInstance.getData().push(1);
myInstance.getData(); // -> []
myInstance.addEntry(100);
myInstance.getData(); // -> [100]

Programmatically defining Javascript properties in the object constructor

I have a Javascript constructor with an object containing all of its fields, and functions that read/write to them:
function myObj(){
var _settingsStore {
setting1: 1, //default value
setting2: 2, //default value
}
//gets/sets _settingsStore.settingName, does other stuff
function _genericSet(settingName, settingValue){}
function _genericGet(settingName){}
I'd like to programmatically create properties around each of the fields:
for (var _setting in _settingsStore){
var _str = _setting.toString();
Object.defineProperties(this, {
_str: {
get: function() {return _genericGet(_str)},
set: function(value) {return _genericSet(_str, value)}
}
})
}
My intent is for _str to be purely a variable, and that myObj.setting1 and myObj.setting2 should be defined. Instead, the loop attempts to define myObj._str twice:
Uncaught TypeError: Cannot redefine property: _str
What's going on? And is there a way to get my desired behavior?
EDIT: To clarify, this is what I effectively want to happen, just unrolled outside of the loop:
Object.defineProperties(this, {
settings1: {
get: function() {return _genericGet("settings1")},
set: function(value) {return _genericSet("settings1", value)}
},
settings2: {
get: function() {return _genericGet("settings2")},
set: function(value) {return _genericSet("settings2", value)}
},
//and so on, if there are more of them
})
I'm starting over. If you just want to use setters and getters that do some extra work, and each object has its on store hidden in the constructor, and you wanted to reuse some generic functions, you could do this:
function myObj(){
var _settingsStore = {
setting1: 1, //default value
setting2: 2, //default value
}
for (var _setting in _settingsStore){
makeGetSet(this, _setting, _settingStore);
}
}
function makeGetSet(obj, key, store) {
Object.defineProperty(obj, key, {
get: function() { return _genericGet(key, store)}
set: function(val) {_genericSet(key, val, store)}
})
}
function _genericGet(key, store){
var val = store[key];
// manipulate the val
return val
}
function _genericSet(key, val, store){
// manipulate the new val
store[key] = val
}
I really don't know what sort of manipulations you're doing when getting/setting, so some of this could maybe be shortened.
Also, you could make the _settingsStore generic, and just use the variable scope for the value storage since you need that anyway.
You're misunderstanding object literals.
{ _str: blah } is an object with a property named _str; it doesn't use the value of the _str variable.
Therefore, you were actually defining the same name every time.
Since you didn't set configurable: true, that threw an error.
Instead, you should call Object.defineProperty (singular), which takes a string followed by a property name.
(alternatively, you could build an object to pass to defineProperties and give it the correct name using indexer notation)
However, your code still won't work correctly, since all of the callbacks will close over the same variable (Javascript does not have block scope).
To solve that, you need to wrap each iteration in a separate function.

How can I define a default getter and setter using ECMAScript 5?

How can I specify a default getter for a prototype?
With default getter I mean a function that is called if obj.undefinedProperty123 is called.
I tried Object.prototype.get = function(property) {..} but this is not called in this case.
In ECMAScript 5, you can only intercept get/set operations on specific named properties (not universally all properties) via Object.defineProperty:
Object.defineProperty(someObj, "someProp", {
get: function() {
console.log("you tried to get someObj.someProp");
return "foo";
}
});
Here, the get function will run any time code tries to read someObj.someProp.
In the upcoming ECMAScript 6 draft, this will be possible via proxies. A proxy has an underlying target object and set/get functions. Any time a set or get operation happens on any of a proxy's properties, the appropriate function runs, taking as arguments the proxy's target object, property name used, and the value used in a set attempt.
var proxyHandler = {
get: function(obj, name){
console.log("you're getting property " + name);
return target[name];
},
set: function(obj, name, value) {
console.log("you're setting property " + name);
target[name] = value;
}
}
var underlyingObj = {};
// use prox instead of underlyingObj to use get/set interceptor functions
var prox = new Proxy(underlyingObj, proxyHandler);
Here, setting to getting property values on prox will cause the set/get functions to run.
What Gareth said, except it's __noSuchMethod__.
Or maybe you were thinking of PHP?
Here's a very good article on the recently standardized property getters/setters, highlighting some previous non-standard incarnations.
http://whereswalden.com/2010/04/16/more-spidermonkey-changes-ancient-esoteric-very-rarely-used-syntax-for-creating-getters-and-setters-is-being-removed/
summary: there's no standard 'catch-all' getter / setter (yet), but Object.defineProperty is the future.
You need to wait for the implementation of the ECMA6 "Proxy" system, designed to do exactly this. See http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies.
You want to create a Proxy:
const data = {};
const proxy = new Proxy(data, {
get: (target, prop) => {
console.log({ target, prop });
return "My Value";
},
set: (target, prop, value) => {
console.log({ target, prop, value });
return true;
},
});
proxy["foo"] = "bar";
const bar = proxy["foo"];
Firefox it's possible with non-standard noSuchMethod:-
({__noSuchMethod__:function(){alert(1);}}).a();
I am not sure about what you are asking. But If you want a method to be called when the user attempts to Access object.nonExistingProperty . I dont think there is any way to do that.
maybe late to ther party, let just add simple ES5 friendly "class creation": both string props and getters - i needed it for some ancient rewrite, to temporarily support IE (yes, that hurts, still found someone relying on ActiveX)
var MyObj = function () {
var obj = {
url: 'aabbcc',
a: function(){ return this.url;}
}
Object.defineProperty(obj, "urltoo", { get: function () { return this.url; } })
return obj;
}
var X = new MyObj();
x.url // aabbcc
x.urltoo // aabbcc
x.urltoo() // error - not a function
x.a() // aabbcc
x.a // ƒ (){ return this.url;}
I ran into this question because I wanted this behavior: if object property is undefined, return some default value instead.
const options = {
foo: 'foo',
bar: 'bar',
baz: 'baz'
};
function useOptionValue(optionName) {
// I want `options` to return a default value if `optionName` does not exist
const value = options[optionName];
// etc...
}
The simple way to do this (without Proxy or Object.defineProperty overkill for this use case) is like so:
function useOptionValue(optionName) {
const value = options[optionName] || defaultValue;
}
Or, if you want to get fancy, you could use a Symbol:
const default = Symbol.for('default');
const options = {
foo: 'foo',
bar: 'bar',
baz: 'baz',
[default]: 'foobarbaz'
};
function useOptionValue(optionName) {
const value = options[optionName] || options[default];
}

Categories

Resources