Αυτά είναι τα χαρακτηριστικά του ES6 που πρέπει να γνωρίζετε

Το Discover Functional JavaScript ονομάστηκε ένα από τα καλύτερα νέα βιβλία λειτουργικού προγραμματισμού από το BookAuthority !

Το ES6 φέρνει περισσότερες δυνατότητες στη γλώσσα JavaScript. Κάποια νέα σύνταξη σάς επιτρέπει να γράφετε κώδικα με πιο εκφραστικό τρόπο, ορισμένες λειτουργίες συμπληρώνουν τη λειτουργική εργαλειοθήκη προγραμματισμού και ορισμένες λειτουργίες είναι αμφισβητήσιμες.

ας και συνεχ

Υπάρχουν δύο τρόποι για να δηλώσετε μια μεταβλητή ( letκαι const) συν μια που έχει καταστεί άνευ αντικειμένου ( var).

αφήνω

letδηλώνει και προαιρετικά προετοιμάζει μια μεταβλητή στο τρέχον πεδίο. Το τρέχον πεδίο μπορεί να είναι είτε μια ενότητα, μια συνάρτηση είτε ένα μπλοκ. Η τιμή μιας μεταβλητής που δεν έχει αρχικοποιηθεί είναι undefined.

Το πεδίο εφαρμογής καθορίζει τη διάρκεια ζωής και την ορατότητα μιας μεταβλητής. Οι μεταβλητές δεν είναι ορατές εκτός του πεδίου στο οποίο δηλώνονται.

Εξετάστε τον επόμενο κώδικα που δίνει έμφαση στο letπεδίο αποκλεισμού:

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

Αντιθέτως, η varδήλωση δεν είχε πεδίο εφαρμογής:

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

Η forδήλωση βρόχου, με τη letδήλωση, δημιουργεί μια νέα μεταβλητή τοπική στο πεδίο αποκλεισμού, για κάθε επανάληψη. Ο επόμενος βρόχος δημιουργεί πέντε κλεισίματα σε πέντε διαφορετικές iμεταβλητές.

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

Το varνα γράψετε τον ίδιο κωδικό με θα δημιουργήσει πέντε κλεισίματα, πάνω από την ίδια μεταβλητή, έτσι ώστε όλα τα κλεισίματα θα εμφανίσουν την τελευταία τιμή του i.

Η log()συνάρτηση είναι κλείσιμο. Για περισσότερες πληροφορίες σχετικά με το κλείσιμο, ρίξτε μια ματιά στην ενότητα Ανακαλύψτε τη δύναμη του κλεισίματος στο JavaScript

υπ

constδηλώνει μια μεταβλητή που δεν μπορεί να ανατεθεί εκ νέου. Γίνεται σταθερά μόνο όταν η αντιστοιχισμένη τιμή είναι αμετάβλητη.

Μια αμετάβλητη τιμή είναι μια τιμή που, μόλις δημιουργηθεί, δεν μπορεί να αλλάξει. Οι αρχικές τιμές είναι αμετάβλητες, τα αντικείμενα είναι μεταβλητά.

constπαγώνει τη μεταβλητή, Object.freeze()παγώνει το αντικείμενο.

Η αρχικοποίηση της constμεταβλητής είναι υποχρεωτική.

Ενότητες

Πριν από τις ενότητες, μια μεταβλητή που δηλώθηκε έξω από οποιαδήποτε συνάρτηση ήταν μια καθολική μεταβλητή.

Με τις ενότητες, μια μεταβλητή που δηλώνεται εκτός οποιασδήποτε συνάρτησης είναι κρυφή και δεν είναι διαθέσιμη σε άλλες ενότητες, εκτός εάν εξάγεται ρητά.

Η εξαγωγή καθιστά μια λειτουργία ή αντικείμενο διαθέσιμο σε άλλες ενότητες. Στο επόμενο παράδειγμα, εξάγω συναρτήσεις από διαφορετικές ενότητες:

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

Η εισαγωγή καθιστά μια λειτουργία ή αντικείμενο, από άλλες ενότητες, διαθέσιμη στην τρέχουσα ενότητα.

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

Spread / Rest

Ο χειριστής μπορεί να είναι ο τελεστής διασποράς ή η παράμετρος υπολοίπου, ανάλογα με το πού χρησιμοποιείται. Εξετάστε το επόμενο παράδειγμα:

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

Αυτός είναι ο τελεστής spread. Τώρα δείτε το επόμενο παράδειγμα:

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]

Αυτή είναι η υπόλοιπη παράμετρος.

επιχειρήματα

Με την υπόλοιπη παράμετρο μπορούμε να αντικαταστήσουμε την argumentsψευδο-παράμετρο. Η υπόλοιπη παράμετρος είναι ένας πίνακας, argumentsδεν είναι.

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

Κλωνοποίηση

Ο τελεστής διασποράς κάνει την κλωνοποίηση αντικειμένων και συστοιχιών απλούστερη και πιο εκφραστική.

Ο τελεστής ιδιοτήτων διάδοσης αντικειμένων θα είναι διαθέσιμος ως μέρος του 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 ];

Αληλουχία

Στο επόμενο παράδειγμα, ο τελεστής διασποράς χρησιμοποιείται για τη συνένωση συστοιχιών:

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

Συγχώνευση αντικειμένων

Ο τελεστής διασποράς, όπως Object.assign(), μπορεί να χρησιμοποιηθεί για να αντιγράψει ιδιότητες από ένα ή περισσότερα αντικείμενα σε ένα κενό αντικείμενο και να συνδυάσει τις ιδιότητές τους.

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

Ακίνητα κοντά χέρια

Εξετάστε τον επόμενο κωδικό:

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 ονομάστηκε ένα από τακαλύτερα νέα βιβλία λειτουργικού προγραμματισμού από το BookAuthority !

    Για περισσότερα σχετικά με την εφαρμογή τεχνικών λειτουργικού προγραμματισμού στο React ρίξτε μια ματιά στο Functional React .

    Μάθετε λειτουργικό React , με βάση ένα έργο, με τη λειτουργική αρχιτεκτονική με το React και το Redux .

    Ακολουθήστε στο Twitter