Eloquent Javascript – Parte 2

Esse post é uma continuação do meu estudo do livro Eloquent Javascript, iniciado aqui. Agora eu também mantenho uma página com o índice de livros com todos os posts referentes a eles.

Capítulo 6 – Programação Funcional

Esse capítulo apresenta algumas funcionalidades comuns a linguagens funcionais como funções anônimas, map, reduce, aplicação parcial de função e composição de funções. Boa parte do capítulo é dedicada a apresentar uma solução para um problema de converter texto em HTML. Achei interessante mostrar a aplicação de programação funcional num exemplo real, mas achei que fugiu um pouco do tópico.

Achei estranho o fato de o autor não mencionar closures, que é uma técnica muito empregada em javascript e que a meu ver é base para várias dessas funcionalidades de programação funcional apresentadas.

Em javascript podemos ter funções dentro de funções. Nesse caso, as funções internas têm acesso às variáveis locais definidas nas funções mais externas. No código abaixo, a função interna (anônima) tem acesso (leitura e escrita) à x e também à variável y.

Em geral, quando a função termina, perdemos a referência para as variáveis locais definidas dentro dessa função. Em javascript, funções podem ter comportamento parecido com o de objetos. Uma prova disso é que podemos retornar uma função, como no exemplo abaixo.

Combinado com a propridade de acessar membros das funções externas, temos um mecanismo de acesso indireto aos membros internos da funções. Esse é o conceito mais básico de closures.

function closure(y){
    var x = 10;
    return function(z){
        alert(x + y + z);
        x++;
    }
}
var foo = closure(5);
foo(3); // imprime 18
foo(3); // imprime 19

Com closure podemos fazer a aplicação parcial de funções. O segredo é que a keyword arguments contém todos os argumentos passados para uma função, mesmo que eles não sejam usados pela função. Assim, a função partial definida abaixo recebe uma função func como argumento. Todos os parâmetros adicionais, ele repassa para func, junto com os parâmetros que serão passados para a função anônima quando ela for invocada.

A função asArray serve apenas para converter parameters para um array, pois é esse o tipo esperado pela função apply.

// É preciso converter a lista de argumentos para array
// antes de passar para a função apply
function asArray(quasiArray, start){
    var result = [ ];
    for(var i = (start || 0); i < quasiArray.length; i++)
        result.push(quasiArray[i]);
    return result;
}

// Partial salva os primeiros argumentos da função em 'fixedArgs'
// Quando a função for de fato chamada, apenas os argumentos restantes
// precisam ser passados
function partial(func){
    // O primeiro argumento é func.
    var fixedArgs = asArray(arguments, 1); 
    return function(){
        return func.apply(null, fixedArgs.concat(asArray(arguments)));
    };
}

Para ficar mais claro, considere uma função anônima implementando uma soma e suponha que passamos esta função para partial, mais o parâmetro 3. Este parâmetro será aplicado à soma antes dos parâmetros passados para a função “objeto” foo.

var foo = partial(function(a, b){ return a + b; }, 3);
var res = foo(20); // 23

Usando uma ideia parecida, podemos fazer a composição de duas funções. No exemplo abaixo, se passarmos uma função foo() e bar() para composition, obteremos uma função equivalente a foo(bar()):

// Composição de duas funções
function composition(func1, func2){
    return function(){
        return func1(func2.apply(null, asArray(arguments)));
    };
}

Um exemplo de uso dessa função é a composição de uma função que retorna o dobro do valor da entrada e outra que soma dois elementos.

var composed = composition(
    function(v){ 
        return 2*v;
    }, 
    function(a, b){
        return a + b;
    }
)
alert(composed(6, 7)); // 26

Capítulo 7 – Busca

Logo no começo do capítulo o autor observa que este não introduzirá nenhuma funcionalidade nova de Javascript. De fato, é apresentado o problema do menor caminho, o qual é atacado inicialmente através de um algoritmo aleatório força bruta. Depois ele descreve rapidamente o algoritmo A* e apresenta uma solução em Javascript, usando alguns dos conceitos ensinados até então.

Capítulo 8 – Orientação a Objetos

Neste capítulo são apresentadas técnicas para trabalhar com orientação a objetos em Javascript. Basicamente, podemos criar um objeto a partir de uma função com o operador new.

function Foo(){
}
var obj = new Foo();

Pensando em classes, Foo age como construtor e definidor da classe. Poderíamos declarar membros e métodos dentro desse construtor.

function Foo(int x){
    this.x = x;
    this.bar = function(){
        alert(this.x);
    }
}
var a = new Foo(10);
a.bar();
var b = new Foo(11);
b.bar();

É possível adicionar e modificar métodos da “classe” depois que ela já foi definida através dos chamados protótipos (prototype). Toda função tem uma propriedade chamada prototype e este herda um conjunto de propriedades do protótipo de Object. Entre essas propriedades estão toString e constructor. O constructor aponta para a própria função, de forma que as duas linhas abaixo são equivalentes:

var a = new Foo(10);
var a = new Foo.prototype.constructor(10);

O autor continua apresentando técnicas para se trabalha com OO em Javascript através de um exemplo bem interessante de simulação, consistindo de um mapa 2D e várias criaturas ficam se movimentando. É possível ver a simulação em tempo real (em modo texto) com o interpretador embutido na página.

Ao apresentar o código dessa simulação, ele descreve uma situação problemática: passar um método como parâmetro para uma função externa (callback), como no exemplo abaixo:

function say(func){
    alert(func());
}

function Foo(){
    this.v = [10];
    this.get = function(){
        return this.v;
    };
    this.bar = function(){
        say(this.get);
    };
}
var c = new Foo();
c.bar();

A função foo irá imprimir undefined. Segundo [3], ao executar a função say, a referência ao objeto é perdida. Para contornar a situação, é possível usar um closure, para manter a referência ao objeto.

Nesse sentido, é comum definir a seguinte função:

function bind(func, obj){
    return function(){
        return func.apply(obj, arguments);
    }
}

A função retornada por bind contém a referência ao objeto obj. Podemos reescrever o método bar da seguinte maneira:

this.bar = function(){
    say(bind(this.get, this));
}

Herança

Um mecanismo de herança pode ser obtido em Javascript através de protótipos. A ideia é clonar o protótipo da classe base.

function clone(object) {
    function Constructor(){};
    Constructor.prototype = object;
    return new Constructor();
}

Object.prototype.inherit = function(baseConstructor) {
  this.prototype = clone(baseConstructor.prototype);
  this.prototype.constructor = this;
};

Um exemplo de uso:

function Base(){ } 
Base.prototype.x = 10;
Base.prototype.bar = function(){
    alert(this.x);
}
function Derived(){ }
Derived.inherit(Base);

Capítulo 9 – Modularidade

Este capítulo não apresenta nenhuma novidade de Javascript, focando mais em boas práticas sobre organização do código. São ensinados truques para gerenciar dependências entre módulos e maneiras de se publicar somente um subconjunto das funções presentes no módulo.

Capítulos 10, 11, 12, 13 e 14

O capítulo 10 apresenta expressões regulares em Javascript. Os capítulos restantes falam sobre o básico programação Web, DOM (Document-Object Model), eventos dos browsers e requisições HTTP.

Como eu já conhecia o básico de cada assuntos desses e no livro não foram muito aprofundados, não tenho nenhum comentário adicional para fazer aqui.

Conclusão

Javascript é mais complicado do que parece! Ainda tenho bastante dúvida sobre closure e protótipos e pretendo pesquisar mais sobre o assunto.

Leitura adicional

[1] Stackoverflow – How do JavaScript closures work?
[2] Javascript Closures
[3] Stackoverflow – JavaScript Callback Scope

Os comentários estão fechados.

%d bloggers like this: