Add 3 new articles

This commit is contained in:
Angelos Chalaris
2023-03-04 14:28:19 +02:00
parent 038e9d8f3c
commit eb3a1ad72b
3 changed files with 200 additions and 0 deletions

View File

@ -0,0 +1,32 @@
---
title: "Tip: You can't extend the Proxy object"
shortTitle: Extending the Proxy object
type: tip
tags: javascript,object,proxy
author: chalarangelo
cover: icebreaker
excerpt: Turns out the Proxy object is not extensible, but there's a way around its limitations.
firstSeen: 2023-04-17T05:00:00-04:00
---
While the [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object seems like it can be extended by any other class in JavaScript, that's not the case. This is due to proxy objects having very atypical semantics and being considered **exotic objects**. Simply put, this means they do not have a prototype and are not extensible.
So how do you extend a proxy object? You don't. You can, however, create a class that returns a proxy by returning it from the constructor. After all, this is probably the sort of behavior you're after.
```js
class MyProxy {
constructor(value) {
Object.keys(value).forEach(key => (this[key] = value[key]));
return new Proxy(this, {
set(object, key, value) {
console.log(`Called with ${key} = ${value}`);
object[key] = value;
return true;
}
});
}
}
const myProxy = new MyProxy({ a: 1 });
myProxy.b = 2; // LOGS: 'Called with b = 2'
```

View File

@ -0,0 +1,75 @@
---
title: Can I create dynamic setters and getters in JavaScript?
shortTitle: Dynamic getters and setters
type: question
tags: javascript,object,proxy
author: chalarangelo
cover: green-cabin-cow
excerpt: Using the Proxy object, we can create dynamic getters and setters for objects in JavaScript.
firstSeen: 2023-04-09T05:00:00-04:00
---
Sometimes, when working with objects, the shape of the data is not always known. It might also be inefficient to add special getters for each property of an object, especially if the object is very large. Moreover, if keys are expected to follow a pattern, there are infinite potential key names, the value of which is impossible to validate via the use of setters.
These are only some use-cases where **dynamic setters and getters** could be useful. Luckily, JavaScript's `Proxy` object can be used for this purpose. Using the `get` and `set` traps, we can manipulate the object's behavior when a property is accessed or set. In this post, we will look at two simple examples to give you an idea of how this works.
Note that, in contrast to these examples, a `Proxy` object can define multiple traps to intercept many different operations on the target object.
### Dynamic getters
A **dynamic getter** is a getter that is not explicitly defined for a property, but is instead created on the fly when the property is accessed. This is particularly useful when the shape of the data is not known in advance, or when the value of a property needs to be manipulated before it is returned.
In this example, we will be creating a proxy that will manipulate string values in the target object. The proxy will trim any string values that are accessed, and return the value as-is for any other type of value. Finally, non-existent properties will return `undefined`, as expected.
```js
const obj = { foo: 'bar ', baz: ' qux ', quux: 1 };
const proxiedObj = new Proxy(obj, {
get(target, prop) {
if (prop in target && typeof target[prop] === 'string')
return target[prop].trim();
return target[prop];
}
});
proxiedObj.foo; // 'bar'
proxiedObj.baz; // 'qux'
proxiedObj.quux; // 1
proxiedObj.quuz; // undefined
```
While this is a simple example, it highlights the power of the `Proxy` object. In this case, we are able to manipulate the behavior of the object without having to define a getter for each property. This will also apply to any new properties that are added to the object, as the proxy will be able to intercept the access and return the appropriate value.
### Dynamic setters
A **dynamic setter** is a setter that is not explicitly defined for a property, but is instead created on the fly when the property is set. This can be very useful if the object's keys follow a certain pattern or certain conditions apply to all values that are set.
In this example, we will be creating a proxy that will only allow setting of properties that correspond to a date in the format `yyyy-mm-dd`. Additionally, if a property is already set, its value should be impossible to change. This could be useful if, for example, you were creating something akin to a read-only log.
```js
const obj = {};
const proxiedObj = new Proxy(obj, {
set(target, prop, value) {
if (prop in target) return false;
if (typeof prop === 'string' && prop.match(/^\d{4}-\d{2}-\d{2}$/)) {
target[prop] = value;
return true;
}
return false;
}
});
proxiedObj['2023-01-01'] = 1;
proxiedObj['2023-01-01'] = 2; // This will fail, the property is already set
proxiedObj['2023-ab-cd'] = 1; // This will fail, the property name is not a date
proxiedObj; // { '2023-01-01': 1 }
```
As shown in this example, the `Proxy` object can be used to validate keys as well as values when setting properties. In this case, we were able to prevent the value of a property from being changed, as well as prevent the setting of properties that do not follow the expected pattern.
As a side note, remember that the regular expression used here is not a full date validation, but only checks for a simple pattern to demonstrate the concept. If you need to validate dates in a production environment, this is not the way to go.
### Conclusion
As shown in this post, the `Proxy` object provides a particularly powerful way to manipulate the behavior of objects. That being said, you might want to consider your specific use-case before reaching for this tool. Dynamic getters and setters can be very useful, but they can also cause a lot of headaches if used incorrectly.

View File

@ -0,0 +1,93 @@
---
title: Typechecking objects with Proxy in JavaScript
shortTitle: Object typechecking with Proxy
type: story
tags: javascript,object,type,proxy
author: chalarangelo
cover: customs
excerpt: A simple way to typecheck objects at runtime using the Proxy object.
firstSeen: 2023-04-23T05:00:00-04:00
---
A while back, I was working on a project where some objects had **rigid structure requirements**. As I was really not in the mood to use TypeScript, I decided to create a typechecking mechanism for objects using the `Proxy` object.
Drawing inspiration from React's `PropTypes`, I created a handful of **type checking functions** for the most common types.
```js
const bool = v => typeof v === 'boolean';
const num = v => typeof v === 'number' && v === v;
const str = v => typeof v === 'string';
const date = v => v instanceof Date;
```
The next step was to decide on how an **object's shape** would be defined. This proved an easy task, as I could simply use the names of the type checking functions as values for the keys of the object.
```js
const shape = { name: 'str', age: 'num', active: 'bool', birthday: 'date' };
```
Having decided how to define shapes, I needed to convert this shape definition into a function that would take an object and wrap it with a `Proxy`. The `Proxy` would in turn **intercept any attempts to set a property** and check if the value being set is of the correct type. If it is, the value is set as expected. If not, the trap returns `false`, which means the operation was not a success. Similarly, properties not in the shape definition should not be set, so the trap returns `false` for those as well.
```js
const createShapeCheckerProxy = (types, shape) => {
const validProps = Object.keys(shape);
const handler = {
set(target, prop, value) {
if (!validProps.includes(prop)) return false;
const validator = types[shape[prop]];
if (!validator || typeof validator !== 'function') return false;
if (!validator(value)) return false;
target[prop] = value;
}
};
return obj => new Proxy(obj, handler);
};
```
Having set everything up, it was time to test it out. Here's an example of the whole thing put together:
```js
const createShapeCheckerProxy = shape => {
const types = {
bool: v => typeof v === 'boolean',
num: v => typeof v === 'number' && v === v,
str: v => typeof v === 'string',
date: v => v instanceof Date
};
const validProps = Object.keys(shape);
const handler = {
set(target, prop, value) {
if (!validProps.includes(prop)) return false;
const validator = types[shape[prop]];
if (!validator || typeof validator !== 'function') return false;
if (!validator(value)) return false;
target[prop] = value;
}
};
return obj => new Proxy(obj, handler);
};
const shapeCheckerProxy = createShapeCheckerProxy({
name: 'str', age: 'num', active: 'bool', birthday: 'date'
});
const obj = {};
const proxiedObj = shapeCheckerProxy(obj);
// These are valid
proxiedObj.name = 'John';
proxiedObj.age = 34;
proxiedObj.active = false;
proxiedObj.birthday = new Date('1989-04-01');
// These will fail
proxiedObj.name = 404;
proxiedObj.age = false;
proxiedObj.active = 'no';
proxiedObj.birthday = null;
proxiedObj.whatever = 'something';
```
As you can see, `createShapeCheckerProxy` can be used with a plain object to create a reusable function that wraps an object with a typechecking `Proxy`. The defined `types` are used to typecheck individual properties and could be extended to support more complex types and special rules. Overall, this can be a pretty useful tool for **typechecking objects at runtime**, without having to use TypeScript or similar tools.