Inilah ciri-ciri dalam ES6 yang harus anda ketahui

Discover Functional JavaScript dinobatkan sebagai salah satu buku Pengaturcaraan Fungsional baru terbaik oleh BookAuthority !

ES6 membawa lebih banyak ciri ke bahasa JavaScript. Beberapa sintaks baru membolehkan anda menulis kod dengan cara yang lebih ekspresif, beberapa ciri melengkapkan kotak alat pengaturcaraan berfungsi, dan beberapa ciri dipersoalkan.

biarkan dan konst

Terdapat dua cara untuk menyatakan pemboleh ubah ( letdan const) ditambah satu yang telah menjadi usang ( var).

biarkan

letmenyatakan dan secara opsional memulakan pemboleh ubah dalam skop semasa. Skop semasa boleh berupa modul, fungsi atau blok. Nilai pemboleh ubah yang tidak dimulakan adalah undefined.

Skop menentukan jangka hayat dan keterlihatan pemboleh ubah. Pemboleh ubah tidak dapat dilihat di luar skop di mana ia dinyatakan.

Pertimbangkan kod seterusnya yang menekankan letruang lingkup blok:

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

Sebaliknya, vardeklarasi tersebut tidak mempunyai ruang lingkup blok:

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

The forkenyataan gelung, dengan letperisytiharan, mencipta pembolehubah baru tempatan untuk skop blok, bagi setiap lelaran. Gelung seterusnya membuat lima penutupan di atas lima ipemboleh ubah yang berbeza .

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

Menulis kod yang sama dengan varakan membuat lima penutupan, di atas pemboleh ubah yang sama, sehingga semua penutupan akan memaparkan nilai terakhir dari i.

Yang log()berfungsi penutupan. Untuk lebih lanjut mengenai penutupan, lihat Temukan kekuatan penutupan dalam JavaScript.

penyambung

constmenyatakan pemboleh ubah yang tidak dapat ditugaskan semula. Ia menjadi pemalar hanya apabila nilai yang ditetapkan tidak berubah.

Nilai yang tidak berubah adalah nilai yang, setelah dibuat, tidak dapat diubah. Nilai primitif tidak berubah, objek boleh berubah.

constmembekukan pemboleh ubah, Object.freeze()membekukan objek.

Permulaan constpemboleh ubah adalah wajib.

Modul

Sebelum modul, pemboleh ubah yang dinyatakan di luar fungsi apa pun adalah pemboleh ubah global.

Dengan modul, pemboleh ubah yang dinyatakan di luar fungsi apa pun tersembunyi dan tidak tersedia untuk modul lain melainkan dieksport secara eksplisit.

Mengeksport menjadikan fungsi atau objek tersedia untuk modul lain. Dalam contoh seterusnya, saya mengeksport fungsi dari modul yang berbeza:

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

Mengimport menjadikan fungsi atau objek, dari modul lain, tersedia untuk modul semasa.

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

Sebarkan / Rehat

The pengendali boleh menjadi pengendali penyebaran atau parameter yang lain, bergantung kepada di mana ia digunakan. Pertimbangkan contoh seterusnya:

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

Ini adalah pengendali penyebaran. Sekarang lihat contoh seterusnya:

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]

Ini adalah parameter selebihnya.

hujah

Dengan parameter selebihnya kita dapat menggantikan argumentsparameter pseudo. Parameter selebihnya adalah tatasusunan, argumentsbukan.

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

Pengklonan

Operator penyebaran menjadikan pengklonan objek dan susunan lebih mudah dan lebih ekspresif.

Pengendali sifat penyebaran objek akan tersedia sebagai sebahagian daripada 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 ];

Gabungan

Dalam contoh seterusnya, pengendali penyebaran digunakan untuk menggabungkan tatasusunan:

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

Menggabungkan objek

Operator penyebaran, seperti Object.assign(), dapat digunakan untuk menyalin sifat dari satu atau lebih objek ke objek kosong dan menggabungkan sifatnya.

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

Kekayaan harta tanah

Pertimbangkan kod seterusnya:

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