Categories
Modern JavaScript

Best of Modern JavaScript — Default Parameters and Rest

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 parameters and the rest syntax.

Why does undefined trigger default values?

undefined indicates that something doesn’t exist.

This is different from null in that it indicates an empty value.

Therefore, only undefined will trigger default values to be set.

Referring to Other Parameters in Default Values

We can refer to other parameters in default values.

For example, if we have:

function foo(x = 13, y = x) {
  console.log(x, y);
}

If we call foo(); , then we get that x and y are both 13.

If we call foo(7); , then we get that x and y are both 7.

And if we call foo(17, 12); , then we get that x is 17 and y is 12.

Referring to Inner Variables in Default Values

If we have code like:

const x = 'outer';

function foo(a = x) {
  const x = 'inner';
  console.log(a);
}

foo()

where we assign the outer variable as a value of a parameter, then even if we define a variable with the same name inside it, it’ll refer to the outer one.

We assigned the default value of a to x , so even if we defined x again with a new value, we still get a is 'outer' .

If there’s no x above the function, we’ll get a ReferenceError.

This also applies to parameters when a parameter is a function.

For example, if we have:

const bar = 2;

function foo(callback = () => bar) {
  const bar = 3;
  callback();
}

foo();

The callback is assigned to the function that returns bar by default, so that’s what will be called if we call callback with no callback passed into it.

So callback returns 2.

Rest Parameters

Rest parameters let us capture arguments that aren’t set to any parameter.

For instance, if we have:

function foo(x, ...args) {
  console.log(x, args);
  //···
}
foo('a', 'b', 'c');

Then x is 'a' and args is ['b', 'c'] .

If there’re no remaining parameters, then args will be set to an empty array.

This is a great replacement to the arguments object.

With ES5 or earlier, the only way to get all the arguments of a function is with the arguments object:

function logArgs() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}

With ES6 or later, we can just use rest parameters to do the same thing.

For instance, we can write:

function logArgs(...args) {
  for (const arg of args) {
    console.log(arg);
  }
}

We log all the arguments with the for-of loop.

Combining Destructuring and Access to the Destructured Value

Since the rest operator gives us an array, we can destructure it.

For example, we can write:

function foo(...args) {
  let [x = 0, y = 0] = args;
  console.log(x, y);
}

We set the first 2 entries of args to x and y respectively.

And we set their default values to 0.

The destructuring also works for object parameters.

For example, we can write:

function bar(options = {}) {
  const {
    x,
    y
  } = options;
  console.log(x, y);
}

We have the bar function that has the options parameter.

We destructured the object to the x and y variables.

arguments is an iterable object.

We can use the spread operator with ES6 to convert it to an array.

For example, we can write:

function foo() {
  console.log([...arguments]);
}

Then the array will have all the arguments.

Conclusion

We can use rest parameters to get all arguments passed into a function.

And we can refer to other values as default values.

Categories
Modern JavaScript

Best of Modern JavaScript — Default Parameters

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 parameters.

Default Parameter Values

We can set default parameter values in JavaScript functions since ES6.

If the value of the parameter is undefined , then the default value will be set.

For instance, if we have a function:

function foo(x, y = 0) {
  return [x, y];
}

Then if we call it by writing foo(1, 3); , then we get [1, 3] .

If we call it by writing foo(1); , then we get [1, 0] .

And if we call it by writing foo(); , then we get [undefined, 0] .

Rest Parameters

Rest parameters let us get the remaining parameters as an array.

For instance,e if we have:

function bar(pattern, ...args) {
  return {
    pattern,
    args
  };
}

Then if we call it by writing format(3, 2, 1); , we get:

{ pattern: 3, args: [2, 1] }

Destructuring Parameters

We can destructure object parameters into variables.

For instance, we can write:

function baz({
  a = 0,
  b = -1,
  c = 1
} = {}) {
  console.log(a, b, c);
  //...
}

Our baz function takes an object with the properties a , b , and c .

We set the default values for them to 0, -1, and 1 respectively.

If the argument isn’t passed in, then we set it to an object, which will then set those default values.

So if we write:

baz({
  a: 10,
  b: 30,
  c: 2
});

then a is 10, b is 30, and c is 2.

If we write:

baz({
  a: 3
});

then a is 3, b is -1 and c is 1.

And if we write:

baz({});

or

baz();

then a is 0, b is -1, and c is 1.

Whenever any value is absent, it’ll be replaced with the default value.

Spread Operator

The spread operator lets us spread iterable objects into arguments for function calls.

For instance:

Math.max(2, 3, 4, 6)

is the same as:

Math.max(...[2, 3, 4, 6])

We can also spread some arguments:

Math.max(2, ...[3, 4], 6)

In arrays, we can use the spread operator to turn iterable values in array elements.

For instance, we can write:

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

and get:

`[1,` `2, 3,` `4]`

Parameter Default Values

We can specify default values for parameters.

For instance, we can write:

function foo(x, y = 0) {
  return [x, y];
}

Then if we call it without 2nd argument, then the default value will be set as the value of y .

If we call foo(1) , then we get [1, 0] .

And if we call foo() , we get [undefined, 0] .

The default value is computed only when it’s needed

We can see this with this example:

const log = console.log;

function foo(x = log('x'), y = log('y')) {
  return [x, y];
}

If we call foo(1) , then we get 'x' .

And if we call foo() , then we get 'x' and 'y' logged.

Conclusion

We can add default parameters values to functions so that they’re assigned as the value of them when they’re undefined.

Categories
Modern JavaScript

Best of Modern JavaScript — Callable Entities and Array Like Objects

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.

Converting Iterable or Array-Like Objects to Arrays

We can convert iterable or array-like objects into arrays.

We can do this with the spread operator.

For example, we can write:

const set = new Set([1, 2, 6]);
const arr = [...set];

Then arr is [1, 2, 6] .

We can turn other iterable objects into an array the same way.

For instance, we can write:

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

Then arr is [1, 2, 3] .

If we have something that’s not iterable, but array-like, we can convert it to an array with the Array.from method.

For example, we can write:

const arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

`const` `arr` `=` `Array.from(arrayLike);`

Then arr is [“a”, “b”, “c”] .

We can’t use the spread operator to spread arrayLike into an array.

If we try, then we get a TypeError.

Callable Entities in ES6

There are several types of callable entities with ES6 or later.

They’re functions, methods, and constructors.

A function isn’t part of any object, so we call them by writing foo(1) .

Methods are functions that are properties of an object.

For example, we can call them by writing obj.foo(81) .

Constructors are functions that we can an object that’s an instance of the constructor.

For example, we can write new Constr(8) to call it.

super calls are restricted to specific locations.

We can call super method calls by writing:

super.method('abc')

And we can call the super constructor by writing:

super(8)

We can do this if a class is a subclass created by using the extended keyword.

For example, we can write:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`hello ${name}`)
  }
}

class Employee extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }

  greet() {
    super.greet()
  }
}

We called the super constructor to call the Person constructor from the child Employee class.

And we called the Person instance’s greet method with super.greet() .

Prefer Arrow Functions as Callbacks

Callbacks usually don’t need their own values of this , so we can use arrow functions to define them.

It’s also more compact so we type less.

Some APIs use this as an implicit parameter for their callbacks.

This prevents us from using arrow functions as callbacks.

For example, addEventListener may use this in their callback:

document.addEventListener('click', function() {
  console.log(this);
})

this is the document object.

However, we can change this to document so that we can use arrow functions for our callback:

document.addEventListener('click', () => {
  console.log(document);
})

Prefer Function Declarations as Standalone Functions

If we have standalone functions, we should use function declarations so that we can use them anywhere.

So we can write:

function foo(arg1, arg2) {
  // ···
}

They can be used anywhere and they look like generator functions.

However, we usually don’t need this for standalone functions.

If we don’t need their own this inside the function, then we can set the arrow function to a variable.

For example, we can write:

`const` `foo` `=` `(arg1,` `arg2)` `=>` `{`
  //...
`};`

Then we don’t have to worry about the value of this inside the function.

Conclusion

We can convert non-iterable array-like objects to arrays.

Also, there’re various callable entities with modern JavaScript.

Categories
Modern JavaScript

Best of Modern JavaScript — Callable Entities

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at functions and classes in JavaScript.

Generator Functions

Generator functions are new to ES6.

They’re denoted by the function* keyword.

The value of this in a generator function is handled the same way as in traditional functions.

We can’t call a generator function as a constructor.

If we do, we’ll get a TypeError .

We can define them as function expressions:

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

Or we can define them as function declarations:

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

Generator functions only return generator objects.

Method Definitions

Method definitions are functions that are defined in object literals.

For instance, we can write:

const obj = {
  add(x, y) {
    return x + y;
  },

  sub(x, y) {
    return x - y;
  },
};

We defined the add and sub methods in our obj object.

Also, we can define methods in classes.

For example, we can write:

class Calc {
  add(x, y) {
    return x + y;
  }

  sub(x, y) {
    return x - y;
  }
}

There’re no commas after each method in a class.

We can call super or methods of super in class methods.

Generator Method Definitions

Generators can be added as methods in an object or a class.

The syntax is the same as regular methods.

For instance, we can write:

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

In classes, we can write:

class Gen{
  * gen() {
    //...
  }
}

We can use this and super like any other class methods.

Arrow Functions

Arrow functions don’t have their own value for the following variables:

  • arguments
  • super
  • this
  • new.target

They take the value of them from the surrounding scope.

They can’t be used as constructor.

If we try to use it with the new keyword, we’ll get a TypeError.

Classes

Classes is easier to use the syntax for constructor functions.

We can define them with methods by writing:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return `${this.x}, ${this.y}`;
  }
}

We create the Point class with the toString method and a constructor.

To create a child class of the Point class, we can use the extends keyword:

class ThreeDPoint extends Point {
  constructor(x, y, z) {
    super(x, y);
    this.z = z;
  }

  toString() {
    return `${super.toString()}, ${this.z}`;
  }
}

We called super in the constructor to call the parent Point constructor.

And we called super.toString() to call the Point instance’s toString method.

Classes can’t be called as functions or methods.

Constructor follows subclassing syntax.

The base class has its own this .

And subclasses has a this with the properties of the parent class instance and its own instance.

We’ve to call super before we can access this .

Dispatched and Direct Method Calls

We can call dispatch method call by calling a method on the instance.

For instance, calling arr.splice(1, 1) is calling splice on the arr array instance.

A direct call is calling a function with call or apply .

For instance, we can write:

Array.prototype.splice.call(arr, 1, 1)

or:

Array.prototype.splice.apply(arr, [1, 1])

to call splice with arr as the value of this and the arguments either as arguments or in an array.

Conclusion

There’re many kinds of callable entities in JavaScript. They include functions, methods, generator functions, and classes.

Categories
Modern JavaScript

Best of Modern JavaScript — Arrow Function Syntax

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at arrow functions in JavaScript.

Arrow Function Syntax

We can create arrow functions by using the following syntax:

() => {
  //...
}

or

x => {
  //...
}

or

(x, y) => {
  //...
}

If we have one parameter, then we don’t need the parentheses.

We can specify the body by writing:

x => {
  return x * x
}

or:

x => x * x

Arrow functions are great for reducing the verbosity of our code.

For example, we can reduce:

const squares = [1, 2, 3].map(function (x) { return x * x });

to:

const squares = [1, 2, 3].map(x => x * x);

Omitting Parentheses Around Single Parameters

We can remove the parentheses if we have a single parameter that doesn’t have a default value.

For instance, we can write:

[1, 2, 3].map(x => 4 * x)

If we have anything else, we need the parentheses.

We can define default parameters by writing:

[1, undefined, 3].map((x = 2) => 4 * x)

Propagating Variable Values

Variable values can be propagated statically or dynamically.

Static propagation is getting variable values from outside the function:

const x = 'foo';

function foo(y) {
  return x;
}

Dynamic propagation is getting variable value from parameters:

`function` `bar(arg)` `{`
    `return` `arg;` `_// value received dynamically_`
`}`

Arrow functions don’t bind to their own special values.

These special values include:

  • arguments
  • super
  • this
  • new.target

Arrow Functions Bind Loosely

Arrow functions bind loosely.

If we have:

`const` `foo` `=` `x` `=>` `(x` `%` 5`)` `===` `0` `?` `x` `:` `0;`

Then x => (x % 5) === 0 is considered to be part of the function.

To keep the whole expression together as the function, we can wrap them in the parentheses:

`const` `foo` `=` `x` `=>` (`(x` `%` 5`)` `===` `0` `?` `x` `:` `0);`

We should wrap whatever we want in the function wither parentheses.

No Line Break After Arrow Function Parameters

We can’t add a line break after the arrow function parameters.

For instance, we can’t write:

`const` foo `=` `(x,` `y)`
`=>` `{`
  `return` `x` `+` `y;`
`};`

We’ll get a syntax error if we write that.

Instead, we write:

`const` foo `=` `(x,` `y)` `=>` `{`
  `return` `x` `+` `y;`
`};`

Then it’ll run.

This way, if our arrow function has only one parameter, we can omit the parentheses and still keep it valid.

No Statements as Expression Bodies

Expressions are code that returns a value.

For instance:

`3` `+` 39
`foo(1)`
`'abc'.length`

all return something and are expressions.

Statements do things. The following are examples of statements:

if `(true)` `{`
  //...
`}`

`return` `123;`

Most expressions can be used as statements, just that they don’t do anything.

We can put expressions into arrow functions if it’s one line long and the value will be returned.

For example, we can write:

Promise.resolve(1).then(x => console.log(x));

But we’ve to put statements in braces, so we’ve to write:

Promise.resolve(1).then(x => {
  throw x
});

Conclusion

Arrow functions can be defined and used in various ways.