Categories
Modern JavaScript

Best of Modern JavaScript — Prototypes and Calls

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at prototypes and method calls in JavaScript.

Prototype Chains

JavaScript objects are a chain of one or more objects.

The first object inherits properties from later objects.

For example, the prototype chain of an array has an instance with the elements of the array.

Array.prototytpe has the properties provided by the Array constructor.

Object.prototype has the properties provided by the Object constructor.

And null is the end of the chain.

We can use the Object.getPrototype of method to get the prototype of the array.

For example, we can write:

const arr = ['a', 'b'];
const proto = Object.getPrototypeOf(arr);

Then we see the contents of the array’s prototype in the proto variable.

We can see various methods, the iterator, and more.

We can also use the getOwnPropertyNames method to get the names of the member of the prototype.

We can write:

const arr = ['a', 'b'];
const p = Object.getOwnPropertyNames(arr);

And we get [“0”, “1”, “length”] as the value of p .

These are the properties that can be enumerated.

Dispatched Method Calls

If we call an instance, the JavaScript interpreter does 2 steps.

It gets the method from the prototype chain.

And then it calls the method with the value of this and the arguments.

For example, we can make the 2 steps explicit by writing:

const func = arr.toString;
func.call(arr);

Use Cases for Direct Method Calls

Direct method calls are useful in ES5 since there’s no spread operator to call a function with an array spread as arguments.

To call methods with an array of items as arguments, we can write:

const arr = [1, 2];
Array.prototype.push.apply(arr, [3, 4])

We call push with the apply method.

arr is the value of this , which is the array instance.

The 2nd argument is the array of arguments we want to pass into push .

Then arr is [1, 2, 3, 4] .

The spread operator replaces the use of apply .

For instance, we can write:

const arr = [1, 2];
arr.push(...[3, 4]);

It’s much simpler and we don’t have to worry about the value of this .

They do the same thing.

We can also use the spread operator with the new operator.

For example, we can write:

new Date(...[2020, 11, 25])

apply can’t be used with new since we haven’t created an instance of the constructor yet.

In ES5, there’s no easy way to convert an array-like object into an array.

For instance, if we want to convert the arguments object into an array, we’ve to use the Array.prototype.slice method to do so.

For example, we can write:

function foo(a, b, c) {
  var args = Array.prototype.slice.call(arguments);
  console.log(args);
}

We called Array.prototype.slice.call which takes an iterable object.

It returns an array, so we can use array operations and methods with it.

Likewise, we can use this for the Nodelist returns by document.querySelectorAll ,

For example, we can write:

var divs = document.querySelectorAll('div');
var arr = Array.prototype.slice.call(divs);

We pass the divs , which is a NodeList into the slice.call method to convert it into an array.

With ES6, these are all replaced by the spread and rest operators:

function foo(...args) {
  console.log(args);
}

and

const divs = document.querySelectorAll('div');
const arr = [...divs];

We used the rest operator with foo to get the arguments as an array.

And we used the spread operator to spread the div into an array.

Conclusion

There’re a few ways to call methods.

We can call them from the instance, or we can call them with call and apply .

Categories
Modern JavaScript

Best of Modern JavaScript — Parameters and Spread

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at working with objects and array parameter destructuring and the spread operator.

Optional Parameters

We can create optional parameter by setting the parameter to undefined to indicate that it’s optional.

For example, we can write:

function foo(x, optional = undefined) {
  //···
}

We set optional to undefined to indicate that it’s optional.

Required Parameters

If we have required parameters, there’s no good way to ensure that they’re sweet with ES5.

For instance, we may have to do something like:

function foo(required) {
  if (required === undefined) {
    throw new Error();
  }
  //···
}

or we can write:

function foo(required) {
  if (arguments.length < 1) {
    throw new Error();
  }
  //···
}

They aren’t very elegant.

However, we can improve this by writing with ES6:

function checkRequired() {
  throw new Error();
}

function foo(required = checkRequired()) {
  return required;
}

We assigned a function call to the parameter so that it runs when required is undefined .

It throws an error, so it’ll be obvious when it’s undefined .

This way, we can enforce the required parameters having a value.

Enforcing a Maximum Number of Parameters

JavaScript has no way to control the number of parameters passed into the function.

However, we can do this easily with ES6 by checking the number of arguments passed in with the rest operator.

For example, we can write:

function foo(...args) {
  if (args.length > 2) {
    throw new Error();
  }
  //...
}

The rest operator returns an array, so we can check the length of it with the length property.

If there’re more parameters than we like, then we can throw an error.

We can also write:

function foo(x, y, ...empty) {
  if (empty.length > 0) {
    throw new Error();
  }
}

to ensure that we have no extra parameters.

The Spread Operator

We can use the spread operator to spread array entries as arguments of a function call.

For instance, we can write:

Math.max(...[-1, 2, 3, 4])

This is the same as:

Math.max(-1, 2, 3, 4)

We can do the same with the push method.

For example, we can write:

const arr1 = [1, 2];
const arr2 = [3, 4];

arr1.push(...arr2);

The arr2 is spread as argument of push .

The spread operator also works with constructors.

For example, we can write:

new Date(...[2020, 11, 25])

to spread an argument into the Date constructor.

The spread operator also works with arrays.

For instance, we can write:

[1, ...[2, 3], 4]

And we get [1, 2, 3, 4] returned.

We can use it to merge arrays into one.

For example, we can write:

const x = [1, 2];
const y = [3];
const z = [4, 5];

const arr = [...x, ...y, ...z];

We spread the x , y and z arrays into the array.

Then arr is [1, 2, 3, 4, 5] since the entries are spread into the new array.

Conclusion

We can add optional and required parameters in various ways.

Also, we can use the spread operator to spread arrays in various places.

Categories
Modern JavaScript

Best of Modern JavaScript — OOP Features

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at new OOP features in JavaScript.

Method Definitions

Methods can be defined in a shorter way with ES6 or later.

Instead of writing:

var obj = {
  foo: function(x, y) {
    //..
  }
};

like we do with ES5 or earlier, we can write:

const obj = {
  foo(x, y) {
    //...
  }
};

It’s much shorter and cleaner.

We can use the same shorthand for getters and setters:

const obj = {
  get foo() {
    return 123;
  },

  set bar(value) {
    this.val = value;
  }
};

We have the foo getter and bar setter as indicated by the get and set keywords.

For generator methods, we can write:

const obj = {
  * gen() {
    //...
  }
};

Property Value Shorthands

Property values can also be written in a shorter way.

For instance, we can write:

const x = 2;
const y = 1;
const obj = { x, y };

This is the shorthand for:

const x = 2;
const y = 1;
const obj = { x: x, y: y };

We can also use the shorthand together with destructuring.

For example, we can write:

const obj = {
  x: 14,
  y: 1
};
const {
  x,
  y
} = obj;

Then x is 14 and y is 1.

Computed Property Keys

We can add computed property keys to an object.

They’re denoted by brackets.

As long as the expression returns a string or symbol, we can use it inside the brackets.

For instance, we can write:

const obj = {
  foo: true,
  ['b' + 'ar']: false
};

Then we have the foo and bar properties in the obj object.

This syntax also works with method definitions.

For instance, we can write:

const obj = {
  ['h' + 'ello']() {
    return 'hello';
  }
};

to create the hello method.

We can pass in a symbol into the square brackets.

For example, to create an iterable object, we’ve to create the Symbol.iterator method to create the object.

We can write:

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
  }
};

Then we can loop through it by writing:

for (const x of obj) {
  console.log(x);
}

New Methods of Object

The Object object received several new methods.

One of them is the Object.assign method.

For instance, we can write:

const obj = {
  foo: 123
};

const newObj = Object.assign(obj, {
  bar: true
});

Then we put the bar property into obj and also return the new object.

So obj and newObj are both:

{ foo: 123, bar: true }

Object.assign() is aware of boot strings and symbols as property keys.

Only enumerable own properties are added by Object.assign .

Other kinds of properties are ignored.

The values are read by a simple get operation:

`const` `value` `=` `source[prop];`

And writing the value is done by a simple set:

target[prop] = value;

Conclusion

ES6 comes with many new object-oriented programming features.

They include method shorthand and Object.assign .

Categories
Modern JavaScript

Best of Modern JavaScript — Object and Array Parameter Destructuring

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at working with objects and array parameter destructuring.

Objects and Names Parameters

We can pass in objects as parameters and destructure them into variables.

This way, we can have one object parameter that has multiple properties and we can turn them into variables.

Now we don’t have to have many parameters in our function.

For instance, we can write:

function foo({
  a = 1,
  b = 2,
  c = 3
}) {
  console.log(a, b, c)
}

And then we have an object parameter with properties a , b and c .

And we set their default values to 1, 2, and 3 respectively.

This way, we can pass in an object with any of these properties and they’ll be destructured.

Otherwise, we set the default values.

For example, we can write:

foo({
  a: 2,
});

Then a and b are 2 and c is 3.

a is passed in but b and c are set from the default values.

This is much shorter than what we have in ES5 or earlier:

function foo(props) {
  props = props || {};
  var a = props.a || 0;
  var b = props.b || -1;
  var c = props.c || 1;
  console.log(a, b, c)
}

We have the props parameter which is an object.

If it’s falsy, then we set it to an object.

And we assign the properties of it to variables after that.

We assign the default values if they’re falsy as opposed to only when they’re undefined .

As we can see, this is much longer and we might not want to return the default value for all falsy values.

Destructuring Arrays

We can destructure arrays in parameters.

For instance, we can write:

const arr = [
  ['foo', 3],
  ['bar', 19]
];
arr.forEach(([word, count]) => {
  console.log(word, count);
});

Then we have the arr array with arrays as entries.

We destructured the callback with the array and then we can use the nested entries as variables.

Also, we can use them to transform maps by converting them to arrays and calling the map method to do what we want with it.

We can write:

const map = new Map([
  [1, 'a'],
  [2, 'b'],
  [3, 'c'],
]);

const newMap = new Map(
  [...map]
  .map(([k, v]) => [k * 2, v])
);

We have a map with the arrays in it.

Then we created a new map by spreading the existing map to an array.

And then we called map to return the new entries.

The spread operator will convert it to an array with the entries being arrays of the key and value of each entry.

Therefore, we can use the destructuring assignment in the same way.

We can do the same thing with an array of promises.

For example, we can write:

const promises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
];

Promise.all(promises)
  .then(([foo, bar, baz]) => {
    console.log(foo, bar, baz);
  });

We destructured the array parameter in then .

Then we get the destructured variables in the console log.

They have all the resolved values.

Conclusion

We can destructure object and array parameters to assign properties and array entries in arguments to variables.

Categories
Modern JavaScript

Best of Modern JavaScript — Methods, IIFEs, and this

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the spread operator and functions in JavaScript.

Method Definitions Syntax for Methods

We should use the method definition syntax for methods.

For example, we can write:

const obj = {
  foo() {},
  bar() {}
}

to define methods in obj .

This is the same as:

const obj = {
  foo: function() {},
  bar: function() {}
}

If we don’t need the value of this , we can also write:

const obj = {
  foo: () => {},
  bar: () => {}
}

We used arrow functions so that we don’t have to worry about the value of this in the function.

Avoid IIFEs in ES6

We don’t really need IIFEs in ES6 or later.

The most common use of IIFEs is to define private variables that are only available within a function.

In ES5, we have something like:

(function() {
  var tmp = 'foo';
  //...
}());

In ES6, we can just define tmp within a block:

{
  let tmp = 'foo';
  //...
}

We also used to use IIFEs as modules.

For instance, we may write something like:

var module = (function() {
  var foo = 0;

  function bar(x) {
    foo++;
    //...
  }

  return {
    bar: bar
  };
}());

We return an object with the public properties so that we can use them elsewhere.

With ES6, we don’t need this anymore since we have native modules.

For example, we can just write:

module.js

let foo = 0;

export function bar(x) {
  foo++;
  //...
}

We just create a module file and use export to export what we want from it.

Then we can use it by importing the function.

For example, we can write:

import { foo } from './module';

foo(100);

We can still use IIFEs to immediately invoked arrow functions.

For example, we can write:

const arr = [3, 2, 1];

const sorted = (() => {
  arr.sort();
  return arr.join('');
})();

to sort our array.

The Rules for this

this is defined differently in various situations.

For traditional standalone functions in strict mode, this is undefined .

For traditional standalone functions in sloppy mode, this is the window object.

Generator functions, generator methods, and methods work like traditional functions.

Arrow functions always take the value of this from the function outside of it.

Classes are implicitly strict so we can’t call a class directly.

We’ll get a TypeError if we try to call it directly.

Traditional Functions

Traditional functions are functions that we have from ES5 or earlier.

We can create it as a function expression:

const foo = function(x) {
  //...
};

or we can create a function declaration;

function foo(x) {
  //...
}

this is undefined in strict mode and it’s a global object in sloppy mode.

In method calls this is the receiver of the method call.

It’s the first argument of call or apply .

In constructor calls, this is the newly created instance.

Conclusion

There’re various kinds of functions in ES6.

Also, they all have different values of this depending on the type of function and location.