Uncommon yet powerful: Exploring the lesser-known features of JavaScript
Published: 2023-03-31 17:47:21

JavaScript is a popular programming language that is widely used for web development. It offers a vast range of functionalities, from manipulating web pages to building complex web applications. However, there are some parts of JavaScript that are not commonly used, but they can be helpful in certain scenarios. In this blog, we will discuss some of these less commonly used parts of JavaScript.


**1. Generators**

Generators are a type of function in JavaScript that allow you to generate a sequence of values over time. They are useful in situations where you need to iterate over a large set of data, but you don't want to load all of it into memory at once.

Generator functions are defined using the function* syntax and they use the yield keyword to return a value and pause the generator function. When the generator function is called again, it resumes execution from where it left off.

Here's an example of a generator function that generates the first 10 even numbers:

```javascript
function* evenNumbers() {
  let num = 0;
  while (num < 20) {
    yield num;
    num += 2;
  }
}
```

In this example, the evenNumbers function is a generator function that generates even numbers between 0 and 20. The yield keyword is used to pause the function and return the current value of num. When the function is called again, it resumes execution from where it left off.

To use the evenNumbers generator, you can create an iterator using the next() method:

```javascript
const iterator = evenNumbers();
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 4
console.log(iterator.next().value); // 6
// ...

```

In this example, the evenNumbers generator is called to create an iterator. The next() method is then called on the iterator to get the next value in the sequence. The value property of the returned object contains the current value of the generator.

Generators are useful in situations where you need to generate a sequence of values over time. For example, if you need to load a large set of data from a server, you can use a generator to load the data in chunks and iterate over it as it is loaded.

Another use case for generators is in building custom iterators. For example, you can create a generator function that generates values based on user input or some other external source.

In summary, generators are a powerful feature in JavaScript that allow you to generate a sequence of values over time. They are useful in situations where you need to iterate over a large set of data or generate values based on external sources.

**2. Proxy**

Proxy is a powerful feature in JavaScript that allows you to intercept and modify operations performed on objects. With Proxy, you can create a proxy object that acts as a middleman between the caller and the target object, intercepting method calls and property access and allowing you to modify or block them as needed.

Proxy objects are created using the Proxy constructor. Here's an example of a simple proxy object:

```javascript
const target = {};
const handler = {
  get: function(target, prop) {
    console.log(`Getting property '${prop}'`);
    return target[prop];
  },
  set: function(target, prop, value) {
    console.log(`Setting property '${prop}' to ${value}`);
    target[prop] = value;
  }
};

const proxy = new Proxy(target, handler);

proxy.foo = "bar"; // Setting property 'foo' to 'bar'
console.log(proxy.foo); // Getting property 'foo' // 'bar'

```
In this example, we create a proxy object proxy that intercepts and logs the get and set operations performed on the target object. The get handler is called whenever a property is accessed on the proxy object, while the set handler is called whenever a property is set on the proxy object.

Proxies can be useful in a variety of situations. Here are some examples:

- Validation:
 You can use proxies to validate the input to a function or object. For example, you can create a proxy that checks the type of the input and throws an error if it's not a string.

```javascript
const validator = {
  set: function(target, prop, value) {
    if (typeof value !== 'string') {
      throw new Error('Value must be a string');
    }
    target[prop] = value;
    return true;
  }
};

const obj = new Proxy({}, validator);
obj.name = 'John'; // OK
obj.age = 30; // Throws an error

```

- Security:
 You can use proxies to restrict access to certain properties or methods of an object. For example, you can create a proxy that blocks access to sensitive data.

```javascript
const user = {
  name: 'John',
  password: 'secret'
};

const secureUser = new Proxy(user, {
  get: function(target, prop) {
    if (prop === 'password') {
      throw new Error('Access denied');
    }
    return target[prop];
  }
});

console.log(secureUser.name); // 'John'
console.log(secureUser.password); // Throws an error

```

- Logging:
 You can use proxies to log all the method calls and property accesses on an object.

```javascript
const logger = {
  get: function(target, prop) {
    console.log(`Getting property '${prop}'`);
    return target[prop];
  },
  apply: function(target, thisArg, args) {
    console.log(`Calling method '${target.name}' with arguments ${args}`);
    return target.apply(thisArg, args);
  }
};

const math = {
  square: function(x) {
    return x * x;
  },
  cube: function(x) {
    return x * x * x;
  }
};

const mathProxy = new Proxy(math, logger);
mathProxy.square(5); // Calling method 'square' with arguments 5 // 25
mathProxy.cube(5); // Calling method 'cube' with arguments 5 // 125

```

In summary, proxies are a powerful feature in JavaScript that allow you to intercept and modify operations performed on objects. They can be useful in a variety of situations, such as validation, security, logging, and more.


**3. Symbols**

Symbols are a new primitive data type introduced in ES6 (ECMAScript 2015) that allow you to create unique identifiers that cannot be accidentally overwritten or collided with other properties. Each symbol is unique and can be used as a key for object properties or other use cases.

Here's an example of creating and using a symbol in JavaScript:

```javascript
const mySymbol = Symbol();
const anotherSymbol = Symbol('another');

const obj = {
  [mySymbol]: 'foo',
  [anotherSymbol]: 'bar'
};

console.log(obj[mySymbol]); // 'foo'
console.log(obj[anotherSymbol]); // 'bar'

```


In this example, we create two symbols using the Symbol function, one without a description and one with the description 'another'. We then use these symbols as keys for two properties of an object obj, and we can access these properties using the square bracket notation and the symbol as the key.

One common use case for symbols is to create private or hidden properties on objects, which are not directly accessible or modifiable from outside the object.

```javascript
const person = {
  name: 'John',
  [Symbol('age')]: 30,
  getAge() {
    return this[Symbol('age')];
  }
};

console.log(person.name); // 'John'
console.log(person.age); // undefined
console.log(person.getAge()); // 30

```

In this example, we create a symbol to represent the age property of the person object. We then define a getAge method that returns the value of the age property using the symbol. Since the symbol is not directly accessible from outside the object, the age property is effectively private or hidden.

Another use case for symbols is to define built-in language features or behaviors. For example, several built-in symbols are defined in the language specification, such as Symbol.iterator, which is used to define iterable objects.

```javascript
const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const value of myIterable) {
  console.log(value);
}
// Output:
// 1
// 2
// 3

```

In this example, we define an iterable object using a generator function and the built-in Symbol.iterator symbol. We can then use this object in a for...of loop, which uses the built-in iterator behavior defined by the Symbol.iterator symbol to iterate over the values of the object.

In summary, symbols are a powerful feature in JavaScript that allow you to create unique identifiers and define private or hidden properties on objects, as well as to define built-in language features and behaviors. They can be useful in a variety of situations, such as creating libraries, frameworks, or other code that needs to define unique or hidden properties or behaviors.