From 4da5a4a5e50f127ee93c311f539887755fb8ff7d Mon Sep 17 00:00:00 2001 From: Nitesh Seram Date: Thu, 6 Jun 2024 07:04:59 +0530 Subject: [PATCH] quiz/js: property flags and descriptors (#468) --- .../en-US.mdx | 284 ++++++++++++++++++ .../metadata.json | 11 + 2 files changed, 295 insertions(+) create mode 100644 packages/quiz/questions/what-are-property-flags-and-descriptors/en-US.mdx create mode 100644 packages/quiz/questions/what-are-property-flags-and-descriptors/metadata.json diff --git a/packages/quiz/questions/what-are-property-flags-and-descriptors/en-US.mdx b/packages/quiz/questions/what-are-property-flags-and-descriptors/en-US.mdx new file mode 100644 index 000000000..907c2033e --- /dev/null +++ b/packages/quiz/questions/what-are-property-flags-and-descriptors/en-US.mdx @@ -0,0 +1,284 @@ +--- +title: What are property flags and descriptors? +--- + +**TL;DR** + +In JavaScript, property flags and descriptors manage the behavior and attributes of object properties. + +### Property Flags + +Property flags are used to specify the behavior of a property. Here are the available flags: + +- `writable`: Specifies whether the property can be written to. Default is `true`. +- `enumerable`: Specifies whether the property is enumerable. Default is `true`. +- `configurable`: Specifies whether the property can be deleted or its attributes changed. Default is `true`. +- `value`: Specifies the initial value of the property. +- `get`: Specifies a getter function for the property. +- `set`: Specifies a setter function for the property. + +### Property Descriptors + +These provide detailed information about an object's property, including its value and flags. They are retrieved using `Object.getOwnPropertyDescriptor()` and set using `Object.defineProperty()`. + +### Use Cases + +- **Data Validation**: Validate data before setting, like ensuring age is a positive number. +- **Computed Properties**: Derive properties from others, like creating a fullName property from firstName and lastName. +- **Encapsulation**: Control data access with getter and setter methods, ensuring private properties. +- **Inheritance**: Control property inheritance behavior in class hierarchies, setting non-writable or non-configurable properties. + +--- + +In JavaScript, property flags and descriptors are used to manage the behavior and attributes of object properties. These flags and descriptors are essential for understanding how properties are accessed, modified, and inherited. + +## Property Flags + +Property flags are used to specify the behavior of a property. They are set using the `Object.defineProperty()` method, which allows you to define a property on an object with specific attributes. The available property flags are: + +- `writable`: Specifies whether the property can be written to. Default is `true`. +- `enumerable`: Specifies whether the property is enumerable. Default is `true`. +- `configurable`: Specifies whether the property can be deleted or its attributes changed. Default is `true`. +- `value`: Specifies the initial value of the property. +- `get`: Specifies a getter function for the property. +- `set`: Specifies a setter function for the property. + +## Property Descriptors + +Property descriptors provide detailed information about an object's property, encapsulating its value and flags. They are retrieved using `Object.getOwnPropertyDescriptor()` and set using `Object.defineProperty()` + +```js +let user = { name: 'John Doe' }; +let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); + +console.log(descriptor); // Output: {value: "John Doe", writable: true, enumerable: true, configurable: true} +``` + +## Manipulating property flags + +### `writable` Flag + +The `writable` flag specifies whether a property can be written to. When `writable` is `false`, trying to assign value to the property fails silently in non-strict mode, and it throws a `TypeError` in strict mode. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + writable: false, + value: 'John Doe', +}); + +console.log(obj.name); // Output: John Doe +obj.name = 'Jane Doe'; // TypeError: Cannot assign to read only property 'name' of object '#' +``` + +### `enumerable` Flag + +The `enumerable` flag specifies whether a property is enumerable. The `enumerable flag` is set to `true`, which means the property is visible in a `for...in` loop. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + enumerable: false, + value: 'John Doe', +}); + +for (const prop in obj) { + console.log(prop); // Output: nothing +} + +const obj1 = {}; + +Object.defineProperty(obj1, 'name', { + enumerable: true, + value: 'John Doe', +}); + +for (const prop in obj1) { + console.log(prop); // Output: name +} +``` + +## `configurable` Flag + +The `configurable` flag specifies whether a property can be deleted or its attributes changed. When `configurable` is `false`, trying to delete or change the property fails silently in non-strict mode, and it throws a `TypeError` in strict mode. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + configurable: false, + value: 'John Doe', +}); + +delete obj.name; // Output: TypeError: Cannot delete property 'name' of # +``` + +## `value` Flag + +The `value` flag specifies the initial value of a property. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + value: 'John Doe', +}); + +console.log(obj.name); // Output: John Doe +``` + +## `get` Flag + +The `get` flag specifies a getter function for a property. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + get() { + return 'John Doe'; + }, +}); + +console.log(obj.name); // Output: John Doe +``` + +## `set` Flag + +The `set` flag specifies a setter function for a property. + +```js +const obj = {}; + +Object.defineProperty(obj, 'name', { + set(value) { + if (value.length < 3) { + throw new Error('Name must have more than 3 characters'); + } + console.log(`Setting name to ${value}`); + }, +}); + +obj.name = 'Jane Doe'; // Output: Setting name to Jane Doe +obj.name = 'Ja'; // Error: Name must have more than 3 characters +``` + +## Use cases + +### Data Validation + +You can use property descriptors to validate data before it is set on an object. For example, you can create a `person` object with an `age` property that has a setter function that checks if the `age` is a valid number: + +```js +const person = {}; + +Object.defineProperty(person, 'age', { + set(value) { + if (typeof value !== 'number' || value < 0) { + throw new Error('Age must be a positive number'); + } + this._age = value; + }, + get() { + return this._age; + }, +}); + +person.age = 30; +console.log(person.age); // Output: 30 + +person.age = -10; // Output: Error: Age must be a positive number +``` + +### Computed Properties + +You can use property descriptors to create computed properties that are derived from other properties on the object. For example, you can create a `fullName` property that is derived from `firstName` and `lastName` properties: + +```js +const person = { + firstName: 'John', + lastName: 'Doe', +}; + +Object.defineProperty(person, 'fullName', { + get() { + return `${this.firstName} ${this.lastName}`; + }, + set(value) { + [this.firstName, this.lastName] = value.split(' '); + }, +}); + +console.log(person.fullName); // Output: John Doe +person.fullName = 'Jane Smith'; +console.log(person.firstName); // Output: Jane +console.log(person.lastName); // Output: Smith +``` + +### Encapsulation + +You can use property descriptors to encapsulate data and control access to it. For example, you can create a `BankAccount` class with a `balance` property that is only accessible through getter and setter methods: + +```js +class BankAccount { + #balance = 0; + + get balance() { + return this.#balance; + } + + deposit(amount) { + this.#balance += amount; + } + + withdraw(amount) { + if (amount <= this.#balance) { + this.#balance -= amount; + } else { + console.log('Insufficient funds'); + } + } +} + +const account = new BankAccount(); +account.deposit(1000); +console.log(account.balance); // Output: 1000 +account.withdraw(500); +console.log(account.balance); // Output: 500 +account.#balance = 10000; // Error: Private field '#balance' must be declared in an enclosing class +``` + +### Inheritance + +You can use property descriptors to control how properties are inherited in a class hierarchy. For example, you can create a `Vehicle` class with a make property that is non-writable and non-configurable, and a `Car` class that inherits from `Vehicle`: + +```js +class Vehicle { + constructor(make) { + Object.defineProperty(this, 'make', { + value: make, + writable: false, + configurable: false, + }); + } +} + +class Car extends Vehicle { + constructor(make, model) { + super(make); + this.model = model; + } +} + +const car = new Car('Toyota', 'Camry'); +console.log(car.make); // Output: Toyota +car.make = 'Honda'; // No effect +``` + +## Further reading + +- [Object.defineProperty() | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) +- [Property flags and descriptors | Javascript.info](https://javascript.info/property-descriptors) +- [JavaScript: Property Flags and Descriptors | W3docs.com](https://www.w3docs.com/learn-javascript/property-flags-and-descriptors.html) diff --git a/packages/quiz/questions/what-are-property-flags-and-descriptors/metadata.json b/packages/quiz/questions/what-are-property-flags-and-descriptors/metadata.json new file mode 100644 index 000000000..fa5c5363a --- /dev/null +++ b/packages/quiz/questions/what-are-property-flags-and-descriptors/metadata.json @@ -0,0 +1,11 @@ +{ + "slug": "what-are-property-flags-and-descriptors", + "languages": [], + "companies": [], + "premium": false, + "duration": 5, + "published": true, + "topics": ["javascript"], + "importance": "medium", + "difficulty": "medium" +}