Categories
Modern JavaScript

Best of Modern JavaScript — Super Calls and Private Variables

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at how to define classes with JavaScript.

Superconstructor Calls

We’ve to call super before we call anything else.

For example, we can’t write:

class Foo {}

class Bar extends Foo {
  constructor(foo) {
    this.foo = foo;
    super();
    this.bar = bar;
  }
}

The first line must be the super call.

Instead, we write:

class Foo {}

class Bar extends Foo {
  constructor(foo) {
    super();
    this.foo = foo;
    this.bar = bar;
  }
}

Removing the super call also gives us an error. So we can’t write:

class Foo {}

class Bar extends Foo {
  constructor() {}
}

Overriding the Result of a Constructor

We can override the result of a constructor by returning something we want in the constructor .

For example, we can write:

class Foo {
  constructor() {
    return {};
  }
}

Then when we log:

console.log(new Foo() instanceof Foo);

We get false returned.

It doesn’t matter whether this is initialized or not since we’re returning an object instead returning this implicitly in our constructor.

We don’t have to call super in the child constructor if we override the result as we did in the example.

Default Constructors for Classes

We don’t need to specify an empty constructor if we don’t put anything in there.

So if we have:

`constructor()` `{}`

we can remove it.

For derived classes, we don’t need to add a constructor just to call the super constructor.

So we don’t have to write:

constructor(...args) {
  super(...args);
}

in our code.

Subclassing Built-in Constructors

We can create subclasses of built-in constructors.

For example, we can write:

class SomeError extends Error {}
throw new SomeError('error');

We create a subclass of Error with the extends keyword.

Then we can throw it like any other Error instance.

We can also create subclasses of the Array constructor.

For example, we can write:

class Stack extends Array {
  get first() {
    return this[0];
  }
}

Then we can create a new Stack instance and use the available Array properties:

class Stack extends Array {
  get first() {
    return this[0];
  }
}

const stack = new Stack();
stack.push('foo');
stack.push('bar');
console.log(stack.first);
console.log(stack.length);

We called pusg to push entries to our Stack instance.

Then we get the first and length properties.

first is the getter we defined.

And length is inherited from Array .

Private Data for Classes

JavaScript classes have no private members.

If we want private data, then we’ve to hide them somewhere else.

Or we can just create public members with a special naming scheme to indicate that they’re private.

We can just add an underscore before the property to indicate that they’re private.

For example, we can write;

class Foo {
  constructor() {
    this._count = 0;
  }
}

we add the this._count instance property to indicate that count is private.

We can also store private properties with weak maps and symbols.

For instance, we can write:

const _count = new WeakMap();

class Counter {
  constructor(count) {
    _count.set(this, count);
  }

  increment() {
    let count = _count.get(this);
    count++;
    _count.set(this, count);
  }
}

We create 2 weak maps and use this as the key for both weak maps.

The values are set to what we pass into the constructor.

Then we can get the value with the weak map’s get method.

And set the value with the set method.

Weak maps are useful since we can access the values with this , preventing the items inside from being accessed any other way.

Conclusion

There are several things we’ve to look at when we call super .

Also, there are no easy ways to keep variables private.

Categories
Modern JavaScript

Best of Modern JavaScript — Prototypes and 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 properties in JavaScript.

Using call and apply to Call hasOwnProperty() Safely

hasOwnProperty is a method of an object’s prototype.

Therefore, it can easily be overridden with an object’s own methods.

To call hasOwnProperty safety, we can call it with call .

So instead of writing:

obj.hasOwnProperty('prop')

We write:

Object.prototype.hasOwnProperty.call(obj, 'prop')

The 2nd way is safer because hasOwnProperty is always part of the Object.prototype .

We can’t guarantee that hasOwnProperty isn’t overridden with the first way.

With ES6, we can use the Map constructor to store key-value pairs, so that we don’t need to create objects to store them.

Therefore, we won’t need hasOwnProperty as much.

Abbreviations for Object.prototype and Array.prototype

Using Object.prototype and Array.prototype are long.

But we can shorten Object.prototype to an empty object literal.

We can shorten Array.prototype to an empty array literal.

So instead of writing:

Object.prototype.hasOwnProperty.call(obj, 'prop')

We can write:

({}).hasOwnProperty.call(obj, 'prop')

With arrays, we can write:

[].slice.call(1)

The name Property of Functions

The name property of the function contains the function’s name.

For example, we can write:

function foo() {}

Then foo.name returns 'foo' .

Arrow functions also has the name property.

For instance, if we write:

`const` bar `=` `()` `=>` `{};`

Then bar.name returns 'bar' .

Default Values

If we use a function as a default value, then it gets its name from its variable or parameter.

For example, if we have:

`let` `[foo =` `function` `()` `{}]` `=` `[]`

Then foo.name is 'foo' .

Likewise,

`let` `{` bar`:` foo `=` `function` `()` `{}` `}` `=` `{};`

and

`function` `g(foo =` `function` `()` `{})` `{`
  `return` `foo.name;`
`}`

all get the same result.

Named Function Definitions

If we have function declarations, then the name property of the function will have the name:

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

foo.name would be 'foo' .

For function expressions, we get the same thing:

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

so bar.name is 'bar' .

However, if we assigned a named function to a variable, then the function’s name would be the function’s name.

For example, if we write:

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

bar()

Then we call it with bar and baz.name would be baz .

But we can’t write baz() to call it, we’ll see the ‘Uncaught ReferenceError: baz is not defined’ error.

Methods in Object Literals

Methods in object literals can be defined with fixed and computed property names.

For instance, we can write:

function qux() {}

let obj = {
  foo() {},
  bar: function() {},
  ['ba' + 'z']: function() {},
  qux,
};

foo is defined with the object method shorthand.

bar is defined as a traditional method.

baz is defined with the computed key.

And qux is passed in from the outside.

If we get the name property of each method:

console.log(obj.foo.name);
console.log(obj.bar.name);
console.log(obj.baz.name);
console.log(obj.qux.name);

We get:

foo
bar
baz
qux

Conclusion

We can use the name property to get the property name of a function.

Also, we can call a constructor instance’s method in shorter ways in some situations.

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 .