Dies sind die Funktionen in ES6, die Sie kennen sollten

Discover Functional JavaScript wurde von BookAuthority als eines der besten neuen funktionalen Programmierbücher ausgezeichnet !

ES6 bringt mehr Funktionen in die JavaScript-Sprache. Mit einer neuen Syntax können Sie Code ausdrucksstärker schreiben, einige Funktionen vervollständigen die Toolbox für die funktionale Programmierung und einige Funktionen sind fraglich.

let und const

Es gibt zwei Möglichkeiten, eine Variable ( letund const) zu deklarieren, plus eine, die veraltet ist ( var).

Lassen

letdeklariert und initialisiert optional eine Variable im aktuellen Bereich. Der aktuelle Bereich kann entweder ein Modul, eine Funktion oder ein Block sein. Der Wert einer nicht initialisierten Variablen ist undefined.

Der Bereich definiert die Lebensdauer und Sichtbarkeit einer Variablen. Variablen sind außerhalb des Bereichs, in dem sie deklariert sind, nicht sichtbar.

Betrachten Sie den nächsten Code, der den letBlockumfang betont :

let x = 1; { let x = 2; } console.log(x); //1

Im Gegensatz dazu hatte die varDeklaration keinen Blockumfang:

var x = 1; { var x = 2; } console.log(x); //2

Die forSchleifenanweisung mit der letDeklaration erstellt für jede Iteration eine neue Variable lokal im Blockbereich. Die nächste Schleife erstellt fünf Abschlüsse über fünf verschiedene iVariablen.

(function run(){ for(let i=0; i<5; i++){ setTimeout(function log(){ console.log(i); //0 1 2 3 4 }, 100); } })();

Wenn Sie denselben Code mit schreiben var, werden fünf Abschlüsse über dieselbe Variable erstellt, sodass alle Abschlüsse den letzten Wert von anzeigen i.

Die log()Funktion ist ein Abschluss. Weitere Informationen zu Schließungen finden Sie unter Entdecken Sie die Leistungsfähigkeit von Schließungen in JavaScript.

const

constdeklariert eine Variable, die nicht neu zugewiesen werden kann. Sie wird nur dann zu einer Konstante, wenn der zugewiesene Wert unveränderlich ist.

Ein unveränderlicher Wert ist ein Wert, der nach seiner Erstellung nicht mehr geändert werden kann. Primitive Werte sind unveränderlich, Objekte sind veränderlich.

constfriert die Variable ein, Object.freeze()friert das Objekt ein.

Die Initialisierung der constVariablen ist obligatorisch.

Module

Vor Modulen war eine außerhalb einer Funktion deklarierte Variable eine globale Variable.

Bei Modulen ist eine außerhalb einer Funktion deklarierte Variable ausgeblendet und steht anderen Modulen nur zur Verfügung, wenn sie explizit exportiert wird.

Durch das Exportieren wird eine Funktion oder ein Objekt anderen Modulen zur Verfügung gestellt. Im nächsten Beispiel exportiere ich Funktionen aus verschiedenen Modulen:

//module "./TodoStore.js" export default function TodoStore(){} //module "./UserStore.js" export default function UserStore(){}

Durch das Importieren wird dem aktuellen Modul eine Funktion oder ein Objekt aus anderen Modulen zur Verfügung gestellt.

import TodoStore from "./TodoStore"; import UserStore from "./UserStore"; const todoStore = TodoStore(); const userStore = UserStore();

Spread / Rest

Der Operator kann der Spread-Operator oder der Rest-Parameter sein, je nachdem, wo er verwendet wird. Betrachten Sie das nächste Beispiel:

const numbers = [1, 2, 3]; const arr = ['a', 'b', 'c', ...numbers]; console.log(arr); ["a", "b", "c", 1, 2, 3]

Dies ist der Spread-Operator. Schauen Sie sich nun das nächste Beispiel an:

function process(x,y, ...arr){ console.log(arr) } process(1,2,3,4,5); //[3, 4, 5] function processArray(...arr){ console.log(arr) } processArray(1,2,3,4,5); //[1, 2, 3, 4, 5]

Dies ist der Restparameter.

Argumente

Mit dem Restparameter können wir den argumentsPseudoparameter ersetzen . Der Rest-Parameter ist ein Array, argumentsnicht.

function addNumber(total, value){ return total + value; } function sum(...args){ return args.reduce(addNumber, 0); } sum(1,2,3); //6

Klonen

Der Spread-Operator macht das Klonen von Objekten und Arrays einfacher und ausdrucksvoller.

Der Operator für Eigenschaften der Objektverteilung ist als Teil von ES2018 verfügbar.

const book = { title: "JavaScript: The Good Parts" }; //clone with Object.assign() const clone = Object.assign({}, book); //clone with spread operator const clone = { ...book }; const arr = [1, 2 ,3]; //clone with slice const cloneArr = arr.slice(); //clone with spread operator const cloneArr = [ ...arr ];

Verkettung

Im nächsten Beispiel wird der Spread-Operator zum Verketten von Arrays verwendet:

const part1 = [1, 2, 3]; const part2 = [4, 5, 6]; const arr = part1.concat(part2); const arr = [...part1, ...part2];

Objekte zusammenführen

Mit dem Spread-Operator Object.assign()können Sie beispielsweise Eigenschaften von einem oder mehreren Objekten in ein leeres Objekt kopieren und deren Eigenschaften kombinieren.

const authorGateway = { getAuthors : function() {}, editAuthor: function() {} }; const bookGateway = { getBooks : function() {}, editBook: function() {} }; //copy with Object.assign() const gateway = Object.assign({}, authorGateway, bookGateway); //copy with spread operator const gateway = { ...authorGateway, ...bookGateway };

Eigentum Short-Hands

Betrachten Sie den nächsten Code:

function BookGateway(){ function getBooks() {} function editBook() {} return { getBooks: getBooks, editBook: editBook } }

With property short-hands, when the property name and the name of the variable used as the value are the same, we can just write the key once.

function BookGateway(){ function getBooks() {} function editBook() {} return { getBooks, editBook } }

Here is another example:

const todoStore = TodoStore(); const userStore = UserStore(); const stores = { todoStore, userStore };

Destructuring assignment

Consider the next code:

function TodoStore(args){ const helper = args.helper; const dataAccess = args.dataAccess; const userStore = args.userStore; }

With destructuring assignment syntax, it can be written like this:

function TodoStore(args){ const { helper, dataAccess, userStore } = args; }

or even better, with the destructuring syntax in the parameter list:

function TodoStore({ helper, dataAccess, userStore }){}

Below is the function call:

TodoStore({ helper: {}, dataAccess: {}, userStore: {} });

Default parameters

Functions can have default parameters. Look at the next example:

function log(message, mode = "Info"){ console.log(mode + ": " + message); } log("An info"); //Info: An info log("An error", "Error"); //Error: An error

Template string literals

Template strings are defined with the ` character. With template strings, the previous logging message can be written like this:

function log(message, mode= "Info"){ console.log(`${mode}: ${message}`); }

Template strings can be defined on multiple lines. However, a better option is to keep the long text messages as resources, in a database for example.

See below a function that generates an HTML that spans multiple lines:

function createTodoItemHtml(todo){ return `
  • ${todo.title} ${todo.userName}
  • `; }

    Proper tail-calls

    A recursive function is tail recursive when the recursive call is the last thing the function does.

    The tail recursive functions perform better than non tail recursive functions. The optimized tail recursive call does not create a new stack frame for each function call, but rather uses a single stack frame.

    ES6 brings the tail-call optimization in strict mode.

    The following function should benefit from the tail-call optimization.

    function print(from, to) { const n = from; if (n > to) return; console.log(n); //the last statement is the recursive call print(n + 1, to); } print(1, 10);

    Note: the tail-call optimization is not yet supported by major browsers.

    Promises

    A promise is a reference to an asynchronous call. It may resolve or fail somewhere in the future.

    Promises are easier to combine. As you see in the next example, it is easy to call a function when all promises are resolved, or when the first promise is resolved.

    function getTodos() { return fetch("/todos"); } function getUsers() { return fetch("/users"); } function getAlbums(){ return fetch("/albums"); } const getPromises = [ getTodos(), getUsers(), getAlbums() ]; Promise.all(getPromises).then(doSomethingWhenAll); Promise.race(getPromises).then(doSomethingWhenOne); function doSomethingWhenAll(){} function doSomethingWhenOne(){}

    The fetch() function, part of the Fetch API, returns a promise.

    Promise.all() returns a promise that resolves when all input promises have resolved. Promise.race() returns a promise that resolves or rejects when one of the input promises resolves or rejects.

    A promise can be in one of the three states: pending, resolved or rejected. The promise will in pending until is either resolved or rejected.

    Promises support a chaining system that allows you to pass data through a set of functions. In the next example, the result of getTodos() is passed as input to toJson(), then its result is passed as input to getTopPriority(), and then its result is passed as input to renderTodos() function. When an error is thrown or a promise is rejected the handleError is called.

    getTodos() .then(toJson) .then(getTopPriority) .then(renderTodos) .catch(handleError); function toJson(response){} function getTopPriority(todos){} function renderTodos(todos){} function handleError(error){}

    In the previous example, .then() handles the success scenario and .catch() handles the error scenario. If there is an error at any step, the chain control jumps to the closest rejection handler down the chain.

    Promise.resolve() returns a resolved promise. Promise.reject() returns a rejected promise.

    Class

    Class is sugar syntax for creating objects with a custom prototype. It has a better syntax than the previous one, the function constructor. Check out the next exemple:

    class Service { doSomething(){ console.log("doSomething"); } } let service = new Service(); console.log(service.__proto__ === Service.prototype);

    All methods defined in the Service class will be added to theService.prototype object. Instances of the Service class will have the same prototype (Service.prototype) object. All instances will delegate method calls to the Service.prototype object. Methods are defined once onService.prototype and then inherited by all instances.

    Inheritance

    “Classes can inherit from other classes”. Below is an example of inheritancewhere the SpecialService class “inherits” from the Service class:

    class Service { doSomething(){ console.log("doSomething"); } } class SpecialService extends Service { doSomethingElse(){ console.log("doSomethingElse"); } } let specialService = new SpecialService(); specialService.doSomething(); specialService.doSomethingElse();

    All methods defined in the SpecialService class will be added to the SpecialService.prototype object. All instances will delegate method calls to the SpecialService.prototype object. If the method is not found in SpecialService.prototype, it will be searched in the Service.prototypeobject. If it is still not found, it will be searched in Object.prototype.

    Class can become a bad feature

    Even if they seem encapsulated, all members of a class are public. You still need to manage problems with this losing context. The public API is mutable.

    class can become a bad feature if you neglect the functional side of JavaScript. class may give the impression of a class-based language when JavaScript is both a functional programming language and a prototype-based language.

    Encapsulated objects can be created with factory functions. Consider the next example:

    function Service() { function doSomething(){ console.log("doSomething"); } return Object.freeze({ doSomething }); }

    This time all members are private by default. The public API is immutable. There is no need to manage issues with this losing context.

    class may be used as an exception if required by the components framework. This was the case with React, but is not the case anymore with React Hooks.

    For more on why to favor factory functions, take a look at Class vs Factory function: exploring the way forward.

    Arrow functions

    Arrow functions can create anonymous functions on the fly. They can be used to create small callbacks, with a shorter syntax.

    Let’s take a collection of to-dos. A to-do has an id , a title , and a completed boolean property. Now, consider the next code that selects only the title from the collection:

    const titles = todos.map(todo => todo.title);

    or the next example selecting only the todos that are not completed:

    const filteredTodos = todos.filter(todo => !todo.completed);

    this

    Arrow functions don’t have their own this and arguments. As a result, you may see the arrow function used to fix problems with this losing context. I think that the best way to avoid this problem is to not use this at all.

    Arrow functions can become a bad feature

    Arrow functions can become a bad feature when used to the detriment of named functions. This will create readability and maintainability problems. Look at the next code written only with anonymous arrow functions:

    const newTodos = todos.filter(todo => !todo.completed && todo.type === "RE") .map(todo => ({ title : todo.title, userName : users[todo.userId].name })) .sort((todo1, todo2) => todo1.userName.localeCompare(todo2.userName));

    Now, check out the same logic refactored to pure functions with intention revealing names and decide which of them is easier to understand:

    const newTodos = todos.filter(isTopPriority) .map(partial(toTodoView, users)) .sort(ascByUserName); function isTopPriority(todo){ return !todo.completed && todo.type === "RE"; } function toTodoView(users, todo){ return { title : todo.title, userName : users[todo.userId].name } } function ascByUserName(todo1, todo2){ return todo1.userName.localeCompare(todo2.userName); }

    Even more, anonymous arrow functions will appear as (anonymous) in the Call Stack.

    For more on why to favor named functions, take a look at How to make your code better with intention-revealing function names.

    Less code doesn’t necessary mean more readable. Look at the next exampleand see which version is easier for you to understand:

    //with arrow function const prop = key => obj => obj[key]; //with function keyword function prop(key){ return function(obj){ return obj[key]; } }

    Pay attention when returning an object. In the next example, the getSampleTodo() returns undefined.

    const getSampleTodo = () => { title : "A sample todo" }; getSampleTodo(); //undefined

    Generators

    I think the ES6 generator is an unnecessary feature that makes code more complicated.

    The ES6 generator creates an object that has the next() method. The next() method creates an object that has the value property. ES6 generators promote the use of loops. Take a look at code below:

    function* sequence(){ let count = 0; while(true) { count += 1; yield count; } } const generator = sequence(); generator.next().value;//1 generator.next().value;//2 generator.next().value;//3

    The same generator can be simply implemented with a closure.

    function sequence(){ let count = 0; return function(){ count += 1; return count; } } const generator = sequence(); generator();//1 generator();//2 generator();//3

    For more examples with functional generators take a look at Let’s experiment with functional generators and the pipeline operator in JavaScript.

    Conclusion

    let and const declare and initialize variables.

    Modules encapsulate functionality and expose only a small part.

    The spread operator, rest parameter, and property shorthand make things easier to express.

    Promises and tail recursion complete the functional programming toolbox.

    Discover Functional JavaScript was named one of thebest new Functional Programming books by BookAuthority!

    For more on applying functional programming techniques in React take a look atFunctional React.

    Learn functional React, in a project-based way, with Functional Architecture with React and Redux.

    Follow on Twitter