Categories
Modern JavaScript

Best of Modern JavaScript — Find Item and Holes

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at some instance methods of Array and holes.

Array.prototype.findIndex

The findIndex method lets us return the index of the item that’s found.

It takes a callback that returns the condition that we’re looking for.

The 2nd argument is the value of this we use inside the callback.

It returns the index of the first element that meets the given condition.

If none is found, then -1 is returned.

For example, we can write:

const index = [2, -1, 6].findIndex(x => x < 0)

Then index is 1.

And if we write:

const index = [2, 1, 6].findIndex(x => x < 0)

Then index is -1.

The signature is the callback is predicate(element, index, array) .

element is the array being iterated through.

index is the index of the array.

array is the array it’s called on.

Finding NaN via findIndex()

With findIndex , we can find NaN because we can use Object.is to compare against NaN .

For example, we can write:

const index = [2, NaN, 6].findIndex(x => Object.is(x, NaN))

Object.is assumes that NaN is the same as itself, so we can use it to check for NaN .

This doesn’t work with indexOf .

Array.prototype.copyWithin()

The Array.prototype.copyWithin() method lets us copy a chunk of an array into another location.

The signature of it is Array.prototype.copyWithin(target: number, start: number, end = this.length) .

target is the starting index to copy to.

start is the starting index of the chunk to copy from.

And end is the ending index of the chunk to copy from.

So if we write:

const arr = [1, 2, 3, 4, 5, 6];
arr.copyWithin(2, 0, 2);

Then we get:

[1, 2, 1, 2, 5, 6]

as the new value of arr .

Array.prototype.fill()

Array.prototype.fill() is a method that lets us fill an array with the given value.

Its signature is:

Array.prototype.fill(value, start=0, end=this.length)

The value is the value to populate.

start is the starting index of the array filling.

end is the ending index to fill to the array.

For example, we can write:

const arr = ['foo', 'bar', 'baz', 'qux'];
arr.fill(7, 1, 3)

Then arr is [“foo”, 7, 7, “qux”] .

Holes in Arrays

JavaScript allows holes in arrays.

Indexes that have no associated element inside the array is a hole.

For example, we can write:

const arr = ['foo', , 'bar']

to add an array with a hole in it.

ES6 treats holes in undefined or null elements.

If we call:

const index = [, 'foo'].findIndex(x => x === undefined);

The index is 0.

And if we write:

const entries = ['foo', , 'bar'].entries();

Then entries is:

[
  [
    0,
    "foo"
  ],
  [
    1,
    null
  ],
  [
    2,
    "bar"
  ]
]

There’re some inconsistencies in how they’re treated.

With the in operator:

const arr = ['foo', , 'bar'];
console.log(1 in arr);

We get false logged with our arr .

Conclusion

Holes in arrays are allowed in JavaScript.

Also, there’re various methods to find items with arrays.

Categories
Modern JavaScript

Best of Modern JavaScript — Class Safety and Pros and Cons

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.

Safety Checks

The JavaScript interpreter does some safety checks when instantiating classes.

this is originally uninitialized in the derived constructor means that an error will be thrown if we try to access this before calling super in a subclass.

When this is initialized, then calling super produces a ReferenceError since super is already called to initialize this .

If a constructor returns an non-object, then a TypeError is thrown.

If a constructor returns an object explicitly, then that’s used as the result.

In this case, it doesn’t matter if this is initialized or not.

The extends Keyword

The value that we’re extending must be a constructor.

However, null is allowed.

For instance, we can write:

class Foo extends Bar {}

given that Bar is a constructor.

Foo.prototype would be Bar in this case.

We can also write:

class Foo extends Object {}

since Object is a constructor.

Foo.prototype would be Object in this case.

And we can also write:

class Foo extends null {}

Then Foo.prototype is null .

Referring to Base Class Properties in Methods

We can refer to base class properties in methods.

For example, if we have:

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

  toString() {
    return `${this.name}`;
  }
}

Then we can create a subclass for Person by writing:

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }

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

We create the toString to create a method that calls the toString method of Person with super.toString .

This is done by searching for the toString method up the prototype chain to get the toString method and calls it.

Then the method is called if it’s found.

This is different from what we did in ES5 or earlier.

In earlier versions, we call a superclass method with the call method.

For example, we can write:

var result = Person.prototype.toString.call(this);

With ES6 or later, as we can see, we don’t have to refer to the parent class directly.

We just use super .

super can be used in subclass methods and constructors.

They can’t be used in function declarations.

A method that uses super can’t be moved.

It’s tied to the object that it’s created in.

Pros and Cons of Classes

There are some pros and cons of classes.

The class syntax makes constructors look more like classes from class-based languages.

The unconventional pattern of inheritance throws many people off.

It hides a lot of complexity with managing prototypes and constructors.

Classes are backward compatible with any current code, so no breaking change is introduced.

Subclassing is supported by the class syntax.

It’s also easier for beginners to under the class syntax instead of prototypes.

No library is required for an inheritance, which is good.

This makes them more portable.

They also provide foundations for more advanced OOP features like traits and mixins.

Classes can also be statically analyzed more easily with IDEs, text editors, and more.

However, they do hide the true nature of JavaScript’s object-oriented model.

JavaScript classes look like its own entity, but it’s actually a function.

However, because of the need for backward compatibility, JavaScrtipt classes can’t be a completely new entity.

This is a compromise to make the class syntax work with existing code.

Conclusion

The JavaScript interpreter provides us with safety checks for classes.

Also, there are pros and cons with the class syntax.

Categories
Modern JavaScript

Best of Modern JavaScript — Catches for Classes and Module Basics

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.

Single Inheritance

We can only inherit from one class with the extends keyword.

However, we can generate a new class from existing classes and inherit from that.

This works since extends accepts an expression that returns a constructor.

Classes Lock-In

If we want to instantiate a class, we’re forced to use the new keyword with ES6.

This means switching from a class to a factory function will mean we’ve to remove the new keyword from the existing code.

However, we can override what the constructor returns by return our own object with the constructor .

The module system and class syntax also make refactoring JavaScript code much easier than before.

Classes can’t be called as a Function

Classes can’t be called as a function even though they’re functions underneath.

This keeps options open for the future to add ways to handle function calls with classes.

Instantiate a Class Given an Array of Arguments

We can let our class constructor take an array of arguments with the rest syntax.

For instance, we can write:

class Foo {
  constructor(...args) {
    //...
  }
}

Then we can instantiate it by running:

new Foo(...args);

where args is an array of arguments.

We use the spread operator to spread the arguments into the args array as arguments.

We can then use them however we like.

Also, we can use the Reflect.construct method to create a class instance with an array of arguments.

For instance, we can write:

`const foo = Reflect.construct(Foo, ['foo', 'bar']);

We pass in our class or constructor as the first argument, and we pass in an array of arguments for the constructor as the 2nd argument.

Modules

JavaScript doesn’t have a native module system until ES6.

However, there were many module systems implemented as libraries.

ES6 modules can ve accessed in the browser and Node.js.

In the browser, we add a script tag with the type attribute set to module to import a module.

Modules are in strict mode by default.

Top-level value os this is local to the module.

Modules are executed asynchronously.

The import keyword is also provided to import module items.

Programmatic imports are also available.

The import function returns a promise that resolves to an object with the module’s contents.

The file extension for modules is still .js .

This is different from old-style scripts.

Scripts are run synchronously unless specified otherwise.

And they default to non-strict mode.

However, they can be imported asynchronously.

Each module is a piece of code that’s run once it’s loaded.

In a module, there may be declarations of various kinds, like functions, classes, objects, etc.

A module can also import things from other modules.

They may be imported with a relative path like './foo/bar' or an absolute path like '/foo/bar' .

Modules are singletons so all imports of a module are the same.

Conclusion

Classes can’t be called as a function.

We can instantiate them with an array of arguments.

Modules are useful for dividing code into smaller chunks.

Categories
Modern JavaScript

Best of Modern JavaScript — Array Holes and Operations

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at some array operations with holes.

Array Operations and Holes

Various operations treat array holes differently.

Iteration is done with the Array.prototype[Symbol.iterator] call.

It treats holes as if the entry is undefined .

We can see this if we call Symbol.iterator directly.

For instance, we can write:

const arr = [, 'foo'];
const iter = arr[Symbol.iterator]();

console.log(iter.next())
console.log(iter.next())

We return the iterator from to let us get the elements of the array sequentially with an iterator.

If we call next the first time, we get:

{ value: undefined, done: false }

And if we call next again, we get:

{ value: "foo", done: false }

As we can see, the value is undefined is returned from the first next call.

The spread operator also treats holes the same way.

For instance, we can write:

const arr = [, 'foo'];
console.log([...arr]);

After we spread the arr array, we can see the logged value is:

[undefined, "foo"]

The for-of loop also treats holes the same way.

For instance, if we write:

const arr = [, 'foo'];

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

then we get undefined and 'foo' .

Array.from() uses iteration to convert an iterable object to an array.

This works exactly like the spread operator.

For instance, if we have:

const arr = [, 'foo'];

console.log(Array.from(arr));

Then we get:

[undefined, "foo"]

If we pass in an array-like object that isn’t iterable into the Array.from method, the missing entry is still treated as undefined .

If we have:

const arr = Array.from({
  1: 'foo',
  length: 2
})

then we get the same result.

With a second argument, Array.from works like Array.prototype.map .

For example, if we write:

const arr = Array.from([, 'foo'], x => x)

console.log(arr);

Then arr is [undefined, “foo”] .

We can also use it to get the index. To do that, we write:

const arr = Array.from([, 'foo'], (x, i) => i)

then arr is [0, 1] .

Array.prototype.map skip holes, but preserve them.

For instance, if we have:

const arr = [, 'foo'].map(x => x)

console.log(arr);

Then we get:

[empty, "foo"]

The hole stays as an empty slot after mapping.

It acts the same way if we map an array with holes to an array of indexes.

If we write:

const arr = [, 'foo'].map((x, i) => i)

we get [empty, 1] .

The behavior of array holes is treated differently by different array instance methods.

forEacg , filter , and some ignore holes.

map skips but preserves holes.

join and toString treat holes like they’re undefined ,

null and undefined are treated as empty strings.

copyWithin creates a hole when copying holes.

entries , keys , values, find, and findIndex treat each hole as if they’re undefined .

fill doesn’t care whether there are holes or not.

concat , map , push , reverse , slice , sort , splice and unshift all preserve holes.

pop and shift treat them as elements.

Conclusion

Different operators and methods treat array holes differently.

Categories
Modern JavaScript

Best of Modern JavaScript — Array.from and Getting Items

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 Array.from method and some instance methods.

Array.from and Map

Array.from takes a second argument that takes a function that maps the entry to the values we want.

For example, we can write:

const divs = document.querySelectorAll('div');
const texts = Array.from(divs, d => d.textContent);

We get the div elements with querySelectorAll method.

Then we call Array.from with the NodeList with the divs as the first argument.

The 2nd argument is a callback to let us map a value.

This is shorter than writing:

const divs = document.querySelectorAll('div');
const texts = Array.prototype.map.call(divs, s => s.textContent);

The from method is static, so it’ll be inherited if we create a subclass of an array.

For instance, we can write;

const obj = {
  length: 1,
  0: 'foo'
};

class FooArray extends Array {
  //...
}
const arr = FooArray.from(obj);

We can pass in an array-like object to FooArray.from the way we do with any array.

The mapping functionality also works.

So we can write:

class FooArray extends Array {
  //...
}

const divs = document.querySelectorAll('div');
const arr = FooArray.from(divs, d => d.textContent);

We called FooArray.from like we do with Array.from and get the same result.

Array.of

Array.of is another method of an array.

It takes a list of arguments to let us create an array.

This is an alternative to the Array constructor.

Instead of writing:

const arr = new Array(1, 2, 3);

We can write:

const arr = Array.of(1, 2, 3);

Array.of is better since it returns an array with the arguments even if there’s only one argument.

This isn’t the case with the Array constructor.

If we pass one argument, then it’ll try to create an array with the given length.

This also works with subclasses of Array.

For instance, we can write:

class FooArray extends Array {
  //...
}

const arr = FooArray.of(1, 2, 3);

Then we can check if an arr is an instance of FooArray by writing:

console.log(arr instanceof FooArray);
console.log(arr.length === 3);

Array.prototype Methods

There are also new Array.prototype methods added with ES6.

They include the Array.prototype.entries() , Array.prototype.keys() , and Array.prototype.entries() .

Array.prototype.entries() returns an array with arrays of index and element as entries.

For example, we can write:

const arr = ['foo', 'bar', 'baz'];

for (const [index, element] of arr.entries()) {
  console.log(index, element);
}

index will have the index of each entry and element has the element for each index.

Array.prototype.keys() have the index of the array.

For instance, we can write:

const arr = ['foo', 'bar', 'baz'];

for (const index of arr.keys()) {
  console.log(index);
}

Then we get the index value for each entry.

Array.prototype.values returns an array of values.

So we can write:

const arr = ['foo', 'bar', 'baz'];

for (const element of arr.values()) {
  console.log(element);
}

Searching for Array Elements

We can search for array elements with the Array.prototype.find method.

It takes the predicate which is a callback that returns the condition we’re looking for.

The 2nd argument is a value for this we use in our callback.

It returns the first item that’s found.

If nothing’s found, then undefined is returned.

For example, we can write:

const num = [2, -1, 6].find(x => x < 0)

then num is -1.

If we write:

const num = [2, 1, 6].find(x => x < 0)

then num is undefined .

Conclusion

Array.from can be used to map items the way we want.

Also, we can get indexes and elements and find elements in various ways.