Categories
JavaScript Basics

Introduction to JavaScript Symbols

In ES2015, a new primitive type called Symbol is introduced. It is a unique and immutable identifier. Once you have created it, it cannot be copied.

Every time you create a new symbol, it’s a unique one. Symbols are mainly used for unique identifiers in an object. That’s a symbol’s only purpose.

There are also special symbols that we can use to implement various operations or override the default behavior of some operations.


Defining Symbols

There are some static properties and methods of its own that expose the global symbol registry. It is like a built-in object, but it doesn’t have a constructor, so we can’t write new Symbol to construct a symbol object with the new keyword.

To create new symbols, we can write:

const fooSymbol = Symbol('foo')

Note that each time we call the Symbol function, we get a new symbol, so if we write:

Symbol('sym') === Symbol('sym')

The expression above would be false. This is because every symbol is unique.


Built-In Symbols

There are built-in Symbols that are used as identifiers for various methods and values. Methods with some symbols are called when some operators are being used.

Symbol.hasInstance

Symbol.hasInstance is a method that checks if an object is an instance of a given constructor. This method is called when the instanceof operator is invoked.

We can override the Symbol.hasInstance method as follows:

class Foo {
  static [Symbol.hasInstance](instance) {
    return typeof instance.foo != 'undefined';
  }
}
console.log({ foo: 'abc' } instanceof Foo);

In the code above, we defined that an object is an instance of the Foo class if there’s a value for the foo property. Therefore, { foo: ‘abc’ } instanceof Foo should return true since it has the foo property set to 'abc'.

Symbol.isConcatSpreadable

Symbol.isConcatSpreadable is a boolean value that indicates whether an object should be flattened in an array by the array concat method.

We can use it as in the following code:

The first console.log should output:

["a", "b", "c", true, false]

And the second one should output:

["a", "b", "c", Array(2)]

This is because before the second concat call, we set arr2[Symbol.isConcatSpreadable] to false, which prevents the content of arr2 from being spread into the new array that’s returned by the concat method.

Symbol.iterator

This is a method that’s called when we want to return an iterator for the spread operator or the for...of loop. It’s called when the for...of loop is run.

For example, given that we have the following code:

const obj = {
  0: 1,
  1: 2,
  2: 3
};
console.log(obj[0]);

If you try to loop through an array with the for...of loop or the forEach function, or try to use the spread operator with it, the example with the obj object will result in an error since it’s not an iterable object.

We can make it iterable by adding a generator function with the Symbol Symbol.iterator to it like in the following code:

Then, when we iterate the obj object with the for...of loop like the code below:

for (let num of obj) {
  console.log(num);
}

We get back the entries of the new obj object that we made iterable.

The spread operator would also work. If we have the following code:

console.log([...obj]);

We get [1, 2, 3] from the console.log output.

Symbol.match

A boolean property that’s part of a regular expression instance that replaced the matched substring of a string. It’s called by the string’s replace method.

For example, we can use it to let us call the startsWith and endsWith methods with the regular expression strings:

const regexpFoo = /foo/;
regexpFoo[Symbol.match] = false;
console.log('/foo/'.startsWith(regexpFoo));
console.log('/baz/'.endsWith(regexpFoo));

The important part is that we set regexpFoo[Symbol.match] to false, which indicates that the string we called startsWith and endsWith with aren’t regular expression objects since the isRegExp check will indicate that the '/foo/' and '/baz/' strings aren’t regular expression objects.

Otherwise, they’ll be considered regular expression objects even though they’re strings and we’ll get the following error:

Uncaught TypeError: First argument to String.prototype.startsWith must not be a regular expression

Symbol.replace

A regular expression method that replaces matched substrings of a string. Called by the String.prototype.replace method.

We can create our own replace method for our object as follows by using the Symbol.replace symbol as an identifier for the method:

The Replacer class has a constructor that takes a value that can be used to replace the current string instance.

When we run the last line, we should get ‘bar’ since string has the value 'foo' and we call the replace method to replace itself with whatever we passed into the constructor of Replacer.

Symbol.search

A regular expression method that returns the index within a string that matches the regular expression. Called by the String.prototype.search method.

For example, we can implement our own Symbol.search method as we do in the following code:

In the code above, our Symbol.search method looks up if the string, which is the string that we call search on, has whatever we pass into the this.value field which we assign when we call the constructor.

Therefore, we get true from the console.log output since 'bar' is in ‘foobar'. On the other hand, if we call:

console.log('foobar'.search(new Searcher('baz')));

Then we get the value false since ‘baz’ isn’t in 'foobar'.

Symbol.species

A property that has a function as its value that is the constructor function which is used to create derived objects.

Symbol.split

A method that’s part of the regular expression object that splits a string according to the indexes that match the regular expression. It’s called by the string’s split method.

Symbol.toPrimitive

A method that converts an object to a corresponding primitive value. It’s called when the + unary operator is used or converting an object to a primitive string.

For example, we can write our own Symbol.toPrimitive method to convert various values to a primitive value:

Then we get:

10
hello
true
false

From the console.log statements at the bottom of our code.

Symbol.toString

A method that returns a string representation of an object. It’s called whenever an object’s toString method is called.

Symbol.unscopables

An object whose own property names are property names that are excluded from the with environment bindings of the associated objects.


Conclusion

Symbols are a new type of data that was introduced with ES6. They are used to identify the properties of an object. They’re immutable and every instance is considered different, even though they may have the same content.

We can implement various methods identified by special symbols to implement certain operations like instanceof, converting objects to primitive values, and searching for substrings, in our own code.

Categories
JavaScript JavaScript Basics

Handy JavaScript Shorthands

With the latest versions of JavaScript, the language has introduced more syntactic sugar. In this article, we’ll look at handy shortcuts that are easy to read from versions of JavaScript new and old.

In this article, we’ll look at the ternary operator, declaring multiple variables, arrow functions, default parameter values, and more.

Ternary Operator

We can write an if...else statement in a concise way by using the ternary operator.

Instead of writing:

const x = 20;
let grade;

if (x >= 50) {
  grade = "pass";
} else {
  grade = "fail";
}

We can write:

const x = 20;
let grade = (x >= 50) ? "pass" : "fail";

They both check if x is bigger than or equal to 50 then assign the string 'pass' if it’s true and fail otherwise.

We can also write nested if statement with the ternary operator as follows:

const x = 20;
let grade = (x >= 50) ? "pass" : (x >= 25) ? "good fail" : 'bad fail';

This is the same as:

const x = 20;
let grade;
if (x >= 50) {
  grade = "pass";
} else {
  if (x >= 25) {
    grade = "good fail";
  } else {
    grade = "bad fail";
  }
}

Setting Default Value

We can set a default value if a variable is falsy by writing:

let x;
let y = x || 10;

This is the same as:

let x;
let y;
if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Because x || 10 means that if x is falsy, which means that x is undefined, null, 0, empty string, or NaN, then we assign 10 to y, which is the same as:

if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Shorthand for Declaring Multiple Variables

We can declare multiple variables by writing:

let x = y = z = 5;

This is the same as writing:

let x = 5;
let y = 5;
let z = 5;

It works by first assigning 5 to z, then the value of z to y, and finally the value of y to x .

If Truthy Shorthand

A shorthand for checking if something is truthy is JavaScript, which is anything other than undefined, null, 0, empty string, or NaN, is the following:

if (x){
 console.log('x is truthy')
}

The code above checks if x is truthy, then we execute the console.log if it is.

For…Of Loop Shorthand

Since ES6, we can use the for...of loop to loop through variables in an array or array-like object, which includes things like Maps, Sets, the arguments object, generators, iterators, and any object with the [Symbol.iterator] method.

We can write:

let fruits = ['apple', 'orange', 'grape'];
for (let fruit of fruits) {
  console.log(fruit);
}

This is cleaner than using a regular for loop with indexes, and it also works other iterable objects. For example, we can use it with generators:

let fruits = function*() {
  yield 'apple';
  yield 'orange';
  yield 'fruits';
}

for (let fruit of fruits()) {
  console.log(fruit);
}

Photo by Robin Mantz on Unsplash

Array.forEach

We can use the Array.forEach method to loop through an array, although it’s slower than loops.

To use it, we can write something like the following code:

let fruits = ['apple', 'orange', 'grape'];

fruits.forEach((fruit, index) => console.log(fruit));

Decimal Base Exponents

We can specify exponential numbers instead of writing out the whole number out with all the trailing zeroes.

For example, if we have:

1e0

for 1

1e1

for 10

1e2

for 100

1e3

for 1000 and so on.

Digit Separators

The latest browsers let us use an underscore to separate digits to make them easy to read. For example, we can write

100_000_000

for 100 million. Underscores can be placed anywhere we choose.

Object Property Shorthand

Instead of writing:

const foo = 1,
  bar = 2;
const obj = {
  foo: foo,
  bar: bar
};

We can write:

const foo = 1,
  bar = 2;
const obj = {
  foo,
  bar
};

The 2 pieces of code are exactly the same.

Arrow Functions

If an arrow function is only one line, then we don’t need the braces and we can return a value from it without using the return keyword.

For example:

() => 1

is the same as:

() => {
  return 1
}

We can use arrow functions if we don’t care about the value of this since arrow functions don’t change the this value inside the function.

Default Parameter Values

We can specify default parameter values in ES6. For example, we can write:

const add = (a = 1, b = 2) => {
  return a + b
}

This is the same as:

const add = (a, b) => {
  if (typeof a === 'undefined') {
    a = 1;
  }

  if (typeof b === 'undefined') {
    b = 1;
  }
  return a + b
}

The shorthands above are mostly from ES6. This version of JavaScript provides lots of handy shortcuts which lets us write code easier and reading just as easy.

for...of loop is very useful since it can loop through arrays and array-like objects. No other loops can do that.

Digit separators are more recent and available in the latest browsers only.

Categories
JavaScript Basics

Introduction to Web Workers

To run tasks that take a long time to complete, we need a way to run them in the background.

Web workers are the way to do that. They live in their own context within the browser. Therefore, they don’t have access to the DOM and have their own global variable, self.

A web worker can’t access the window object, but we still use many things that are parts of it like WebSockets and IndexedDB.

In this article, we’ll look at how to create a basic web worker that can send messages between workers and other scripts.


Characteristics of Web Workers

Web workers allow us to send messages between them and other scripts. Non-worker scripts can listen to messages that are emitted by workers by attaching handlers for messages.

To send messages, we use the postMessage() method. The message can be accessed via the Message event’s data property. Message event objects are passed into the event handler of the onmessage handler.

Workers can spawn new workers, as long as they are hosted within the same origin as the parent page. Workers may also use XmlHttpRequest for network I.O, with the exception that the responseXML and channel attributes on XMLHttpRequest always return null.

In addition to dedicated workers, there are also other kinds of workers:

  • Shared workers are workers that can be used by multiple scripts in different windows, IFrames, etc, as long as they’re in the same domain as the worker. They are more complex than dedicated workers in that scripts must communicate via an active port.
  • Service workers act as proxy services that sit between web apps, the browser, and the network. They are intended to create effective offline experiences. They intercept network requests and take appropriate actions based on whether the network is available.
  • Chrome workers are a Firefox-only type of worker that we can use when developing add-ons and want to use workers in extensions.
  • Audio workers provide the ability to direct scripted audio processing to be done inside a web worker context.

Photo by David Siglin on Unsplash


Creating a Simple Dedicated Worker

We can create a dedicated worker by creating a simple worker script and then have other scripts communicate with it.

For example, we can create a dedicated worker that takes values from two inputs and then compute the result by adding them and sending it back to the screen.

First, we create the HTML for getting the input values as follows:

Then, in main.js, we add keyup event handlers to the inputs to send the values to our dedicated worker as follows:

We also add the onmessage event handler to receive the message from the worker, which we’ll add to worker.js to get the message back from it in the following piece of code:

worker.onmessage = e => {  
  result.textContent = e.data;  
};

Finally, we create the worker code in worker.js as follows:

The code above gets the message from main.js, where we called postMessage. Whatever we passed to postMessage will be in the e parameter and we can get the data from there.

Then, in this file, we get the two numbers we passed in with the data property on the first line of the function.

We then check if both numbers are numbers and if they are, we add them together and then send the sum back to main.js with postMessage after we compute the sum and put the result in the workerResult string.

In the end, we should get something like the following:

As long as the dedicated worker script is in the same domain as our other scripts, it’ll run.

The Worker constructor also takes an option object with the following options:

  • type: A string specifying the type of worker to create. The value can be classic or module and defaults to classic.
  • credentials: A string specifying the type of credentials to use for the worker. The value can be omit (no credentials required), same-origin, or include. If it’s not specified, the type is class, then omit is the default.
  • name: A string specifying the identifying name for the DedicatedWorkerGlobalScope representing the scope of the worker. It’s mainly used for debugging.

The constructor will throw a SecurityError if it’s not allowed to start workers, like if the URL is invalid or the same-origin policy is violated.

  • NetworkError is raised if the MIME type of the worker script is incorrect.
  • SyntaxError is raised is the worker’s URL can’t be parsed.

Creating dedicated workers is simple. They let us run background tasks. Communication between workers and other scripts is by sending messages back and forth.

Web workers have their own context so they can’t use the window object or access the DOM.

Categories
JavaScript JavaScript Basics

Introduction to JavaScript Modules

JavaScript modules allow us to divide code into small pieces. They also let us keep some code private while exposing other pieces of code that can be imported into another module.

In this article, we’ll look at how to use define and use them.

Named Exports

Names exports start with the export keyword and they expose items from a module to the outside. Then we can import them somewhere else.

There can be multiple named exports in one module. For instance, we can write:

export const foo = 1;  
export let bar = 1;

Then we can import them into another module as follows:

import { foo, bar } from "./module";
const baz = foo + bar;

We can also import the whole module with the * sign as follows:

import * as module from "./module";
const baz = module.foo + module.bar;

Default Export

We can export one default export using the export default keywords. For instance, we can write:

export default 1;

Then import it as follows:

import foo from "./bar";  
const baz = foo;

We can also export functions and class as follows:

export default () => {};

or:

export default class {}

We don’t need a semicolon at the end of the class export.

Also, we can define default exports with the following code:

const foo = 1;  
export { foo as default };

Browser Differences Between Scripts and Modules

In browsers, scripts are denoted by the script tag. Modules are the same, but it’s denoted by the type attribute with value module .

Scripts are not in strict mode by default, while modules are in strict mode by default.

Top-level variables are global in scrips and are local to the module in modules.

Top-level value of this is window in scripts and it’s undefined in modules.

Scripts are run synchronously while modules are run asynchronously.

There are no import statements in scripts and we can selectively import module members in modules.

We can programmatically import both scripts and modules using promise-based APIs.

Module Characteristics

ES6 modules can be statically analyzed for static checking, optimization, and more. It has a declarative syntax for importing and exporting.

Imports are hoisted to the top so that they can be referenced anywhere in the module.

For instance, if we have:

export const foo = 1;

Then we can import it as follows:

const baz = foo;
import { foo } from "./bar";

Also, they must be at the top-level. Therefore, we can’t have something like:

{  
  import { foo } from "./bar";  
  const baz = foo;  
}

Imports are Read-Only Views on Exports

ES6 imports are read-only views on export entities. Connections to variables inside the module that imported the export remain live.

For instance, if we have:

export const foo = 1;

Then if we have the following:

import { foo } from "./bar";  
foo = 1;

Then we’ll get a ‘”foo” is read-only.’ error since it’s referencing the export directly in a read-only manner.

We get the same result if we change the const to let .

Cyclic Dependencies

If module A and B import members from each other, then we call it a cyclic dependency. This is supported with ES6 modules. For instance, if we have:

index.js :

import { foo } from "./bar";
export const baz = 2;

bar.js :

import { baz } from "./index";
export let foo = 1;

Then the modules are cyclic dependencies since we import a member from bar inindex and we import a member from index in bar .

This works because imports just refer to the original data, so it doesn’t matter when they come from.

Importing Styles

We can import JavaScript modules in various ways. One way is the default import, which is how we import members from a module.

For instance, we can write:

bar.js :

let foo = 1;  
export default foo;

Then we can import it as follows:

import foo from "./bar";

Named imports can be imported as follows. Given the following named exports:

export let foo = 1;

Then we can import it as follows:

import { foo } from "./bar";

We can rename named exports by using the as keyword as follows:

import { foo as baz } from "./bar";

We can also rename default exports as follows:

import { default as foo } from "./bar";

We can also have empty where we don’t import anything. Instead, we run what’s included in the module.

For instance, if we have the following in bar.js :

console.log("bar");

Then we can run the code in bar.js as follows:

import "./bar";

Therefore, we should see 'bar' logged in the console log.

Conclusion

ES6 modules are a great way to divide code into small chunks. We can export module members and import them in another file. Imported members are read-only. Modules are in strict mode by default, so we avoid lots of issues with non-strict mode.

Categories
JavaScript JavaScript Basics

How to Add an ID to Element with Javascript

We can add an ID to a given element with JavaScript.

To do this, first we get the element. We can use the following methods:

  • document.querySelector
  • document.querySelectorAll
  • document.getElementById
  • document.getElementsByTagName

We probably want to use anything other than getElementById since our element doesn’t have an ID yet.

document.querySelector can get an element with any CSS selector and return it.

For instance, given that we have an element with class foo, we can write:

const el = document.querySelector('.foo');

With querySelectorAll, it returns a NodeList with all elements with the given CSS selector.

So we’ve to find the one that’s right for us. But assuming that we want the first element, we can write:

const el = document.querySelectorAll('.foo')[0]

With getElementsByTagName, we can get all the elements by their tag name.

For instance, we can write:

const el = document.getElementsByTagName('div')[0]

to get all the divs with the given name.

In all cases, we can get an HTMLElement object, which has the id property, which we can use to set the ID of an element by assigning a string to it.

We can write:

el.id = 'bar';

to set the ID of el above to bar.

The id property of an HTMLElement can be set to let us add an ID to the element of our choice with JavaScript.