惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
雷峰网
雷峰网
The Register - Security
The Register - Security
The Cloudflare Blog
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
I
InfoQ
博客园 - 三生石上(FineUI控件)
H
Help Net Security
博客园 - 司徒正美
Vercel News
Vercel News
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
B
Blog
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
L
LangChain Blog
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recorded Future
Recorded Future
小众软件
小众软件
Martin Fowler
Martin Fowler
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Apple Machine Learning Research
Apple Machine Learning Research
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
V
Visual Studio Blog
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
V
V2EX
Blog — PlanetScale
Blog — PlanetScale

Dmitri Pavlutin Blog

Pure Functions in JavaScript: A Beginner's Guide Record Type in TypeScript: A Quick Intro How to Write Comments in React: The Good, the Bad and the Ugly 4 Ways to Create an Enum in JavaScript React forwardRef(): How to Pass Refs to Child Components TypeScript Function Types: A Beginner's Guide How to Use v-model to Access Input Values in Vue Mastering Vue refs: From Zero to Hero Environment Variables in JavaScript: process.env 5 Must-Know Differences Between ref() and reactive() in Vue How to Destructure Props in Vue (Composition API) Triangulation in Test-Driven Development How to Use nextTick() in Vue Programming to Interface Vs to Implementation A Smarter JavaScript Mapper: array.flatMap() Array Grouping in JavaScript: Object.groupBy() How to Access ES Module Metadata using import.meta JSON Modules in JavaScript How to Trim Strings in JavaScript TypeScript Function Overloading How to Debounce and Throttle Callbacks in Vue How to Show/Hide Elements in Vue Sparse vs Dense Arrays in JavaScript How to Fill an Array with Initial Values in JavaScript Covariance and Contravariance in TypeScript What are Higher-Order Functions in JavaScript? How to Use TypeScript with React Components Index Signatures in TypeScript How to Use React useReducer() Hook unknown vs any in TypeScript A Guide to React Context and useContext() Hook How to Use Promise.any() 2 Ways to Remove a Property from an Object in JavaScript 'return await promise' vs 'return promise' in JavaScript How to Use Promise.allSettled() How to Use fetch() with JSON JavaScript Promises: then(f,f) vs then(f).catch(f) What is a Promise in JavaScript? How to Use Promise.all() A Simple Guide to Component Props in React Don't Stop Me Now: How to Use React useTransition() hook A Simple Explanation of JavaScript Variables: const, let, var ES Modules Dynamic Import How to Memoize with React.useMemo() How to Cleanup Async Effects in React Why Math.max() Without Arguments Returns -Infinity How to Debounce and Throttle Callbacks in React Don't Confuse Function Expressions and Function Declarations in JavaScript How to Use ES Modules in Node.js Solving a Mystery Behavior of parseInt() in JavaScript How to Use Array Reduce Method in JavaScript 3 Ways to Merge Arrays in JavaScript A Guide to Jotai: the Minimalist React State Management Library The Difference Between Values and References in JavaScript How to Implement a Queue in JavaScript A Helpful Algorithm to Determine "this" value in JavaScript React useRef() Hook Explained in 3 Steps 7 Interview Questions on "this" keyword in JavaScript. Can You Answer Them? How to Greatly Enhance fetch() with the Decorator Pattern 7 Interview Questions on JavaScript Closures. Can You Answer Them? What's a Method in JavaScript? array.sort() Does Not Simply Sort Numbers in JavaScript How to Solve the Infinite Loop of React.useEffect() The New Array Method You'll Enjoy: array.at(index) What's the Difference between DOM Node and Element? Why Promises Are Faster Than setTimeout()? Everything About Callback Functions in JavaScript How React Updates State 5 Mistakes to Avoid When Using React Hooks 5 Best Practices to Write Quality JavaScript Variables Type checking in JavaScript: typeof and instanceof operators 3 Ways to Check if a Variable is Defined in JavaScript React Forms Tutorial: Access Input Values, Validate, Submit Forms Prototypal Inheritance in JavaScript How to Timeout a fetch() Request How to Learn JavaScript If You're a Beginner A Simple Explanation of React.useEffect() A Simple Explanation of JavaScript Iterators How to Use React Controlled Inputs Everything about null in JavaScript How to Use Fetch with async/await Getting Started with Arrow Functions in JavaScript An Interesting Explanation of async/await in JavaScript Front-end Architecture: Stable and Volatile Dependencies Is it Safe to Compare JavaScript Strings? How to Access Object's Keys, Values, and Entries in JavaScript What Actually is a String in JavaScript? 3 Ways to Shallow Clone Objects in JavaScript (w/ bonuses) Checking if an Array Contains a Value in JavaScript JavaScript Event Delegation: A Beginner's Guide How to Parse URL in JavaScript: hostname, pathname, query, hash 3 Ways to Detect an Array in JavaScript How to Get the Screen, Window, and Web Page Sizes in JavaScript 3 Ways to Check If an Object Has a Property/Key in JavaScript How to Compare Objects in JavaScript Object.is() vs Strict Equality Operator in JavaScript Own and Inherited Properties in JavaScript 5 Differences Between Arrow and Regular Functions How to Use Object Destructuring in JavaScript Your Guide to React.useCallback()
How Three Dots Changed JavaScript
Dmitri Pavlutin · 2016-06-14 · via Dmitri Pavlutin Blog

I don't like arguments keyword when I access the arguments of a function call. Its hardcoded name makes difficult to access arguments of an outer function in an inner one (which defines its own arguments).

Even worse arguments is an array-like object. You cannot use array methods like .map() or .forEach() directly on it.

To access arguments from the enclosing function, you have to use workarounds by storing it into a separated variable. And to walk through this array-like object, you have to use duck typing and make indirect invocations. See the following example:


function outerFunction() {

// store arguments into a separated variable

const argsOuter = arguments;

function innerFunction() {

// args is an array-like object

const even = Array.prototype.map.call(argsOuter, function(item) {

// do something with argsOuter

});

}

}


Another situation is the function invocation that accepts a dynamic number of arguments. Filling the arguments from an array is unpleasant.

For instance .push(item1, ..., itemN) inserts elements into an array one by one: you have to enumerate each element as an argument. This is not always convenient: often an entire array of elements needs to be pushed into an existing array, without creating a new instance.

In ES5 it's solved with .apply(): an unfriendly and verbose approach. Let's take a look:


const fruits = ['banana'];

const moreFruits = ['apple', 'orange'];

Array.prototype.push.apply(fruits, moreFruits);

console.log(fruits); // => ['banana', 'apple', 'orange']


Fortunately, the JavaScript world is changing. The three dots operator ... fixes many of these situations. The operator is introduced by ECMAScript 6 and in my opinion, is a noticeable improvement.

This article walks through ... operator use cases and shows how to solve similar problems.

1. Three dots

The JavaScript rest operator collects the arguments list passed to a function on invocation or collects the pieces after destructuring an array. A case when the operator gathers the rest remained after the operation.


function countArguments(...args) {

return args.length;

}

// get the number of arguments

countArguments('welcome', 'to', 'Earth'); // => 3

// destructure an array

let otherSeasons, autumn;

[autumn, ...otherSeasons] = ['autumn', 'winter'];

otherSeasons // => ['winter']


The JavaScript spread operator constructs arrays or sets the function arguments on invocation from an array. A case when the operator spreads the array (or iterable object) elements.


let cold = ['autumn', 'winter'];

let warm = ['spring', 'summer'];

// construct an array

[...cold, ...warm] // => ['autumn', 'winter', 'spring', 'summer']

// function arguments from an array

cold.push(...warm);

cold // => ['autumn', 'winter', 'spring', 'summer']


2. Improved parameters access

2.1 Rest parameter

As presented in the introduction, dealing with arguments object in a function body becomes troublesome in complex scenarios.

For example a JavaScript inner function filterNumbers() wants to access arguments from its outer function sumOnlyNumbers():


function sumOnlyNumbers() {

const args = arguments;

const numbers = filterNumbers();

return numbers.reduce((sum, element) => sum + element);

function filterNumbers() {

return Array.prototype.filter.call(args,

element => typeof element === 'number'

);

}

}

sumOnlyNumbers(1, 'Hello', 5, false); // => 6


To access arguments of sumOnlyNumbers() inside filterNumbers(), you have to create a temporary variable args. It happens because filterNumbers() defines its own arguments object that overwrites the external one.

The approach works, but it's too verbose. const args = arguments can be omitted and Array.prototype.filter.call(args) can be transformed to args.filter() using a rest parameter. Let's optimize this part.

The rest operator solves this elegantly. It allows to define a rest parameter ...args in a function declaration:


function sumOnlyNumbers(...args) {

const numbers = filterNumbers();

return numbers.reduce((sum, element) => sum + element);

function filterNumbers() {

return args.filter(element => typeof element === 'number');

}

}

sumOnlyNumbers(1, 'Hello', 5, false); // => 6


The function declaration function sumOnlyNumbers(...args) indicates that args receives the invocation arguments in an array. Because the names conflict is solved, args can be used inside filterNumbers().

Also forget about array-like objects: args is an array - which is a nice bonus. As result, filterNumbers() can get rid of Array.prototype.filter.call() and make a filter method call directly args.filter().

Notice that the rest parameter must be the last one in the function parameters list.

2.2 Selective rest parameter

When not all values should be included in the rest parameter, you can keep those as comma separated parameters at the beginning. Explicitly defined parameters are not included in the rest parameter.

Let's see an example:


function filter(type, ...items) {

return items.filter(item => typeof item === type);

}

filter('boolean', true, 0, false); // => [true, false]

filter('number', false, 4, 'Welcome', 7); // => [4, 7]


arguments object doesn't have this selective property and always includes all the values.

2.3 Arrow function case

An arrow function does not define arguments object in its body but accesses the one from the enclosing scope.

If you want to get all the arguments in an arrow function, use a rest parameter.

Let's try this in an example:


(function() {

let outerArguments = arguments;

const concat = (...items) => {

console.log(arguments === outerArguments); // => true

return items.reduce((result, item) => result + item, '');

};

concat(1, 5, 'nine'); // => '15nine'

})();


items rest parameter contains all function call arguments in an array. Also arguments object is taken from the enclosing scope and equals to outerArguments variable, so it has no valuable meaning.

3. Improved function call

In the article introduction, the second problem asks a better way to fill the invocation arguments from an array.
ES5 provides .apply() method on the function object to solve this. Unfortunately, this technique has 3 problems:

  • It's necessary to indicate manually the context of the function invocation
  • Is not possible to use in a constructor invocation
  • A shorter solution is more preferable

Let's see an example of .apply() usage:


const countries = ['Moldova', 'Ukraine'];

const otherCountries = ['USA', 'Japan'];

countries.push.apply(countries, otherCountries);

console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']


As mentioned, it seems irrelevant to indicate in .apply() the second time the context countries. The property accessor countries.push is enough to determine the method invocation on an object.
And the entire invocation looks verbose.

The JavaScript spread operator fills the function invocation arguments with values from an array (or more strictly from an iterable object, see 5.).
Let's improve the above sample with a spread operator:


const countries = ['Moldova', 'Ukraine'];

const otherCountries = ['USA', 'Japan'];

countries.push(...otherCountries);

console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']


As seen, the spread operator is a cleaner and straightforward solution. The only additional characters are 3 dots (...).

The JavaScript spread operator configures the constructor invocation arguments from an array, which is not possible directly when using .apply(). Let's see an example:


class King {

constructor(name, country) {

this.name = name;

this.country = country;

}

getDescription() {

return `${this.name} leads ${this.country}`;

}

}

const details = ['Alexander the Great', 'Greece'];

const Alexander = new King(...details);

Alexander.getDescription(); // => 'Alexander the Great leads Greece'


Moreover you can combine multiple spread operators and regular arguments in the same invocation. The following example is removing from an array existing elements, then adds other array and an element:


const numbers = [1, 2];

const evenNumbers = [4, 8];

const zero = 0;

numbers.splice(0, 2, ...evenNumbers, zero);

console.log(numbers); // => [4, 8, 0]


4. Improved array manipulation

4.1 Array construction

The array literal [item1, item2, .., itemN] does not provide functionality other than enumerating the initial array elements.

The spread operator improves array literals by allowing to insert on the fly other arrays (or any other iterables) into the initialized instance. This improvement makes easier to accomplish common tasks described below.

Create an array with initial elements from another array:


const initial = [0, 1];

const numbers1 = [...initial, 5, 7];

console.log(numbers1); // => [0, 1, 5, 7]

const numbers2 = [4, 8, ...initial];

console.log(numbers2); // => [4, 8, 0, 1]


number1 and number2 arrays are created using an array literal and in the meantime initialized with items from initial.

Concatenate 2 or more arrays:


const odds = [1, 5, 7];

const evens = [4, 6, 8];

const all = [...odds, ...evens];

console.log(all); // => [1, 5, 7, 4, 6, 8]


all array is created from concatentation of odds and evens arrays.

Clone an array instance:


const words = ['Hi', 'Hello', 'Good day'];

const otherWords = [...words];

console.log(otherWords); // => ['Hi', 'Hello', 'Good day']

console.log(otherWords === words); // => false


otherWords is a clone version of words array. Notice that cloning happens only on the array itself, but not on the contained elements (i.e. it's not a deep clone).

4.2 Array destructure

Destructuring assignments, available in ECMAScript 6, are powerful expressions to extract data from arrays and objects.

As a part of the destructuring, the rest operator extracts parts of an array. The extraction result is always an array.

In terms of syntax, the rest operator should be the last one in a destructuring assignment: [extractedItem1, ...restArray] = destructuredArray.

Let's see some applications:


const seasons = ['winter', 'spring', 'summer', 'autumn'];

const head, restArray;

[head, ...restArray] = seasons;

console.log(head); // => 'winter'

console.log(restArray); // => ['spring', 'summer', 'autumn']


[head, ...restArray] extracts the first item 'winter' into head variable and the rest of elements into restArray.

5. Spread operator and iteration protocols

The spread operator uses iteration protocols to navigate over elements of a collection. This makes the spread operator even more valuable because any object can define how the operator will extract data.

An object is Iterable when it conforms to iterable protocol.

The iterable protocol requires the object to contain a special property. The property name must be Symbol.iterator and value as a function that returns an iterator object.


interface Iterable {

[Symbol.iterator]() {

//...

return Iterator;

}

}


The Iterator object must conform to iterator protocol.

It needs to provide a property next, which value is a function that returns an object with properties done (a boolean to indicate the end of iteration) and value (the iteration result).


interface Iterator {

next() {

//...

return {

value: <value>,

done: <boolean>

};

};

}


It seems tough to understand the iteration protocols from verbal description, but the code behind those is quite simple.

The object or primitive must be iterable so that the spread operator to extract data from it.

Many native primitive types and objects are iterable: strings, arrays, typed arrays, sets and maps. They work by default with the spread operator.

For instance, let's see how a string conforms to iteration protocols:


const str = 'hi';

const iterator = str[Symbol.iterator]();

iterator.toString(); // => '[object String Iterator]'

iterator.next(); // => { value: 'h', done: false }

iterator.next(); // => { value: 'i', done: false }

iterator.next(); // => { value: undefined, done: true }

[...str]; // => ['h', 'i']


I like the spread operator for its ability to use the object's custom iteration implementation. You can control how the spread operator consumes your object - an effective coding technique.

The following sample makes an array-like object conformed to iteration protocols, then transforms it to an array using spread operator:


function iterator() {

let index = 0;

return {

next: () => ({ // Conform to Iterator protocol

done : index >= this.length,

value: this[index++]

})

};

}

const arrayLike = {

0: 'Cat',

1: 'Bird',

length: 2

};

// Conform to Iterable Protocol

arrayLike[Symbol.iterator] = iterator;

const array = [...arrayLike];

console.log(array); // => ['Cat', 'Bird']


arrayLike[Symbol.iterator] creates a property on the object that contains an iteration function iterator(), making the object conformed to iterable protocol.

iterator() returns an object (conformed to iteration protocol) with next property as a function that returns the control object {done: <boolean>, value: <item>}.

Since arrayLike is now iterable, spread operator is used to extract its elements into an array: [...arrayLike].

6. Finale

Three dots operator adds a bunch of great features to JavaScript.

The rest parameter makes it a lot easier to collect the arguments. It's a reasonable replacement for the hardcoded array-like object arguments. If the situation permits to choose between the rest parameter and arguments, use the first one.

.apply() method is not convenient for its verbose syntax. The spread operator is a good alternative when invocation arguments should be taken from an array.

The spread operator improves array literals. You can initialize, concatenate and clone arrays a lot simpler.

You can extract parts of an array using destructuring assignments. In combination with iteration protocols, the spread operator can be used in a more configurable manner.

I hope from now on the spread operator will appear more often in your code!