Estas son las características de ES6 que debes conocer

Discover Functional JavaScript fue nombrado uno de los mejores libros nuevos de programación funcional por BookAuthority .

ES6 trae más funciones al lenguaje JavaScript. Alguna sintaxis nueva le permite escribir código de una manera más expresiva, algunas características completan la caja de herramientas de programación funcional y algunas características son cuestionables.

dejar y const

Hay dos formas de declarar una variable ( lety const) más una que se ha vuelto obsoleta ( var).

dejar

letdeclara y, opcionalmente, inicializa una variable en el ámbito actual. El alcance actual puede ser un módulo, una función o un bloque. El valor de una variable que no está inicializada es undefined.

El alcance define la vida útil y la visibilidad de una variable. Las variables no son visibles fuera del ámbito en el que se declaran.

Considere el siguiente código que enfatiza el letalcance del bloque:

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

Por el contrario, la vardeclaración no tenía alcance de bloque:

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

La fordeclaración de bucle, con la letdeclaración, crea una nueva variable local al alcance del bloque, para cada iteración. El siguiente ciclo crea cinco cierres sobre cinco ivariables diferentes .

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

Escribir el mismo código con varcreará cinco cierres, sobre la misma variable, por lo que todos los cierres mostrarán el último valor de i.

La log()función es un cierre. Para obtener más información sobre los cierres, eche un vistazo a Descubra el poder de los cierres en JavaScript.

constante

constdeclara una variable que no se puede reasignar. Se convierte en una constante solo cuando el valor asignado es inmutable.

Un valor inmutable es un valor que, una vez creado, no se puede cambiar. Los valores primitivos son inmutables, los objetos son mutables.

constcongela la variable, Object.freeze()congela el objeto.

La inicialización de la constvariable es obligatoria.

Módulos

Antes de los módulos, una variable declarada fuera de cualquier función era una variable global.

Con los módulos, una variable declarada fuera de cualquier función está oculta y no está disponible para otros módulos a menos que se exporte explícitamente.

La exportación hace que una función u objeto esté disponible para otros módulos. En el siguiente ejemplo, exporto funciones de diferentes módulos:

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

La importación hace que una función u objeto, de otros módulos, esté disponible para el módulo actual.

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

Esparcir / Descansar

El operador puede ser el operador de propagación o el parámetro resto, dependiendo de dónde se utilice. Considere el siguiente ejemplo:

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

Este es el operador de propagación. Ahora mira el siguiente ejemplo:

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]

Este es el parámetro de descanso.

argumentos

Con el parámetro rest podemos reemplazar el argumentspseudoparámetro. El parámetro rest es una matriz, argumentsno lo es.

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

Clonación

El operador de extensión hace que la clonación de objetos y matrices sea más simple y expresiva.

El operador de propiedades de distribución de objetos estará disponible como parte de ES2018.

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 ];

Concatenación

En el siguiente ejemplo, el operador de propagación se utiliza para concatenar matrices:

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

Fusionar objetos

El operador de propagación, como Object.assign(), se puede utilizar para copiar propiedades de uno o más objetos a un objeto vacío y combinar sus propiedades.

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 };

Propiedad de manos cortas

Considere el siguiente código:

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