Categories
Flow JavaScript

JavaScript Type Checking with Flow — Arrays and Tuples

Flow is a type checker made by Facebook for checking JavaScript data types. It has many built-in data types we can use to annotate the types of variables and function parameters.

In this article, we’ll look at how to add Flow types to arrays and tuples.

Array Definition

We define arrays exactly like in JavaScript. To define an array, we can use the literal:

let arr = [1, 2, 3];

We can also use the Array constructor as follows:

new Array(3);

Array constructor with one argument will create an empty array with the given length, so new Array(3) returns:

[empty × 3]

Calling the Array constructor with more than one argument creates an array with the arguments as the content, so:

new Array('foo', 'bar')

will get:

["foo", "bar"]

We can also add array entries after it’s defined:

let arr = [];  
arr[0] = 'foo';

Array Type

We can type arrays by using the Array<Type> syntax, where Type is any type that we want the array entries to be.

For example, we can define a number array by writing:

let arr: Array<number> = [100, 200];

We can type an array with different types of values by using the mixed type as the type. For instance, we can write:

let arr: Array<mixed> = [1, true, "foo"];

Array Type Shorthand

We can use the Type[] syntax as a shorthand for typing array. For example, we can write:

let arr: number[] = [100, 200];

to declare a number array.

We can declare an array that can be null by putting a question mark before the type name. For example, we can write:

let arr: ?number[] = null;

Also, we can assign a number array to it:

let arr: ?number[] = [1, 2];

If we want an array to take on null values in addition to values with the given type, we can put parentheses around the type name. For example, if we want a numerical array that can have null entries, we can write:

let arr: (?number)[] = [1, 2, null];

Accessing Array Values

Flow doesn’t check whether an array entry is defined before we access it. Therefore, we can run into undefined values at runtime, which may cause errors.

For instance, we can write:

let arr: number[] = [1, 2];  
let num = arr[3];

We should check if entries are undefined before accessing them.

Read Only Arrays

We can define read-only arrays by writing $ReadOnly<T> , where T is the placeholder for the type that we want the array values to take on.

For example, we can define a numerical read-only array by writing:

const arr: $ReadOnlyArray<number> = [100, 200];

We can only read the values from the arr array.

Also, read-only arrays can take on object types. For example, we can write:

const arr: $ReadOnlyArray<{foo: number}> = [{foo: 1}, {foo: 2}];

In the case of object entries, we can modify the property value of the object’s existing properties as follows:

arr[0].foo = 2

Note that read-only arrays have subtypes of a union type. For example, we $ReadOnlyArray<number> is a subtype of $ReadOnlyArray<number | string> .

However, Array<number> isn’t a subtype of Array<number | string> .

For example, we can write:

const foo = (arr: $ReadOnlyArray<number | string>) => {  
    
}const array: Array<number> = [1]  
foo(array)

In the code above, array will be converted to a $ReadOnlyArray<number | string> as it’s passed in to foo . This is because read-only arrays can’t have its entries modified to types that aren’t specified, so Flow allows it to be passed in.

Tuples

Tuples are lists with a small number of items. In JavaScript, we create a tuple with arrays.

With Flow, we have a tuple type to type these kinds of arrays. For example, we can write:

let tuple: [string, boolean] = ['foo', true];

Then the first entry always is a string and the second is always a boolean.

We can access entries by its index, so given the tuple that we have above, we can write:

let str : string  = tuple[0];

We can’t access indexes that aren’t defined in the tuple. For example, the following code will fail:

let str : string  = tuple[3];

since we don’t have 4 or more elements in our tuple.

Flow doesn’t know the type of value we try to access, so whatever the value is will have the union of all the types in the tuple.

For example, the value of tuple we have above will be of type string | boolean :

let val: string | boolean = tuple[0];

When we set new values to a tuple entry, it must match the type of that’s defined in that location. For example, if we have:

let tuple: [number, boolean, string] = [1, false, "foo"];

Then whatever we assign to tuple[0] must be a number, tuple[1] must be a boolean, and tuple[2] must be a string.

Strictly Enforced Length

Lengths of tuples are strictly enforced, so we can assign a tuple of one length to one with different length.

For example, the following assignment will fails since tuple1 and tuple2 have different lengths:

let tuple1: [number, boolean, string] = [1, true, 'foo'];  
let tuple2: [number, boolean] = tuple1;

Tuples aren’t Arrays

In Flow, tuples aren’t arrays even though they look the same. We can’t use arrays methods with tuples and we can’t assign arrays to tuples regardless of length.

For example, the following will fail:

let array: number[] = [10, 20];  
let tuple: [number, number] = array;

Assigning tuples to arrays also doesn’t work. For example:

let tuple: [number, number] = [1, 2];  
let arr: number[] = tuple;

will fail.

Array methods like join or push can’t be used on tuples, so:

tuple.push(3);  
tuple.join(',');

will give errors.

Arrays and tuples are lists of items. They aren’t the same. Arrays can use array methods. Also, we specify the type for all entries at once. With tuples, we define the type for each entry. Tuples also can’t use array methods.

Arrays can have null entries if we add a question mark before the type name and parentheses around it. An array can be null if we skip the parentheses and keep the rest.

Categories
JavaScript

More Lodash Features that are Available in Plain JavaScript

In recent years, new features in JavaScript have been rolling out at a rapid pace. The deficiencies that are filled in by other libraries before have become built-in features of plain JavaScript.

In this article, we’ll look at the methods in Lodash that are now available in plain JavaScript, like function currying, partially applied functions and more.

Some features are better with Lodash but for others, plain JavaScript will suffice.

Curry

The curry method in Lodash returns a function that has one or more arguments of the function originally passed in. We can use it as follows:

const subtract = (a, b) => a - b;  
const currySubtract = _.curry(subtract);  
const subtract1 = currySubtract(1);  
const diff = subtract1(5);  
console.log(diff);

In the code above, we defined the subtract function which returns the first parameter subtracted by the second.

Then we called the curry method with the subtract method passed in to create a new method that makes one argument and returns the subtract function with the first argument set by the parameter. That’s the currySubtract function.

Then we call the currySubtract to set the argument of the subtract function and return the function with the first argument set. Finally, we call the subtract1 function with the second argument of subtract to get the final result.

We can do the same thing with plain JavaScript by writing:

const currySubtract = a => b => a - b;  
const subtract1 = currySubtract(1);  
const diff = subtract1(5);  
console.log(diff);

It does exactly the same thing, but without calling the curry method.

Partial

Lodash also has a method for partially applying a function, which is different from curry since some of the arguments of the function are passed into the function directly and the new function is returned.

For example, we can write the following:

const add = (a, b) => a + b;  
const add1 = _.partial(add, 1);  
const sum = add1(2);  
console.log(sum);

The partial method passed in the first argument and returns the function with the first argument passed in. This gets us the add1 function.

Then when can call the add1 function with the second argument, which is 2 in the code above, and we get 3 for the sum .

In plain JavaScript, we can write:

const add = (a, b) => a + b;  
const add1 = b => add(1, b);  
const sum = add1(2);  
console.log(sum);

Again, we can skip the Lodash partial method call like we did with the curry method call.

Eq

Lodash has the eq method to compare values. For example, we can write:

const equal = _.eq(1, 1);

It does the same thing as the Object.is, so we can just use that.

Add

It also has the add method, which we can use as we do in the following code:

const sum = _.add(1, 1);

We see the value is 2. It does the same thing as the + operator, so we can use that instead.

Nesting Operators

The good thing is that we can pass these methods straight into other Lodash methods like map and reduce as follows:

const mult = _.map([1, 2, 3], n => _.multiply(n, 2));

We get [2, 4, 6] from the code above, and we get 6 from:

const sum = _.reduce([1, 2, 3], _.add);

At

The at method lets us access the value of the properties of an object or an entry of an array by its index.

For example, given the following object, we can write the following:

const obj = { a: [{ b: { c: 2 } }, 1] };

We can get the value of the c property with at by writing:

const c = _.at(obj, ["a[0].b.c"]);

Then we get 2 for c .

Also, we can access more than one property of an object by passing more paths into the array above:

const vals = _.at(obj, ["a[0].b.c", "a[0].b"]);

Then we et:

2  
{c: 2}

In JavaScript, we can access the paths directly:

const vals = [obj.a[0].b.c, obj.a[0].b];

However, it’s good for access paths that may not exist. For example, given the same object, if we write the following:

const vals = _.at(obj, ["a[0].b.c", "d.e"]);

Then we get undefined for the second entry instead of crashing the app.

As we can see, Lodash still has some advantages, with object path access. However, other operators like add, multiply, curry and partial, we can define easily with plain JavaScript ourselves, so Lodash still has some value.

Categories
JavaScript

Using JavaScript’s Object Constructor — Part 1

In JavaScript, the Object constructor lets us create object wrapper with the given values. It will create an empty object if null or undefined is passed into the Object constructor. If the value passed into the constructor is an object already then, it will return the object.

The Object constructor has 2 properties. It has a length property that is always 1, and like all other objects, the Object constructor has a prototype to get all the property additions to the type Object.

The Object constructor has many useful methods that can be used without constructor a new object. Part 1 of this list is below:

Object.assign()

The Object.assign() method makes a shallow copy of an object. The first parameter is a target object that you copy the object to, and the second parameter accepts an object that you want to copy. Note that if the source and target objects have the same properties, the source object’s property value to overwrite the one in the target object. For example, we can write:

const target = { a: 1, b: 2 };  
const source = { b: 3, c: 4};
const newObj = Object.assign(target, source);  
console.log(newObj)

If we run the code, we will get { a: 1, b: 3, c: 4} . We can also copy arrays. For example, we can write:

const targetArr = [1,2];  
const sourceArr = [2,3];
const newArr = Object.assign(targetArr, sourceArr);  
console.log(newArr)

We get [2,3] logged when we run the code above. For arrays, it will overwrite the whole target array with the source array.

Object.create()

The Object.create() method creates a new object with the object you pass in as the prototype of the new object. For example, we can write:

const obj = { a: 1, b: 2, c: 3 };  
const newObj = Object.create(obj);  
console.log(newObj);

In the code above, when we log newObj , it doesn’t have its own properties that weren’t inherited from obj . This is because we only passed in the first argument to the constructor, which is the prototype for the object that’s returned. If we want to add properties that are available only in the returned object, we pass in an object with the property names as keys and the properties writable , configurable , enumerable and value as properties of the property name keys, which is called the property descriptor. For example, we can write:

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
};

const childObj = {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  }  
}  
const newObj = Object.create(obj, childObj);  
console.log(newObj);

In the code above, writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property.

If we want to create an object that has properties that can’t be changed, then we set writable to false , like in the following code:

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
};

const childObj = {  
  a: {  
    writable: false,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  }  
}  
const newObj = Object.create(obj, childObj);  
newObj.a = 1;  
newObj.d = 1;  
console.log(newObj);

Notice that the assignment operator has no effect. If we have strict mode enabled, then a TyepError will be thrown. We have {a: “hello”, d: 1} logged in the console.log . This means that writable set to false is working for the property a and writable value set to true is working for the property d .

If we pass in null to the constructor, we get an empty object:

const nullObj = Object.create(null)  
console.log(nullObj)

We will get an error ‘Object prototype may only be an Object or null: undefined’ is we pass in undefined as the prototype of an object like in the code below:

const undefinedObj = Object.create(undefined);  
console.log(undefinedObj)

Object.defineProperty()

The Object.defineProperty() method defines a new property on an object. The first parameter is the object that you want to add the property to. The second parameter is the name of the property you want to add passed in as a string, and the last parameter is the property descriptor included in the Object.create() method when we try to add properties to the returned object. The property descriptor should have the properties writable , configurable , enumerable and value as properties of the property name keys. The writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property. For example, we can write:

let obj = {};

Object.defineProperty(obj, 'a', {  
  writable: false,  
  configurable: true,  
  value: 'hello'  
})

console.log(obj.a)  
obj.a  = 1;  
console.log(obj.a)

As we can see, the property descriptor acts the same way as in Object.create() . When writable is false , assignment to the property has no effect. If we have strict mode enabled, then a TyepError will be thrown.

We can also define getters and setter function for properties called get and set respectively for a property:

let obj = {};  
let value;

Object.defineProperty(obj, 'a', {  
  get() {  
    return value;  
  },  
  set(a) {  
    value = a;  
  }  
});

console.log(obj.a)  
obj.a = 1;  
console.log(obj.a)

As we can see, in the code above, we defined the property a for the object obj with the get and set functions to get the value of the property and set the value respectively.

Accessor properties are set on the prototype if we defined it on the prototype, but value properties are set on the current object. If an object inherits non-writable properties, it will still be non-writable on the current object. For example, if we have:

let ObjClass = function() {};  
ObjClass.prototype.a = 1;
Object.defineProperty(ObjClass.prototype, "b", {  
  writable: false,  
  value: 1  
});

const obj = new ObjClass();  
ObjClass.prototype.a = 3  
obj.a = 2  
ObjClass.prototype.b = 3  
obj.b = 2  
console.log(obj);

Then assigning to property b is no effect. If we have strict mode enabled, then a TyepError will be thrown.

Object.defineProperties()

The Object.defineProperties method let us define more than one property on an object. The first parameter of the method is the object that you want to define the properties on, and the second object contains the property names as key and the corresponding property descriptors as values. The property descriptor should have the properties writable , configurable , enumerable and value as properties of the property name keys. The writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property. For example, we can write:

let obj = {}  
Object.defineProperties(obj, {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: false,  
    configurable: true,  
    value: 'hello'  
  }  
})  
obj.a = 1;  
obj.d = 1;  
console.log(obj);

To add the a and d properties where a ‘s value can change and d ‘s value can’t. The console.log will show {a: 1, d: “hello”} since the value assignment to d fails because the writable property of property d ‘s descriptor is set to false . We can also set configurable to false to prevent it from being deleted or having its property descriptor changed. For example, we can write:

let obj = {}  
Object.defineProperties(obj, {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: false,  
    configurable: false,  
    value: 'hello'  
  }  
})  
obj.a = 1;  
delete obj.d;  
console.log(obj);

If we run the code above, then we can see that the delete operator had no effect on property d of obj .

Object.entries()

The Object.entries() method returns an array of key-value pairs of an object that are enumerable by the for...in loop. They are returned in the same order as they are in if they are iterated with the for...in loop. Each entry of the array will have the key as the first element and the value of the corresponding key as the second element. For example, we can use it to loop through the properties of an object in the following code:

const obj = {  
  a: 1,  
  b: 2  
};

for (let [key, value] of Object.entries(obj)) {  
  console.log(key, value);  
}

When we run the code above, we get a 1 and b 2 . These are the key-value pairs of this object.

Object.freeze()

The Object.freeze() method freezes an object. This means that all properties’ values in an object can’t be changed. Also, new properties can’t be added to it, and existing property descriptors for the frozen object can’t be changed. The object is frozen in place. This method doesn’t return a new frozen object. Instead, it returns the original object before it’s frozen. The frozen object’s prototype also can’t be changed. For example, if we run the following to freeze an object:

const obj = {  
  a: 1  
};
Object.freeze(obj);
obj.a = 2;
console.log(obj.a);

We get that obj.a is still 1. This is because the object’s properties’ values can’t be changed. If we have strict mode enabled, a TypeError will be raised. It’s important to note that values that are objects can still be modified. For example, if we have the following code:

const obj = {  
  a: 1,  
  b: {  
    c: 2  
  }  
};
Object.freeze(obj);
obj.b.c = 3;
console.log(obj.b.c);

Then obj.b.c is 3 since Object.freeze doesn’t freeze properties that are inside of nested objects unless they’re frozen explicitly. So if we have:

const obj = {  
  a: 1,  
  b: {  
    c: 2  
  }  
};

Object.freeze(obj);  
Object.freeze(obj.b);
obj.b.c = 3;
console.log(obj.b.c);

Then obj.b.c is 2 since we frozen obj.b so that we can’t modify the value of obj.b .

The Object constructor has many more methods for constructing objects from an array of arrays with key and value of properties, and also methods to get property descriptors from objects, property names, property symbols, gets the keys of objects and prevent properties from being added or deleted or modify their property descriptors.

Categories
Express JavaScript Nodejs

Use body-parser Express Middleware to Parse JSON and Raw Requests

By default, Express 4.x or later doesn’t come with anything to parse request bodies. Therefore, we need to add something to do this.

In this article, we’ll look at how to use the body-parser middleware to do this with JSON and raw bodies.

Adding the Body-Parser Package

body-parser is a Node package that we can add onto our Express app to parse request bodies.

It doesn’t support multipart bodies, so we have to use other middleware packages to parse those.

However, it can parse JSON bodies, raw request bodies, text, and URL-encoded bodies.

To install the package, we run:

npm install body-parser

Then we can include by adding:

const bodyParser = require('body-parser');

Parsing JSON Bodies

We can parse JSON bodies by calling the JSON method. It takes an optional object with a few properties.

The options include:

  • inflate — compressed request bodies will be inflated when this is set to true . Otherwise, they’ll be rejected.
  • limit — controls the maximum request body size. If it’s number, then it’s measured in bytes. If it’s a string then it can be parsed into a number of bytes.
  • reviver — this is a function that’s passed into JSON.parse as the second argument to map values to what we want
  • strict — only accepts objects and arrays when set to true . Otherwise, it’ll accept anything that JSON.parse accepts. Defaults to true .
  • type — this is used to determine what media type it’ll parse. It can be a string, array of strings or a function. If it’s not a function, then it’s directly passed into the type-is library. Otherwise, the request is parsed if the data type the function is called with returns a truthy value
  • verify — this is a function with signature (req, res, buf, encoding) , where buf is a Buffer object of the raw request body. The parsing can be aborted by throwing an error in the function.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  reviver: (key, value) => {  
    if (key === 'age') {  
      if (value < 50) {  
        return 'young'  
      }  
      else {  
        return 'old';  
      }  
    }  
    else {  
      return value;  
    }  
  }  
};  
app.use(bodyParser.json(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we make a POST request to / , we get back:

{  
    "name": "foo",  
    "age": "young"  
}

as the response since we check the age field in the reviver function and return 'young' or 'old' depending on the value . Otherwise, we return the value as-is.

The request body is parsed and set as the value of req.body .

Parsing Raw Bodies

We can parse raw bodies as a buffer. It supports the automatic inflation of gzip and deflate encodings.

The parsed body containing the parsed data is populated on the request object, i.e. it’ll be set as the value of req.body .

It takes an optional option object that can take the following properties:

  • inflate — compressed request bodies will be inflated when this is set to true . Otherwise, they’ll be rejected.
  • limit — controls the maximum request body size. If it’s number, then it’s measured in bytes. If it’s a string then it can be parsed into a number of bytes.
  • type — this is used to determine what media type it’ll parse. It can be a string, array of strings or a function. If it’s not a function, then it’s directly passed into the type-is library. Otherwise, the request is parsed if the data type the function is called with returns a truthy value
  • verify — this is a function with signature (req, res, buf, encoding) , where buf is a Buffer object of the raw request body. The parsing can be aborted by throwing an error in the function.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  type: 'text/plain'  
};  
app.use(bodyParser.raw(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we send a POST request to / with the body foo , we get back foo as the response.

This is because we specified text/plain as the type to parse the raw data.

It also smart enough to parse multiple body types. We can do that by passing in an array of type strings as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();  
const options = {  
  inflate: true,  
  limit: 1000,  
  type: ['text/plain', 'text/html']  
};  
app.use(bodyParser.raw(options));
app.post('/', (req, res) => {  
  res.send(req.body);  
});
app.listen(3000);

Then when we make a POST request to / with the body <b>foo</b> . Then we get back <b>foo</b> .

Conclusion

We can use the body-parser middleware to parse JSON and raw text bodies.

It also takes a variety of options to let us control whether to inflate compressed request bodies, map JSON values to something else, limit the size of a request body, and so on.

For raw request bodies, we can use body-parser to specify the type that the data is so we can parse it to that type of object and set it to req.body .

Categories
JavaScript

More JavaScript Array Tips — Truncating and Filling Entries

JavaScript, like any other programming language, has many handy tricks that let us write our programs more easily. In this article, we will look at how to do different things that involve arrays, like truncating arrays, converting arrays to objects and filling arrays with data.

Truncating Arrays

In JavaScript, array objects have a length property that specifies the length of an array. It’s both a getter and setter property so in addition to being able to get the length of an array, we can also use it to set the length of an array.

Setting the length property to be less than the original length of the array will truncate the array up to the number of entries that we specify. For example, if we want to only keep the first entry of an array, then we can write something like:

let arr = [1, 2, 3, 4, 5, 6];  
arr.length = 1  
console.log(arr);

We simply set the length property of our array and then magically we get only the first element of the array left.

Alternatively, we can use the splice method to remove entries from an array. This method lets us removing, replace an existing element or adding new elements in place, but we only need it to delete elements from the array.

The argument of the splice method is the starting index start of which to start changing the array. It can be positive or negative. If it’s negative, then it’ll start changing the array from the end and move towards the start of the array. The end index is -1, the second is -2 and so on.

The second argument of the splice method is the deleteCount , which is an optional argument that lets us specify how many items to delete starting from the start parameter in the first element.

Subsequent arguments are the items that we want to insert into an array. This can go on for as long as we want. Only the first argument is required.

For example, if we want to truncate an array and keep only the first 2 elements, we can write something like the following code:

const arr = [1, 2, 3, 4, 5, 6];  
arr.splice(2)  
console.log(arr);

As we can see we only need to specify the first argument, and then the deleteCount will be automatically set so that it removes the entries on the right of the array. We can also specify a negative start index like in the following code:

const arr = [1, 2, 3, 4, 5, 6];  
arr.splice(-1 * arr.length + 1)  
console.log(arr);

So if we specify -1 * arr.length + n in the first argument, then we keep the first n entries of the original array.

We can also use the slice method to truncate arrays. It’s used for extracting values from an array in general and returns a new array with the extracted values. The slice method takes 2 arguments.

The first is an optional argument to specify where to begin extracting entries from an array. The second argument is another optional argument that specifies the end index of the original array to end the extraction of values from it. The value at the end index itself is excluded from the extraction.

For example, if we want the first 2 values from the original array, then we can write something like the following code:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.slice(0, 2);  
console.log(arr);

When we run the code, we get back [1, 2] as the new value of arr . We can also specify negative indexes for the start and end. The last entry of the array is at -1, the second last element is at the index -2, and so on. So if we want to extract the first 2 elements of an array with negative indexes, we can write something like the following:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.slice(-1 * arr.length, -1 * arr.length + 2);  
console.log(arr);

The first element of an array would be at index -1 * arr.length if we expressed it in terms of a negative index. Then the subsequent entries would be at -1 * arr.length + n where n is array entry at the n th position.

Filling Arrays with Data

With ES6 or later, we can fill arrays with an array with new data with the fill method. The fill method takes up to 3 arguments. The first is the value we want to add to the array.

The second optional argument is the start index of which we want to start filling data.

The default value of the second argument is 0. The third argument is an optional one that lets us specify the end index of to fill the array entry up to.

The default value for the third argument is the length of the array. Note that the end index itself is excluded from the filling so it won’t overflow when we call the fill method.

The fill the method returns a new array with the new values filled in. All existing values that were filled will replace any existing values that were in the original array.

For example, we can use it in the following way:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8)  
console.log(arr);

Then we get the following output from the console.log :

[8, 8, 8, 8, 8, 8]

We can also change the index of the fill method to specify the fill boundary. For example, we can write:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8, 3, 5)  
console.log(arr);

Then we get:

[1, 2, 3, 8, 8, 6]

from the console.log . If we specify an end index that’s over the length of the original array, then it’ll replace the values up to the original array up to the last position of the original array. For example, if we have:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8, 3, 10)  
console.log(arr);

Then we get:

[1, 2, 3, 8, 8, 8]

We can also use negative indexes to specify the fill boundary with the fill method. The last position of the array would be -1, the second last would be -2, and so on. So we can call fill with negative indexes like the following:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8, -4, -1)  
console.log(arr);

Then we get:

[1, 2, 8, 8, 8, 6]

since the third argument doesn’t include the last index. If we want to fill the last element as well with the new value, then we just omit the last argument like in the following code:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8, -4)  
console.log(arr);

We get [1, 2, 8, 8, 8, 8] when we run the code above. If the second argument’s value is bigger than the third argument, then no fill operation will take place and the original array is returned. For example, if we have:

let arr = [1, 2, 3, 4, 5, 6];  
arr = arr.fill(8, 4, 1)  
console.log(arr);

Then we get back [1, 2, 3, 4, 5, 6] which is the original array.

Conclusion

In JavaScript, array objects have a length property that specifies the length of an array. It’s both a getter and setter property so in addition to being able to get the length of an array, we can also use it to set the length of an array. This means that setting the length property to be less than the original length of the array. We can also use the splice and slice methods to truncate arrays.

With ES6 or later, we can fill arrays with an array with new data with the fill method. The fill method takes up to 3 arguments. The first is the value we want to add to the array. The second optional argument is the start index of which we want to start filling data.

The default value of the second argument is 0. The third argument is an optional one that lets us specify the end index to fill the array entry up to. The default value for the third argument is the length of the array.

We can also use a negative index for the second and third arguments for setting the boundaries.