Começando com ES6

Começando com ES6

Bem, nas ultimas semanas me deparei com alguns tutoriais utilizando ES6 (tambem conhecido como ECMAScript 2015 ou Harmony) como exemplo. Sim achei algumas coisas divinas, já outras são coisas que temos no Node.js por exemplo. Outras são coisas que alguns queriam a muito tempo para aproximar desenvolvedores back-end da linguagem JavaScript como classes.

Pois bem, este pode ser um post inútil para alguns, mas foi o que absorvi no ultimo mês e estou começando a utilizar em projetos pessoais com Browserify e Babelify.

Na ultima semana terminei de assistir a um workshop realizado pelo @fnando mostrando alguns goodies de JS já utilizando ES6. Confesso que fiquei perplexo no início já que o compilador ficava cuspindo um monte de código sem sentido ES5, porem tudo funcionando. Então percebi assim duas coisas:

  • 1- ES6 não é difícil se você já sabe JS;
  • 2- Babel é o transpiler mais quente do momento (Sorry TypeScript).

Pois bem, agora chegou a hora de passar o pouco que aprendi adiante, não se preocupe se você é um back-end developer, eu acredito que este pode ser o melhor momento para você aprender ES6, já que ele é muito mais clean e possui uma sintaxe um pouco mais bonita (Sugar syntax).

GOALS

  • Modules;
  • Arrow Functions;
  • Classes;
  • let & const;
  • Template strings.

Modules

Porque não utilizar módulos hoje? Nós já fazemos isso no Node utilizando o padrão CommonJS ou até mesmo no front-end utilizando Browserify e RequireJS.

//sum.js
var sum = function(x, y) {
  return x + y;
};

export { sum }

//multiplier.js
var multiplier = function(x, y) {
  return x * y;
};

export { multiplier }

//calculator.js
import { sum } from "./sum";
import { multiplier } from "./multiplier";

var result = sum(15,27);   // 42
result = multiplier(6,7);  // 42

Nada de mais certo? Já temos isso no Node, declaramos algo a ser exportado com module.exports e utilizamos require para utilizarmos em nossa app.

Ok, tudo muito legal…era isso que queriamos…um belo dia nativo para os browsers. Mas calma, ainda é possível brincar um pouco mais:

//calculator.js
export function sum(x, y) {
  return x + y;
};

export function multiplier(x, y) {
  return x * y;
};

// --- OU ---

var sum = function (x, y) {
  return x + y;
},
  multiplier = function (x, y) {
    return x * y;
};

export { sum, multiplier };

//app.js
import { sum, multiplier } from 'calculator';
// Importar ambas as declarações de um mesmo arquivo

// --- OU ---

import { sum as somar, multiplier as multiplicar } from 'calculator';
// Utilizar alias para as declarações exportadas

// --- OU ---

import * from 'calculator';
// Importar todas as declarações exportadas

São algumas outras opções do que se pode fazer com módulos no ES6. Acredito que para alguns isso não será grande coisa, mas achei interessante ter isso nativo no lado cliente.

Arrow Functions

Bom, muita gente que eu vi falar de ES6, estavam completamente apaixonados pela arrow functions, e não é pra menos, já que além de diminuir a quantidade de código também te da “de graça” o escopo pai, sem precisar de .bind(this) nem nada do tipo.

Vamos a um exemplo utilizando coisas que já fazemos no JavaScript.

var products = [
    { name: 'Macbook Pro', val: 1999 },
    { name: 'Go Pro', val: 299 },
    { name: 'PlayStation 4', val: 599 }
];

console.log(products.map(prod => prod.val));
// [1999, 299, 599] // [1999, 299, 599]

var prices = products.map(prod => prod.val);
var sum = prices.reduce((a, b) => a + b);
// 2897

console.log(sum);

var productsWithTax = products.map(prod => {
    var val = prod.val;
    return val * 1.6;
});

console.log(productsWithTax);
// Nossos produtos com 60% de imposto
// Sem levar em consideração a conversão para o dolar =)

Bem simples este exemplo utilizando .map certo? Não sei mais o que podemos utilizar de exemplo, vamos com as funções simplistas utilizadas acima:

var sum = (x, y) => {
  return x + y;
},
  multiplier = (x, y) => {
    return x * y;
};

Entendeu como funciona? Ainda assim temos passagem por block scope com nenhum ou mais argumentos, ou simplesmente a expressão sem necessidade de um bloco.

Algo assim:

() => {  }   // Sem argumentos
x => {  }    // Um argumento

(x, y) => {  }         // N argumentos
x => { return x * x }   // Block scope
x => x * x              // Mesma expressão sem necessidade do block scope

Sabe Promise? Eu venho utilizando esta abordagem em meus projetos e estou bem satisfeito. Vamos a um exemplo simples:

var promise = new Promise((resolve, reject) => {

  if (/* Condição */) {
    resolve("Promise + Arrow Functions = <3");
  }
  else {
    reject(Error("Oops!"));
  }
});

promise.then((result) => {
  console.log(result);  // "Promise + Arrow Functions = <3"
}, (err) => {
  console.log(err);     // Error: "Oops!"
});

Existem N partes que você pode utilizar Arrow Functions, basta ver se faz sentido e, utilizar em seu projeto.

Classes

Agora vem a parte legal, eu particularmente conheço muita gente que fala que JavaScript não suporta Orientação a Objetos, que isso, que aquilo.

Certa vez, lendo o livro Secrets of the JavaScript Ninja do @jeresig, ele cita algumas vezes no começo sobre funções serem objetos de primeira classe e uma parte sobre funções construtoras. Depois que li isso tive certeza quanto ao JavaScript supoartar OO mesmo sendo baseada no que é chamado de prototype.

Você pode nunca ter visto a palavra class, mas provavelmente já viu algo do tipo:

function Todo () {
  var items = [];

  this.getItems = function () {
    return items;
  };

  this.addItem = function(item) {
    items.push(item);
  };
}

var todo = new Todo();
todo.addItem('Aprender ES6');
todo.addItem('Ir no BrazilJS');
console.log(todo.getItems());  // ["Aprender ES6", "Ir no BrazilJS"]

Bem, mesmo não aceitando você acabou de ver um pseudo código bem simples de como objetos funcionam no JS. Não vou prolongar este post explicando sobre isso, você pode entender o conceito neste ótimo post do @wbrunom.

Sem mais delongas… vamos nos benificiar das classes do ES6 utilizando exatamente o mesmo código acima:

class Todo {
  constructor() {
    this.items = [];
  }

  getItems () {
    return this.items;
  }

  addItem(item) {
    this.items.push(item);
  }
}

var todo = new Todo();
todo.addItem('Aprender ES6');
todo.addItem('Ir no BrazilJS');
console.log(todo.getItems());  // ["Aprender ES6", "Ir no BrazilJS"]

Pronto! Ta ai seu class. Vamos analisar o código:

  • Definimos no constructor que iremos criar uma propriedade items que nesse caso não esta recebendo nenhum argumento;
  • Diferente da função construtora, a propriedade items não é privada;
  • Definimos os mesmos métodos utilizando name () { ... }, o que funciona exatamente da mesma forma;
  • Instanciamos o objeto exatamente da mesma forma que a função construtora.

Eu venho acompanhando o blog do Dr. Axel Rauschmayer que esta atualmente escrevendo o livro Exploring ES6 e achei algumas referencias para deixar um atributo privado em uma classe. Não é muito elegante, mas funciona:

var _items = [];

class Todo {
  constructor() {
  }

  getItems () {
    return _items;
  }

  addItem(item) {
    _items.push(item);
  }
}

Como disse, não é uma maneira muito elegante, mas se for necessário você provavelmente vai separa isso num módulo e vai exportar apenas a classe.

Também podemos ter o conceito de herança muito mais claro com ES6, vou utilizar um exempo ridiculo que vi na faculdade mas acho que serve nesse caso:

class Conta {
  constructor(number, dono) {
    this.number = number;
    this.owner = dono;
    this.saldo = 0;
  }

  deposit(qnt) {
    if(qnt > 0) {
      return this.saldo += qnt;
    }
    throw new Error("Insira um valor valido");
  }

  withdraw(qnt) {
    if(qnt <= this.saldo) {
      return this.saldo -= qnt;
    }
    throw new Error("Você não tem saldo suficiente");
  }

  checkSaldo() {
    return this.saldo;
  }
}

class ContaCorrente extends Conta {
  constructor(number, dono){
    super(number, dono);
    this.type = 'Corrente';
  }
}

var cc = new ContaCorrente(10007,'Rafaell');
cc.deposit(1000);

console.log(cc.checkSaldo()); // 1000
  • Criamos a ContaCorrente ContaCorrente e utilizamos extends que herda os métodos da classe pai;
  • Perceba que no construtor, utilizamos a mesma assinatura, porem passamos como atributos para o método super que nada mais é do que o construtor da classe pai.

Esse conceito de herança já existe a algum tempo, mas o código serial algo assim:

function Conta (number, dono) {
  // Code
};

function ContaCorrente (number, dono) {
  // Code
};
ContaCorrente.prototype = Object.create(Conta.prototype);
ContaCorrente.prototype.constructor = ContaCorrente;
ContaCorrente.parent = Conta.prototype;

Funciona, mas realmente é algo um pouco mais cansativo para se ler e/ou escrever.

let & const

Uma coisa interessante que veio com ES6 mas me deixou confuso no inicio foi let e o block scope.

O let funciona parecido como o var porem ele corrige o problema que temos quanto ao escopo e hoisting, ou seja, ela ira apenas existir quando definida em um bloco ({}) onde sera criado seu escopo.

// ES5
var a = 1;

if (1 === a) {
 var b = 2;
}

console.log(b); // 2

// ES6
var a = 1;

if (1 === a) {
 let b = 2;
}

console.log(b); // Uncaught ReferenceError: b is not defined

Nesse caso utilizando let, podemos ver claramente que o valor de b não é declarado fora do bloco if na etapa de hoisting. Ao meu ver isso é até complicado de se explicar, mas vou utilizar um outro exemplo que talvez fara mais sentido:

for (var i = 0; i < 3; i++) {
 // …
}

function someFunc() {
 var a = 'My Secret Value';
}

console.log(i); // 3
console.log(a); // Uncaught ReferenceError: a is not defined

Acho que agora vou conseguir explicar melhor. Se você não sabia, quando utiliza o for, a variavel que usa como count é definida globalmente no escopo durante o hoisting, enquanto a variável a esta presente apenas dentro da função someFunc. A função cria um novo contexto para a variável a, porem ela também pode enxergar a variável i do nosso loop. Uma forma de evitarmos isso durante o hoisting é utilizando let, que cria uma variável dentro de um escopo isolado definido por blocos ({}):

for (let i = 0; i < 3; i++) {
 // …
}

function someFunc() {
 let a = 'My Secret Value';
}

console.log(i); // Uncaught ReferenceError: i is not defined
console.log(a); // Uncaught ReferenceError: a is not defined

Meh…constantes

Agora temos const funciona também respeitando o block scoping como let, porem se comporta da forma que uma constante deveria se comportar (ready only).

const PI = 3.141593;

PI = 3.14; // throws “PI” is read-only

Template strings

Depois de muito tempo, agora temos template strings nativo com ES6.

WAT?!

Melhor eu mostrar =)

// Sem template String
let link = 'http://www.ecma-international.org/ecma-262/6.0/';
let cssClass = 'link-js';
let text = 'ECMAScript® 2015 Language Specification';
let elementTpl = '<a href="' + link + '" class="' + cssClass + '">' + text + '</a>';

console.log(elementTpl);
// <a href="http://..." class="link-js">ECMAScript® 2015...</a>

// Com template String
elementTpl = `<a href="${link}" class="${cssClass}">${text}</a>`;

console.log(elementTpl);
// <a href="http://..." class="link-js">ECMAScript® 2015...</a>

Não é lindo? Simples? Quase uma obra divina?

E o melhor, apenas utilizando `

Conclusão

ES6 não é pra ser usado no futuro, etc… é pra ser aprendido e se possível utilizado hoje!

Você pode aprender lendo sobre isso em blogs ou vendo vídeos como eu fiz, ou procurar alguma documentação.

Algumas boas referencias para estudo e opções de transpilers:

Como fazer isso? Bom, eu li num post do @jaydson sobre utilizar o Babel como transpiler e carregar os módulos com o Browserify. Isso tem funcionado bem nos meus projetos, ainda não consegui colocar isso aqui no Tripda, mas não vai demorar muito. =)