├── README.md ├── an-introduction-to-javascript-proxy.mdx ├── create-a-proxy-wrapper.mdx ├── implement-private-properties-in-a-javascript-class.mdx ├── javascript-proxy-vs-object-define-property.mdx ├── overload-the-in-operator.mdx └── use-javascript-proxy-to-protect-internal-properties.mdx /README.md: -------------------------------------------------------------------------------- 1 | # Practical examples of using JavaScript Proxy 2 | 3 | JavaScript Proxy is a powerful tool that allows you to customize and intercept object behavior. It lets you create a virtual layer on top of an object, which can be used for a variety of purposes, such as validation, caching, and security. 4 | 5 | The Proxy object is a key feature of the JavaScript language and is widely used in modern web development. It enables you to define custom behavior for basic operations on an object, such as property access, assignment, and deletion. This allows you to create objects that behave in ways that are impossible with regular JavaScript objects. 6 | In this series, we'll explore the basics of **JavaScript Proxy** and show you how to use it in practical situations. Get ready to take your JavaScript skills to the next level! 7 | -------------------------------------------------------------------------------- /an-introduction-to-javascript-proxy.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-01' 3 | description: An introduction to JavaScript Proxy 4 | index: 0 5 | openGraphCover: /og/javascript-proxy/introduction-javascript-proxy.png 6 | tags: JavaScript Proxy, Proxy traps 7 | title: An introduction to JavaScript Proxy 8 | --- 9 | 10 | When working with objects in JavaScript, it's common to set and get properties. However, if you access the property directly using square brackets or dot notation, there's no way to prevent users from passing an invalid value. 11 | 12 | Let's take an example to understand this better. Imagine we have a `person` object with `age` and `name` properties that indicate the person's age and name. 13 | 14 | ```js 15 | let person = { 16 | name: 'John Smith', 17 | age: 20, 18 | }; 19 | ``` 20 | 21 | To update the `age` property of our `person` object, simply set `person.age` to the desired value. However, if the value is negative or unreasonably large, it should be rejected as invalid. 22 | 23 | ```js 24 | // Valid 25 | person.age = 42; 26 | 27 | // Invalid 28 | person.age = -5; 29 | person.age = 200; 30 | ``` 31 | 32 | To solve the issue at hand, we can implement additional logic or restrictions when setting or getting a property. That's where the **JavaScript Proxy** can be incredibly useful. It allows us to intercept these operations and apply custom behavior before they're completed. 33 | 34 | With a proxy, we can ensure that certain properties are only set with specific values or that certain properties can't be accessed at all. This kind of control over object behavior can significantly enhance the security and reliability of our code. 35 | 36 | ## Creating a Proxy object 37 | 38 | JavaScript Proxy is a powerful feature that was introduced in ECMAScript 6. It allows us to create custom behavior for operations on objects. Think of the Proxy as a middleman between the user and the object, which intercepts and modifies the object's behavior. 39 | 40 | To create a proxy object in JavaScript, we use the `Proxy` constructor. This constructor takes two arguments: the target object and a handler object. The target object is the object that the proxy wraps around, while the handler object defines the custom behavior for the proxy. 41 | 42 | Let's take a look at an example of creating a proxy object. 43 | 44 | ```js 45 | const target = { 46 | name: 'John Smith', 47 | age: 30 48 | }; 49 | 50 | const handler = { 51 | get: function(target, prop) { 52 | console.log(`Getting the property ${prop}`); 53 | return target[prop]; 54 | }, 55 | set: function(target, prop, value) { 56 | console.log(`Setting the property ${prop} to ${value}`); 57 | target[prop] = value; 58 | }, 59 | }; 60 | 61 | const proxy = new Proxy(target, handler); 62 | ``` 63 | 64 | In this example, we have an object called `target` with two properties: `name` and `age`. Additionally, we have another object called `handler` with two methods: `get` and `set`. Finally, we use the `Proxy` constructor to create a new object called `proxy` by passing in the `target` and `handler` objects as arguments. 65 | 66 | ## Understanding Proxy traps 67 | 68 | In JavaScript, a trap is a method on the handler object that intercepts the proxy's default behavior for a particular operation. There are many traps available to us, such as `get`, `set`, `apply`, and `construct`. In the previous section, we introduced the `get` and `set` traps. 69 | 70 | The `get` trap is called when a property of the target object is accessed using dot notation or square brackets. When we access a property using these notations, JavaScript automatically calls the `get` method on our handler object and passes in two arguments: the `target` object (the object that the proxy wraps around) and `prop` (the name of the accessed property). 71 | 72 | Here's what happens by default when you use the `get` trap: 73 | 74 | ```js 75 | const handler = { 76 | get: function(target, prop) { 77 | return target[prop]; 78 | }, 79 | }; 80 | ``` 81 | 82 | It's important to note that if you don't define a `get` trap for your handler object in JavaScript, the default behavior for accessing properties will be used. 83 | 84 | Similarly, the `set` trap is called when a property of the target object is set using dot notation or square brackets. When we set a property using these notations, JavaScript automatically calls the `set` method on our handler object and passes in three arguments: the `target` object (the object that the proxy wraps around), `prop` (the name of the accessed property), and `value` (the value being assigned to the property). 85 | 86 | ```js 87 | const handler = { 88 | set: function(target, prop, value) { 89 | target[prop] = value; 90 | }, 91 | }; 92 | ``` 93 | 94 | In addition to the three parameters we discussed earlier, there's a fourth parameter called `receiver`. This parameter refers to the proxy or an object that inherits from it. It can be handy in determining whether properties are being set on the target object or one of its prototypes. By checking if the receiver matches our proxy, we can modify only the properties on the target object and not its prototypes. 95 | 96 | ## Customizing the Proxy trap 97 | 98 | Now that you understand the basics of Proxy traps, we can use its parameters to implement custom behavior for getting or setting properties. For example, let's say we want to prevent negative numbers from being passed to the `age` property of the `person` object. We can modify the `set` trap to only accept numbers in the range of 0 to 120. This ensures that our code works as intended and avoids any unwanted behavior. 99 | 100 | ```js 101 | const handler = { 102 | set: function(target, prop, value) { 103 | if (prop === 'age' && value < 0 || value > 120) { 104 | throw new Error('Invalid age'); 105 | return; 106 | } 107 | target[prop] = value; 108 | }, 109 | }; 110 | ``` 111 | 112 | In the `set` trap handler, we first check if the user is setting the `age` property by looking at the `prop` parameter. Then, we check if the `value` parameter is less than 0 or greater than 120. If both of these conditions are met, we throw an exception. 113 | 114 | ## Using a Proxy object 115 | 116 | After creating a proxy object, we can use it just like any other object. When we access a property on the proxy, the `get` method on the `handler` object is called. Similarly, when we set a property on the proxy, the `set` method on the `handler` object is called. 117 | 118 | Let's take a look at an example of using a proxy object: 119 | 120 | ```js 121 | const target = { 122 | name: 'John Smith', 123 | age: 30 124 | }; 125 | const proxy = new Proxy(target, handler); 126 | 127 | console.log(proxy.name); // John Smith 128 | ``` 129 | 130 | In this example, we accessed the `name` property on the `proxy` object by calling the `get` method on the `handler` object. This gave us the `name` value from the `target` object. 131 | 132 | In addition to getting property values, we can also set them. When we set the `age` property on the `proxy` object, it calls the `set` method on the `handler`. This updates the `age` value on the `target` object. 133 | 134 | However, if you try to set an invalid value to the `age` property, an error will be thrown. This is because of the `set` trap customization we did earlier. 135 | 136 | ```js 137 | console.log(proxy.age); // 30 138 | 139 | proxy.age = 42; 140 | proxy.age = -5; // Throws an exception 141 | ``` 142 | 143 | ## Conclusion 144 | 145 | JavaScript Proxy is an incredibly powerful feature that gives us the ability to customize the behavior of operations on objects. This means we can add extra functionality to our objects or alter the way existing objects behave. By using the `Proxy` constructor and a `handler` object, we can intercept and modify the behavior of operations on our objects, making them more flexible and powerful than ever before. 146 | -------------------------------------------------------------------------------- /create-a-proxy-wrapper.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-02' 3 | description: Create a JavaScript Proxy wrapper 4 | index: 1 5 | openGraphCover: /og/javascript-proxy/create-proxy-wrapper.png 6 | title: Create a Proxy wrapper 7 | --- 8 | 9 | In our previous post, we explored the basics of [creating](https://phuoc.ng/collection/javascript-proxy/an-introduction-to-javascript-proxy/) and using a Proxy object. We customized the `set` trap to add a simple validation to our target. Here's the code snippet that demonstrates what we did earlier: 10 | 11 | ```js 12 | const handler = { 13 | set: function(target, prop, value) { 14 | if (prop === 'age' && value < 0 || value > 120) { 15 | throw new Error('Invalid age'); 16 | return; 17 | } 18 | target[prop] = value; 19 | }, 20 | }; 21 | ``` 22 | 23 | In the `set` trap handler, we check if the user is trying to set the `age` property by looking at the `prop` parameter. Then, we verify if the `value` parameter is less than 0 or greater than 120. If both conditions are true, we throw an exception. 24 | 25 | After that, we can create a Proxy for our person object using the Proxy constructor. 26 | 27 | ```js 28 | const target = { 29 | name: 'John Smith', 30 | age: 30, 31 | }; 32 | const proxy = new Proxy(target, handler); 33 | ``` 34 | 35 | ## Limitations to address 36 | 37 | While this implementation is effective and gives us the power of Proxy, there are a few limitations we need to address. 38 | 39 | Firstly, exposing the Proxy to end users is typically not a good idea. We should design the implementation in a way that hides the details. 40 | 41 | > Encapsulation is a fundamental concept in programming. It involves organizing code so that data and methods are grouped together as a single unit, such as a class. This principle also involves information hiding, which means that implementation details should be hidden from users. This is often referred to as the **principle of least knowledge** or the [**law of demeter** (LoD)](https://en.wikipedia.org/wiki/Law_of_Demeter). By following these principles, we can create more maintainable and robust code that is less prone to bugs. 42 | 43 | In our case, it's important not to expose the inner workings of our Proxy implementation to users unless it's absolutely necessary. 44 | 45 | The example we've been working with only applies to a specific target, like a person object. This makes it tough to reuse our code in different projects or with different objects. We'd have to copy and paste our code every time we want to use it on a new object. 46 | 47 | Plus, if we wanted to add more traps or change how our proxy behaves, we'd have to update each instance of our code that uses that particular set of traps. This can get messy and lead to errors. 48 | 49 | ## A better approach 50 | 51 | To solve these issues, we can create a wrapper function around our Proxy object that only shows the necessary interface while keeping the implementation details concealed. This is a great way to use the Proxy object effectively. By creating a wrapper, we can encapsulate the behavior we want to apply to our objects in a single function. This makes it much easier to use and manage. 52 | 53 | For instance, let's say we have multiple `person` objects with different properties that require different validation rules. Instead of defining unique handlers for each object, we can create a single `withValidators` wrapper function. This function takes an object as an argument and returns a new proxy object with customized behavior that meets our validation requirements. 54 | 55 | Here's a simple implementation of a wrapper that validates the `age` property. 56 | 57 | ```js 58 | const withValidators = (person) => { 59 | const handler = { 60 | set: function(target, prop, value) { 61 | if (prop === 'age' && value < 0 || value > 120) { 62 | throw new Error('Invalid age'); 63 | return; 64 | } 65 | target[prop] = value; 66 | }, 67 | }; 68 | 69 | return new Proxy(person, handler); 70 | }; 71 | ``` 72 | 73 | To use the `withValidators` wrapper function, we just need to pass an object as an argument. The function will then return a new proxy object that applies customized behavior to the properties of the original object. 74 | 75 | Here's an example of how to call the `withValidators` function: 76 | 77 | ```js 78 | const person = withValidators({ 79 | name: 'John Smith', 80 | age: 30, 81 | }); 82 | ``` 83 | 84 | In this example, we passed an object with `name` and `age` properties to the `withValidators` function. The function returned a new proxy object that applies custom validation rules for the `age` property. 85 | 86 | Once we have our `person` object wrapped in a proxy, we can access or set its properties just like any other object. However, if we try to set an invalid value to the `age` property, the custom validator in the handler will throw an error. This ensures that our `person` object meets the validation requirements we've set. 87 | 88 | ```js 89 | console.log(person.age); // 30 90 | 91 | person.age = 42; 92 | person.age = -5; // Throws an exception 93 | ``` 94 | 95 | As you can see, using a wrapper makes it easier to reuse our code and ensures consistency across all our objects. Plus, if we need to change how our proxies behave, we only have to update the wrapper function instead of changing each handler one by one. This saves us time and effort in the long run. 96 | 97 | ## Conclusion 98 | 99 | To sum up, we've learned how to create a Proxy wrapper function that enables us to encapsulate the behavior we want to apply to our objects in one place. Wrapping our objects in a proxy with custom validation rules can help ensure consistency and reusability of our code. 100 | 101 | It also simplifies the process of modifying the behavior of all our proxies at once by updating the wrapper function instead of modifying every handler individually. 102 | -------------------------------------------------------------------------------- /implement-private-properties-in-a-javascript-class.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-05' 3 | description: Implement private properties in a JavaScript class 4 | index: 4 5 | openGraphCover: /og/javascript-proxy/implement-private-properties-javascript-class.png 6 | title: Implement private properties in a JavaScript class 7 | --- 8 | 9 | In our previous post, we learned how to safeguard an object's properties so they can't be modified or accessed from outside. Object-oriented programming has three types of properties: public, protected, and private. Public properties are accessible from anywhere in the code, whereas protected and private properties can only be accessed within the object or class they belong to. 10 | 11 | Protected properties can be accessed by the class and any subclasses that inherit from it, providing flexibility for code reuse and modification. Private properties, however, are only accessible by methods within the same class. This helps prevent interference from external code and maintains data integrity. 12 | 13 | Private fields can't be accessed or modified from outside the class they belong to, which makes code more secure and reliable. By restricting access to sensitive data, developers can have more confidence in their code's behavior and avoid potential bugs or security vulnerabilities. 14 | 15 | Private fields also make code more readable and maintainable by clarifying which parts of a class are public-facing and which are internal implementation details. In modern JavaScript, private fields are defined using the hash symbol (#) before the field name. 16 | 17 | However, in this post, we'll delve into how to create private properties of a class with JavaScript Proxy. 18 | 19 | ## Naming private properties 20 | 21 | As we discussed in the previous post, we'll use the underscore (\_) prefix to indicate protection properties. 22 | 23 | In the following example, we create a constructor for the `Person` class that takes three parameters: `name`, `age`, and `_ssn`. The `name` and `age` parameters are public properties that can be accessed outside the class, while `_ssn` is a private property that can only be accessed within the class. 24 | 25 | ```js 26 | class Person { 27 | constructor(name, age, ssn) { 28 | this.name = name; 29 | this.age = age; 30 | this._ssn = ssn; 31 | } 32 | } 33 | ``` 34 | 35 | To make a new `Person`, simply use the `new` keyword followed by the class name and required parameters. Let's say we want to create a person named John Smith who is 42 years old and has an SSN of 123-45-6789. We pass these values as parameters. A new instance of the `Person` class is created with those properties set. 36 | 37 | ```js 38 | const person = new Person('John Smith', 42, '123-45-6789'); 39 | ``` 40 | 41 | We can access the `_ssn` property directly by using dot notation. 42 | 43 | ```js 44 | console.log(person._ssn); // 123-45-6789 45 | ``` 46 | 47 | ## Setting up Proxy traps 48 | 49 | When it comes to protecting the private properties of a class, we can use the same approach that we introduced in our previous post, with just a slight modification. 50 | 51 | The `get` and `set` traps work the same way, preventing users from accessing or modifying properties whose names start with an underscore. Here's a quick reminder of the code snippet we used earlier: 52 | 53 | ```js 54 | const handler = { 55 | get(target, key) { 56 | if (key.startsWith('_')) { 57 | throw new Error(`Cannot access private property '${key}'`); 58 | } 59 | 60 | return target[key]; 61 | }, 62 | set(target, key, value) { 63 | if (key.startsWith('_')) { 64 | throw new Error(`Cannot modify private property '${key}'`); 65 | } 66 | 67 | target[key] = value; 68 | return true; 69 | }, 70 | }; 71 | ``` 72 | 73 | Now, let's create a `ProtectedPerson` class that acts as a proxy for our original `Person` class. 74 | 75 | ```js 76 | const ProtectedPerson = new Proxy(Person, handler); 77 | ``` 78 | 79 | Next, we create a fresh `person` instance using our `ProtectedPerson` constructor. While it may seem like a good idea in theory, in reality, it doesn't work. 80 | 81 | ```js 82 | const person = new ProtectedPerson('John Smith', 42, '123-45-6789'); 83 | 84 | console.log(person._ssn); // 123-45-6789 85 | ``` 86 | 87 | When we try to pass the `Person` class to the `Proxy`, it doesn't work as expected because a class is not an object, but rather a blueprint for creating objects. So, when we create an instance of the `ProtectedPerson` proxy, we're actually creating an instance of the `Person` class, which lacks the protections that our handler provides. 88 | 89 | To solve this issue, we can use the `Proxy` in the constructor of the `ProtectedPerson` class. This creates a new instance of `ProtectedPerson` and applies the traps defined in the `handler`. This way, we can ensure that the protections are applied to the `ProtectedPerson` instance itself, rather than the `Person` class. 90 | 91 | ```js 92 | class ProtectedPerson { 93 | constructor(name, age, ssn) { 94 | this.name = name; 95 | this.age = age; 96 | this._ssn = ssn; 97 | 98 | return new Proxy(this, handler); 99 | } 100 | } 101 | ``` 102 | 103 | In the example above, we define a new class called `ProtectedPerson` that's similar to our original `Person` class. However, instead of directly returning an instance of `this`, we're wrapping it with a `Proxy`. 104 | 105 | When we create an instance of the `ProtectedPerson` class, it returns a new `Proxy` object that wraps the original `Person` object. Our handler checks if any private properties are being accessed or modified. 106 | 107 | Now, let's try to access the `_ssn` property of the `person` instance created with the `ProtectedPerson` class. As expected, we get an error message because `_ssn` is a private property that can't be accessed from outside the class. 108 | 109 | ```js 110 | const person = new ProtectedPerson('John Smith', 42, '123-45-6789'); 111 | 112 | // Uncaught Error: Cannot access private property '_ssn' 113 | console.log(person._ssn); 114 | ``` 115 | 116 | ## Accessing private properties internally 117 | 118 | However, there's another issue we need to address. Private properties are not only inaccessible from outside, but also from inside the class. To demonstrate this issue, let's add a function called `getSsn` that returns the value of the `_ssn` property. 119 | 120 | ```js 121 | class ProtectedPerson { 122 | getSsn() { 123 | return this._ssn; 124 | } 125 | } 126 | ``` 127 | 128 | Even if we execute the `getSsn()` function on a new instance, it will still throw the same error as when we access the `_ssn` property directly. This happens because the new instance is proxied to `this`, and it can't access the `_ssn` property directly due to the protection. 129 | 130 | ```js 131 | const person = new ProtectedPerson('John Smith', 42, '123-45-6789'); 132 | 133 | // Uncaught Error: Cannot access private property '_ssn' 134 | person.getSsn(); 135 | ``` 136 | 137 | To address the issue, we need to make a slight modification to the `get` trap handler. In the previous version, the handler threw an error if a property name started with an underscore. However, in this updated version, we first retrieve the value from the `target` object and check if it's a function. If it is, we bind it to the target object, making it accessible within the instance. This allows us to access private properties within class methods without encountering any errors. 138 | 139 | Here's what the updated `get` trap looks like: 140 | 141 | ```js 142 | const handler = { 143 | get(target, key) { 144 | if (key.startsWith('_')) { 145 | throw new Error(`Cannot access private property '${key}'`); 146 | } 147 | const value = target[key]; 148 | return (typeof value === 'function') ? value.bind(target) : value; 149 | }, 150 | }; 151 | ``` 152 | 153 | Now that we've implemented the updated handler, we can confidently call the `getSsn()` method on a `ProtectedPerson` instance without encountering any errors. 154 | 155 | ```js 156 | const person = new ProtectedPerson('John Smith', 42, '123-45-6789'); 157 | 158 | console.log(person.getSsn()); // 123-45-6789 159 | ``` 160 | 161 | ## Conclusion 162 | 163 | In this post, we explored how to create private properties within a class using the JavaScript Proxy feature. We learned that private fields can't be accessed or modified from outside of the class they belong to, which is great for maintaining data integrity and preventing unintended side effects from external code. Additionally, private fields can make code more readable and maintainable by making it clear which parts of a class are public-facing and which are internal implementation details. 164 | 165 | While using the hash symbol (#) before the field name is one way to define private fields in modern JavaScript, we can also use a `Proxy` object with `get` and `set` traps for more control over how our private properties are accessed and modified. 166 | 167 | By using proxies, we add an extra layer of security to our code and ensure that sensitive information remains protected. 168 | -------------------------------------------------------------------------------- /javascript-proxy-vs-object-define-property.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-03' 3 | description: The differences between JavaScript Proxy and Object.defineProperty 4 | index: 2 5 | openGraphCover: /og/javascript-proxy/javascript-proxy-vs-object-define-property.png 6 | title: JavaScript Proxy vs Object.defineProperty 7 | --- 8 | 9 | When defining properties on a JavaScript object, we have a few options, including using the dot notation (`object.property`) or the bracket notation (`object[property]`). We can also use JavaScript proxy. 10 | 11 | However, there's another way to define or modify object properties that's worth exploring: `Object.defineProperty`. This feature gives us more control over object properties by allowing us to define new ones or modify existing ones with specific attributes. 12 | 13 | In JavaScript, objects are collections of properties, each with their own key-value pair. The value of a property can be any data type, including functions or other objects. With `Object.defineProperty`, we can define new properties on an object or modify existing ones with attributes that determine how they can be accessed or modified. This method gives us more control over our objects. 14 | 15 | In this post, we'll explore the differences between `Object.defineProperty` and JavaScript Proxy. But first, let's take a closer look at the syntax for using `Object.defineProperty`. 16 | 17 | ## Understanding the syntax of Object.defineProperty 18 | 19 | Let's talk about the `defineProperty` function. It takes in three parameters: 20 | 21 | ```js 22 | Object.defineProperty(object, propertyname, descriptor); 23 | ``` 24 | 25 | Here's a breakdown of the `Object.defineProperty` syntax: 26 | 27 | - `object`: This is the object you want to define the property on. 28 | - `propertyname`: This is the name of the property you want to create or modify. 29 | 30 | The `descriptor` parameter is responsible for managing the behavior of the property. It offers a wide range of options to choose from, including: 31 | 32 | - `value`: This is the value you want to assign to the property. It can be any valid JavaScript data type. 33 | - `writable`: This is a boolean that determines whether the property can be changed. If set to `false`, any attempts to change it will result in an error. 34 | - `enumerable`: This is a boolean that determines whether the property will show up during object enumeration (like in a `for..in` loop). If set to `false`, it won't show up. 35 | - `configurable`: This is a boolean that determines whether the property can be deleted. If set to `false`, any attempts to delete it will result in an error. 36 | 37 | Using these attributes with `Object.defineProperty`, we can have precise control over our object properties and ensure they behave exactly how we want them to. In the next sections, we'll explore some common ways to use this function. 38 | 39 | ## Improving the getters and setters 40 | 41 | In our previous discussion, we pointed out an issue where users could pass an invalid value to an object's property. To illustrate this situation, we used the example of a `Person` object with an `age` property that indicates the person's age. 42 | 43 | ```js 44 | let person = { 45 | age: 20, 46 | }; 47 | ``` 48 | 49 | If the `age` property is set to a negative number or a number that is unreasonably large, then it should be considered invalid. 50 | 51 | ```js 52 | person.age = -5; 53 | person.age = 200; 54 | ``` 55 | 56 | The `Object.defineProperty` method allows you to define getters and setters for a property, similar to the functionality of JavaScript Proxy. Getters and setters are functions that enable you to manage access to a property. 57 | 58 | If you want to restrict the `age` property of your `person` object, you can use `get` and `set` in the descriptor of `Object.defineProperty` to create a custom getter and setter for `age`. Here's how you can do it: 59 | 60 | ```js 61 | let _age = 30; 62 | Object.defineProperty(person, 'age', { 63 | get() { 64 | return _age; 65 | }, 66 | set(value) { 67 | if (value < 0 || value > 150) { 68 | throw new Error("Invalid age"); 69 | } 70 | _age = value; 71 | }, 72 | }); 73 | 74 | console.log(person.age); // 30 75 | person.age = 40; 76 | console.log(person.age); // 40 77 | ``` 78 | 79 | In this example, we've created a custom getter and setter for the `_age` property using `get` and `set`. The setter checks if the new value is valid before setting `_age`. This ensures that any attempts to set an invalid value for `person.age` will result in an error being thrown. 80 | 81 | ```js 82 | // Throw an error 83 | person.age = -5; 84 | person.age = 200; 85 | ``` 86 | 87 | By using this approach, we can add additional logic to our getter or setter functions as needed. 88 | 89 | If we want to apply the same restrictions to multiple person objects, we can use the same approach for the corresponding class property. Here's a straightforward implementation: 90 | 91 | ```js 92 | function Person() { 93 | let _age = 0; 94 | 95 | Object.defineProperty(this, 'age', { 96 | get() { 97 | return _age; 98 | }, 99 | set(value) { 100 | if (value < 0 || value > 150) { 101 | throw new Error('Invalid age'); 102 | } 103 | _age = value; 104 | }, 105 | }); 106 | }; 107 | ``` 108 | 109 | If you try to assign an invalid value to the `age` property of a `Person` class instance, an error will be thrown. 110 | 111 | ```js 112 | const person = new Person(); 113 | 114 | // Throw an error 115 | person.age = -5; 116 | person.age = 200; 117 | ``` 118 | 119 | If we want to change the behavior of a specific property, we can use `Object.defineProperty` again. However, the key difference between `Object.defineProperty` and JavaScript Proxy is that the latter can modify multiple properties at once. 120 | 121 | Another difference is that with `Object.defineProperty`, we need to know the name of the property beforehand. But with JavaScript Proxy, we can dynamically handle any property access. 122 | 123 | ## Creating a read-only property 124 | 125 | It's a common task to prevent a property from being changed from outside. Luckily, we have two ways to do it in JavaScript: `Object.defineProperty` and JavaScript Proxy. 126 | 127 | To create a read-only property using `Object.defineProperty`, we can set the `writable` attribute to `false`. This will lock the property's value and prevent anyone from changing it after it's been set. 128 | 129 | ```js 130 | let person = { 131 | name: "John Smith" 132 | }; 133 | 134 | Object.defineProperty(person, 'name', { 135 | writable: false, 136 | }); 137 | ``` 138 | 139 | If you attempt to modify the value of `name`, the property value will not change. 140 | 141 | ```js 142 | person.name = "Jane"; 143 | 144 | console.log(person.name); // John Smith 145 | ``` 146 | 147 | On the other hand, JavaScript Proxy allows us to create a handler object with a `set` trap that will throw an error every time someone tries to modify the target object's property. This can be a powerful tool in some situations. 148 | 149 | ```js 150 | let person = { 151 | name: "John Smith" 152 | }; 153 | 154 | const readOnlyHandler = { 155 | set(target, prop, value) { 156 | if (prop == "name") { 157 | throw new Error(`Cannot set ${prop} to ${value}.`); 158 | } 159 | target[prop] = value; 160 | }, 161 | }; 162 | 163 | let proxyPersonReadOnlyName = new Proxy(person, readOnlyHandler); 164 | ``` 165 | 166 | If you attempt to alter the value of `name`, it will result in an error being thrown. 167 | 168 | ```js 169 | // Throws an error: 170 | // `Cannot set name to Jane.` 171 | proxyPersonReadOnlyName.name = "Jane"; 172 | ``` 173 | 174 | In this scenario, you can use either `Object.defineProperty` or JavaScript Proxy to create read-only properties in your JavaScript code. 175 | 176 | ## Conclusion 177 | 178 | To sum up, `Object.defineProperty` and JavaScript Proxy are both useful tools for manipulating object properties in JavaScript. `Object.defineProperty` lets us have fine-grained control over each property's behavior, so we can define attributes like configurable, enumerable, and writable for each property. JavaScript Proxy, on the other hand, gives us more flexibility to control access to object properties dynamically. 179 | 180 | When deciding between these methods, it's important to consider your specific needs and the level of control you require over your object's properties. Whether you need to define or modify a single property at a time or handle multiple properties dynamically, both `Object.defineProperty` and JavaScript Proxy can help you achieve your goals. 181 | -------------------------------------------------------------------------------- /overload-the-in-operator.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-06' 3 | description: Use JavaScript Proxy to overload the in operator 4 | index: 5 5 | openGraphCover: /og/javascript-proxy/overload-in-operator.png 6 | title: Overload the in operator 7 | --- 8 | 9 | In JavaScript, you can use the `in` operator to check if an object has a specific property. When you use the `in` operator, it returns `true` if the property exists in the object, and `false` otherwise. 10 | 11 | Here's an example of how to use the `in` operator to check if a property exists in an object: 12 | 13 | ```js 14 | const person = { 15 | name: 'John Smith', 16 | age: 42, 17 | occupation: 'developer', 18 | }; 19 | 20 | console.log('name' in person); // true 21 | console.log('email' in person); // false 22 | ``` 23 | 24 | In this example, we have an object called `person` that includes properties for `name`, `age`, and `occupation`. Using the `in` operator, we can check if the properties `name` and `email` exist in the `person` object. The first console log returns `true`, indicating that `name` is a property of the object. The second console log, on the other hand, returns `false`, indicating that `email` is not a property of the object. 25 | 26 | The `in` operator is not just limited to checking objects. It can also be used to verify if an element exists in an array. For instance, we can use the following code to determine if the number 3 is an element of an array called `arr`: 27 | 28 | ```js 29 | const arr = [1, 2, 3, 4, 5]; 30 | console.log(3 in arr); // true 31 | console.log(8 in arr); // false 32 | ``` 33 | 34 | In this post, we'll learn how to overload the in operator with JavaScript Proxy. 35 | 36 | ## Overloading the in operator in JavaScript 37 | 38 | In programming, overloading is when an operator or function can have multiple meanings and implementations depending on the context. In JavaScript, we can overload the `in` operator using a `Proxy` object. 39 | 40 | A `Proxy` object acts as an intermediary between the code and an object, allowing us to intercept and customize various operations such as property lookup, assignment, enumeration, and more. By using this feature, we can overload the `in` operator to create custom behavior. 41 | 42 | Here's an example of how we can overload the `in` operator in JavaScript: 43 | 44 | ```js 45 | const handler = { 46 | has(target, key) { 47 | console.log(`Checking for ${key} property...`); 48 | return key in target; 49 | }, 50 | }; 51 | ``` 52 | 53 | In this example, we're creating a `handler` object with a `has()` method that intercepts calls to the `in` operator. It does two things: logs a message to the console and delegates the operation to the original object using the `target` parameter of the `has()` method. 54 | 55 | Let's give this Proxy trap handler a spin: 56 | 57 | ```js 58 | const person = { 59 | name: 'John Smith', 60 | age: 42, 61 | occupation: 'developer', 62 | }; 63 | 64 | const proxy = new Proxy(person, handler); 65 | // Logs "Checking for name property..." and returns `true` 66 | console.log('name' in proxy); 67 | 68 | // Logs "Checking for email property..." and returns `false` 69 | console.log('email' in proxy); 70 | ``` 71 | 72 | We've created a `Proxy` object called `proxy` that wraps around our original object, `person`, and uses our custom handler. Now, when we call `'name' in proxy`, it logs `"Checking for name property..."` to the console and returns `true`. On the other hand, when we call `'email' in proxy`, it logs `"Checking for email property..."` to the console and returns `false`. 73 | 74 | This example is pretty simple, but in the next section, we'll explore more advanced usage of Proxy. 75 | 76 | ## Checking if a number belongs to a given range 77 | 78 | Many programming languages have built-in support for ranges, making it easy for developers to work with sequences of numbers or other types in a concise and readable way. For example, Python has a `range()` function that generates a sequence of numbers within a specified range, while Ruby has a `Range` class that represents an interval of values. 79 | 80 | How do we check if a number belongs to a range? In Python, we can simply use the membership operator (`in`) with a range object created using the `range()` function. 81 | 82 | ```python 83 | 3 in range(1, 6) # True 84 | 10 in range(1, 6) # False 85 | ``` 86 | 87 | Similarly, Ruby provides a variety of methods, such as `include?` and `cover?`, to check if a value falls within a given range. 88 | 89 | ```ruby 90 | (1..5).include?(3) # true 91 | (1..5).include?(10) # false 92 | ``` 93 | 94 | By using these built-in functions and methods, developers can easily check if a number belongs to a given range and streamline their code. 95 | 96 | While JavaScript doesn't have a similar API natively, we can still achieve similar functionality by using Proxy. Check out how we can implement the `range` function using JavaScript: 97 | 98 | ```js 99 | const range = (min, max) => new Proxy( 100 | Object.create(null), 101 | { 102 | has: (target, key) => (+key >= min && +key <= max), 103 | } 104 | ); 105 | ``` 106 | 107 | The `range` function in the code creates a new `Proxy` object with an empty target. Then, the `has` trap checks if a given key falls between the minimum and maximum values passed as arguments to the function. If it does, the `has` trap returns `true`, meaning the key belongs to the range. If not, it returns `false`. 108 | 109 | Using this proxy object makes it easy to check if a given number is in a particular range without having to use conditional statements. 110 | 111 | ```js 112 | 2 in range(1, 5); // true 113 | 10 in range(1, 5); // false 114 | ``` 115 | 116 | ## Conclusion 117 | 118 | To sum up, in JavaScript, we can use the `in` operator to check if a property exists in an object or if an element exists in an array. Additionally, we can customize the `in` operator with Proxy to create unique behavior for checking properties. 119 | 120 | Furthermore, with the help of Proxy, we can create a `range` function that allows us to check if a given number is within a specific range, without any need for conditional statements. 121 | 122 | Overloading the `in` operator is just one example of how Proxy objects can enhance our JavaScript code. Proxies provide us with the ability to personalize and intercept various operations on objects and functions, giving us greater flexibility and control over our code. 123 | -------------------------------------------------------------------------------- /use-javascript-proxy-to-protect-internal-properties.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | created: '2024-01-04' 3 | description: Use JavaScript Proxy to protect internal properties 4 | index: 3 5 | openGraphCover: /og/javascript-proxy/use-javascript-proxy-protect-internal-properties.png 6 | title: Use JavaScript Proxy to protect internal properties 7 | --- 8 | 9 | Protecting the internal properties of an object can be crucial in many situations. For example, if you're working on a web application that handles sensitive user data, it's vital to safeguard that data from unauthorized access. By using private properties, you can prevent malicious actors from accessing or modifying sensitive information. 10 | 11 | Encapsulation is another important practice in software development. It involves bundling data and behavior into a single unit (i.e., an object) and controlling access to that unit. Private properties allow you to encapsulate data within an object and expose only the necessary methods for manipulating that data. 12 | 13 | When writing tests for your code, it's helpful to isolate certain parts of your codebase to ensure they behave as expected. Using private properties allows you to test individual components without worrying about unintended side effects from other parts of your code. 14 | 15 | Let's say we have an object that represents all the information for a user account. In reality, the object contains data queried from a database and may include sensitive information like a social security number (SSN). 16 | 17 | ```js 18 | let user = { 19 | name: "John Smith", 20 | age: 42, 21 | ssn: "...", 22 | }; 23 | ``` 24 | 25 | How can we prevent someone from accessing or changing certain properties from outside? In this post, we'll learn how to use Proxy to achieve that goal. 26 | 27 | ## Preventing accessing and modifying properties 28 | 29 | With the approach we're about to implement, we can protect specific properties by name. However, we've decided to protect all properties that follow a certain naming convention. 30 | 31 | In programming, we typically use an underscore (\_) to indicate internal properties. For example, the SSN of the `user` object mentioned earlier should be named `_ssn`. 32 | 33 | To achieve this, we define a `get` trap on the `Proxy` object that checks if the requested property begins with an underscore. If it does, we throw an error. If not, we return the property value. 34 | 35 | Here's how we'll make it happen: 36 | 37 | ```js 38 | const protectInternalProps = (obj) => { 39 | const handler = { 40 | get(target, key) { 41 | if (key.startsWith('_')) { 42 | throw new Error(`Cannot access internal property '${key}'`); 43 | } 44 | return target[key]; 45 | }, 46 | }; 47 | 48 | return new Proxy(obj, handler); 49 | }; 50 | ``` 51 | 52 | Similarly, when we attempt to modify an internal property using the assignment operator, it triggers the `set` trap. In our case, we have defined a `set` handler on the `Proxy` object that checks if the property name starts with an underscore (`_`). If it does, we throw an error. Otherwise, we set the value as usual. 53 | 54 | Here's how it all comes together: 55 | 56 | ```js 57 | const protectInternalProps = (obj) => { 58 | const handler = { 59 | set(target, key, value) { 60 | if (key.startsWith('_')) { 61 | throw new Error(`Cannot modify internal property '${key}'`); 62 | } 63 | 64 | target[key] = value; 65 | return true; 66 | }, 67 | }; 68 | 69 | return new Proxy(obj, handler); 70 | }; 71 | ``` 72 | 73 | In the code above, you'll notice that if we attempt to modify any properties that start with an underscore (`_`), an error message reading `Cannot modify internal property` will be thrown. However, if we try to modify any other property, the value will be set as usual. 74 | 75 | Now, let's experiment with it and see what happens. 76 | 77 | ```js 78 | const user = { 79 | name: "John Smith", 80 | _ssn: "XXX-YY-ZZZ", 81 | }; 82 | 83 | const protectedUser = protectInternalProps(user); 84 | 85 | console.log(protectedUser.name); // John Smith 86 | 87 | // Throw error `Cannot access internal property '_ssn'` 88 | console.log(protectedUser._ssn); 89 | protectedUser._ssn = '123-45-678'; 90 | ``` 91 | 92 | But is the current level of protection enough to safeguard these properties? Unfortunately, it is still possible to delete the property using the `delete` function. 93 | 94 | ```js 95 | delete protectedUser._ssn; 96 | ``` 97 | 98 | Another issue is that we can check the existence of the properties of the object by looping over them. 99 | 100 | ```js 101 | Object.keys(protectedUser); // ['name', '_ssn'] 102 | ``` 103 | 104 | Fortunately, Proxy provides additional traps to solve these issues. Now, let's move on to the next sections. 105 | 106 | ## Stopping properties from being deleted 107 | 108 | The `deleteProperty` trap lets us intercept property deletion. If we want to prevent deletion of private properties, we can throw an error if the property name starts with an underscore (`_`). 109 | 110 | Here's how we can modify our `handler` object to include the `deleteProperty` trap: 111 | 112 | ```js 113 | const handler = { 114 | deleteProperty(target, key) { 115 | if (key.startsWith('_')) { 116 | throw new Error(`Cannot delete internal property '${key}'`); 117 | } 118 | 119 | delete target[key]; 120 | return true; 121 | }, 122 | }; 123 | ``` 124 | 125 | If we attempt to use the `delete` function to remove an internal property, an error will be thrown with the message `Cannot delete internal property`. 126 | 127 | ```js 128 | const user = { 129 | name: "John Smith", 130 | _ssn: "XXX-YY-ZZZ", 131 | }; 132 | 133 | const protectedUser = protectInternalProps(user); 134 | 135 | // Throw error `Cannot delete internal property '_ssn'` 136 | delete protectedUser._ssn; 137 | ``` 138 | 139 | ## Stopping object properties from being iterated 140 | 141 | The `ownKeys` trap is a handy feature that helps solve the problem of accessing internal properties. Whenever the `Object.keys()` method is called on an object, this trap is triggered, and it returns an array of keys that can be accessed. 142 | 143 | In our case, we only want to return keys that don't start with an underscore (`_`). We can do this by using the `filter` method to remove all keys that start with an underscore. 144 | 145 | Here's how we can modify our `handler` object to include the `ownKeys` trap: 146 | 147 | ```js 148 | const handler = { 149 | ownKeys(target) { 150 | return Object.keys(target).filter(key => !key.startsWith('_')); 151 | }, 152 | }; 153 | ``` 154 | 155 | By implementing this, when you call `Object.keys()` on our Proxy object, it will only return an array of non-internal keys. 156 | 157 | ```js 158 | const user = { 159 | name: "John Smith", 160 | _ssn: "XXX-YY-ZZZ", 161 | }; 162 | 163 | const protectedUser = protectInternalProps(user); 164 | 165 | console.log(Object.keys(protectedUser)); // ['name'] 166 | ``` 167 | 168 | This also works with the `for ... in` loop. 169 | 170 | ```js 171 | for (let key in protectedUser) { 172 | console.log(key); 173 | } 174 | // ['name'] 175 | ``` 176 | 177 | By implementing these traps on our Proxy object, we can now make sure that any properties that start with an underscore (`_`) are genuinely private. This means that they cannot be accessed or modified from outside the object. 178 | 179 | ## Conclusion 180 | 181 | To sum up, using a Proxy object to protect internal properties is a smart way to keep sensitive data safe in your web applications. By implementing the `get`, `set`, `deleteProperty`, and `ownKeys` traps on the Proxy object, you can prevent unwanted access or modification of private properties. 182 | 183 | This approach also helps you maintain encapsulation and test individual components of your codebase without worrying about unintended consequences. Overall, it's a powerful technique that every developer should consider when working with sensitive data in their applications. 184 | --------------------------------------------------------------------------------