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.

Categories
Modern JavaScript

Best of Modern JavaScript — let and const

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at JavaScript variables.

const Creates Immutable Variables

We can create immutable variables with const .

For example, if we have:

`const` `foo` `=` `'abc';`
`foo` `=` `'baz';`

then we have a TypeError.

const does not Make the Value Immutable

Even though the variable can’t be reassigned, its value can still change.

For instance, if we have an object:

`const` `obj` `=` `{};`

We can add properties to it:

`const` `obj` `=` `{};`
`obj.prop` `=` 'foo'`;`
`console.log(obj.prop);`

obj.prop would be 'foo' .

If we want to make objects immutable, we can call the Object.freeze method:

const obj = Object.freeze({});

Object.freeze only prevents the top level from changing.

Objects stored in its properties are still mutable.

If we have:

`const` `obj` `=` `Object.freeze({` `foo:` `{}` `});`

Then we can write:

obj.foo.qux = 'baz';

and it would work.

const in Loop Bodies

We can use const in loop bodies.

For instance, we can write:

function logArgs(...args) {
  for (const [index, elem] of args.entries()) {
    console.log(index, elem);
  }
}

We call the entries method which returns entries with the index with the index of the entry and elem with the entry itself.

const prevents assignment of the array.

Lifecycle of var-Declared Variables

var variables don’t have temporal dead zones.

This means they’re available everywhere within their scope.

The declaration of it is hoisted, but the value isn’t.

Lifecycle of let-Declared Variables

let variables are only available after they’re declared.

This means within the block, the temporal dead zone is between the start of the block and when they’re declared.

This is the same with const .

If we try to access these variables before they’re declared, we’ll get a ReferenceError .

If there’s isn’t a value assigned to a let variable, it’ll be undefined .

For example, if we have:

let foo = true;
if (true) {
  console.log(foo);

  let foo;
  console.log(foo);

  foo = 123;
  console.log(foo);
}
console.log(foo)

Then the console.log(foo); will get us a ReferenceError .

And:

let foo;
console.log(foo);

will log undefined .

and:

foo = 123;
console.log(foo);

logs 123.

And foo is true .

typeof Throws a ReferenceError for a Variable in the Temporal Deal Zone

We can’t use typeof with let and const variables that haven’t been declared yet.

For example, we can write:

if (true) {
  console.log(typeof foo);
  let foo;
}

Then we’ll get a ReferenceError if we run the code.

If we want to stop that, we should move the typeof below the let declaration.

Having a temporal dead zone lets us catch programming errors.

JavaScript may also have guards in the future to do data type and content checks.

So we should make sure that data is available before doing the checks.

If they’re only available after they’re declared, then the checks can easily be done.

let and const in Loop Heads

We can use let and const in loop heads with the for, for-in, and for-of loops.

Conclusion

let and const provides many benefits that aren’t provided by var .

Categories
Modern JavaScript

Best of Modern JavaScript — Function Names

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at function names in JavaScript.

Methods in Class Definitions

We can define methods in class definitions.

For instance, we can write:

class C {
  foo() {}

  ['ba' + 'r']() {}

  static baz() {}
}

We can define class methods with computed property names.

Then we can get their name property values by writing:

console.log(C.prototype.foo.name);
console.log(C.prototype.bar.name);
console.log(C.baz.name);

As we can see, class instance methods are just methods of C ‘s prototype .

The class syntax is just syntactic sugar on top of the regular prototypical inheritance model.

Static methods are methods attached to the property.

Getters and setters also have names.

For instance, we can write:

class C {
  get foo() {}
  set bar(value) {}
}

Then we get the names of these 2 methods with:

const getter = Object.getOwnPropertyDescriptor(C.prototype, 'foo').get;
console.log(getter.name);

const setter = Object.getOwnPropertyDescriptor(C.prototype, 'bar').set;
console.log(setter.name);

We use the Object.getOwnPropertyDescriptor method to get the property descriptor of our class methods.

And then we can get the getter with the get property and the setter with the set property.

They all have the name property to get the name.

Then the 2 console logs get us:

get foo
set bar

Methods with Symbol Keys

We can create methods with symbol keys.

For example, we can write:

const foo = Symbol('foo');
const bar = Symbol();

let obj = {
  [foo]() {},
  [bar]() {},
};
console.log(obj[foo].name);
console.log(obj[bar].name);

They also have the name property.

The first console log logs ‘[foo]’ .

And the 2nd console log logs an empty string.

Class Definitions

Class definitions create functions, so they also has a name property.

For instance, we can write:

class Foo {}
console.log(Foo.name);

Then Foo.name is 'Foo' .

We can also define it by writing:

const Baz = class {};
console.log(Baz.name);

Then Baz.name is 'Baz' .

Default Exports

Default exports of functions have names set to default .

This includes the following:

`export` `default` `function` `()` `{}`
`export` `default` `(function` `()` `{});`
`export` `default` `class` `{}`
`export` `default` `(class` `{});`
`export` `default` `()` `=>` `{};`

Other Kinds of Functions

new Function() ‘s name property is 'anonymous' .

This is a bug.

func.bind() creates a function with a name value that starts with 'bound' .

For example, we can write:

function bar(x) {
  return x
}
const bound = bar.bind(undefined, 123);
console.log(bound.name);

Then bound.name is 'bound bar' .

Generator functions get the name the same way normal functions do.

Function Names Assignment

Function names are always assigned during creating and never changed later.

For instance, if we have:

function createFunction() {
  return function() {};
}
const bar = createFunction();
console.log(bar.name);

Then bar.name isn’t 'bar' , it’s an empty string.

The name property doesn’t update after it’s created.

Since it’s anonymous at the beginning, it stays anonymous.

Changing Names of Functions

We can’t change the name value of functions.

So we can’t write:

func.name = 'foo';
func.name

to change a function’s name to 'foo' .

However, we can change the name by redefining it.

For example, we can write:

function bar() {}

Object.defineProperty(bar, 'name', {
  value: 'foo',
  configurable: true
});

Then we get 'foo' logged if we log bar.name ‘s value.

Conclusion

We can get and set the name of a function in various ways.

Categories
Modern JavaScript

Best of Modern JavaScript — Function Names and Arrow Functions

Since 2015, JavaScript has improved immensely.

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

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

Determine Whether a Function was Invoked via new

We can find out whether a function was invoked with new by using the new.target property.

For instance, we can write the following to let us stop the function from being called with new :

function foo() {
  if (new.target !== undefined) {
    throw new Error('cannot be called as constructor');
  }
  //...
}

If we call it with new foo() , we get the error ‘Uncaught Error: cannot be called as constructor’.

With ES5, we can check for the value of this :

function foo() {
  "use strict";
  if (this !== undefined) {
    throw new Error('cannot be called as constructor');
  }
  //...
}

If this is undefined in strict mode, then we know it’s not called with new .

Arrow Functions

Arrow functions are new kinds of functions introduced with ES6.

It solves the problem of bindings with this , arguments and other built-in objects.

Also, they can’t be used as constructors and they’re shorter.

This means they’re great for callbacks.

Traditional Functions are Bad Non-Method Functions

Traditional functions are bad non-method functions since they bind to their own value of this inside the function.

Therefore, the following example won’t work:

function Suffix(suffix) {
  this.suffix = suffix;
}

Suffix.prototype.addSuffix = function(arr) {
  'use strict';
  return arr.map(function(x) {
    return x + this.suffix;
  });
};

The callback is defined with a traditional function, so it has its own value of this .

Therefore, there’s no suffix property in the callback.

To fix this, we can assign the this outside to another value.

For instance, we can write:

function Suffix(suffix) {
  this.suffix = suffix;
}

Suffix.prototype.addSuffix = function(arr) {
  'use strict';
  var that = this;
  return arr.map(function(x) {
    return x + that.suffix;
  });
};

We assigned this outside the callback to that , so that we can use it in the callback.

We can also specify the value of this with the map method.

The value is passed into the 2nd argument:

function Suffix(suffix) {
  this.suffix = suffix;
}

Suffix.prototype.addSuffix = function(arr) {
  'use strict';
  return arr.map(function(x) {
    return x + this.suffix;
  }, this);
};

We pass this into the 2nd argument to set this in the callback to the Suffix constructor.

And we can also use bind(this) to return a new function with the this value we want:

function Suffix(suffix) {
  this.suffix = suffix;
}

Suffix.prototype.addSuffix = function(arr) {
  'use strict';
  return arr.map(function(x) {
    return x + this.suffix;
  }.bind(this));
};

With ES6, we don’t need to do any of this.

We just use arrow functions:

function Suffix(suffix) {
  this.suffix = suffix;
}

Suffix.prototype.addSuffix = function(arr) {
  return arr.map((x) => {
    return x + this.suffix;
  });
};

We replace our function with an arrow function so won’t have to worry about the value of this in the callback.

We can replace the constructor with the class syntax:

class Suffix {
  constructor(suffix) {
    this.suffix = suffix;
  }

  addSuffix(arr) {
    return arr.map((x) => {
      return x + this.suffix;
    });
  }
}

We put everything inside the class.

Conclusion

We can determine whether a function is called with new in various ways.

Also, arrow functions don’t bind to any special values like this inside it, so we can use them for callbacks easily.