JavaScript 2ª Ed.

Page 1


3

FUNÇÕES

1

Em JavaScript, e ao contrário do que acontece em muitas linguagens de programação, as funções são consideradas “elementos de primeira classe”. Na prática, as funções permitem agrupar um conjunto de instruções que podem ser executadas várias vezes através de um processo designado por invocação. Neste capítulo, analisamos todas as particularidades associadas à definição e utilização destes objetos em JavaScript.

3.1

DEFINIÇÃO DE FUNÇÕES

À semelhança dos objetos, as funções também podem ser definidas através de literais. O excerto seguinte ilustra esta técnica: function dizOla(nome) { return "Ola, " + nome; }

A definição de uma função através de um literal é composta por várias secções ou zonas. A primeira secção é obrigatória e resume-se ao termo function. A segunda é opcional e indica o nome da função (no exemplo anterior, dizOla). Quando não especificamos um nome, estamos perante uma função anónima. A terceira secção de uma função permite definir os parâmetros esperados pela função. Esta secção é sempre delimitada por parêntesis e, no seu interior, podemos encontrar zero ou mais parâmetros, identificados por um nome (no exemplo anterior, temos apenas um único parâmetro, designado por nome). No interior da função, os parâmetros funcionam como variáveis locais e são inicializadas com valores passados do exterior aquando da invocação da função. Finalmente, uma função está sempre associada a um corpo (quarta secção) que é delimitado por chavetas. O corpo de uma função pode conter zero

© FCA – Editora de Informática


72

JAVASCRIPT

ou mais instruções que apenas são interpretadas e executadas em resposta à sua invocação. Funções anónimas Como vimos, o nome da função é opcional. A invocação de uma função anónima pode ser efetuada aquando da sua declaração. Esta estratégia é conhecida como invocação in-place e é muito usada para evitar a poluição do namespace global. O excerto seguinte ilustra este cenário: (function(){ //várias instruções })(); A delimitação da função por parêntesis seguida do operador de invocação (()) é necessária devido à forma como as instruções são interpretadas em JavaScript. Uma das regras introduzidas pela especificação diz que, numa instrução, a primeira expressão não pode ser o termo function. É devido a esta regra que somos obrigados a delimitar a expressão literal da função por parêntesis para permitir a sua invocação in-place.

Como referimos, uma função é também um objeto. O principal aspeto diferenciador de uma função (quando comparada com um objeto “normal”) reside no facto de ela poder ser invocada através do operador (). No excerto seguinte, ilustramos o uso deste operador para invocar a função dizOla definida previamente: dizOla( "Luis" ); //devolve string "Ola, Luis"

Como as funções também são objetos, então elas podem ser utilizadas em qualquer lugar onde seja esperado um objeto. Na prática, isto quer dizer que podem ser usadas como valores de variáveis, podem ser passadas para outras funções através de parâmetros ou podem mesmo ser usadas como valor devolvido por uma função. As funções também são objetos Em JavaScript, uma função é também um objeto. As eventuais dúvidas que possam surgir em relação a esta afirmação costumam desvanecer-se quando temos contacto com a utilização da função construtora Function: var ola = new Function( "nome", //lista de parâmetros separados por ,

© FCA – Editora de Informática


FUNÇÕES

73

"return ‘Ola, ‘ + nome;"); //corpo da função ola("Luis"); //invocar função O excerto anterior produz uma função semelhante à apresentada no início da secção 3.1. A função construtora Function pode receber vários parâmetros, sendo que o último representa sempre o corpo da função. Apesar de o excerto tornar claro que uma função é um objeto (recorde-se que o uso do operador new em conjunto com invocação de função devolve sempre um objeto), esta sintaxe não traz grandes vantagens e deve ser mesmo evitada. Isto porque ela acaba por ser menos eficiente do que o uso de literais (o código definido através do uso direto da função construtora é apenas interpretado aquando da sua execução). Como vimos no Capítulo 2, os objetos caracterizam-se por serem contentores de propriedades, com uma referência a uma propriedade “escondida” especial, designada por prototype. No caso dos objetos definidos através de literais (Capítulo 2), essa propriedade referencia o objeto devolvido por Object.prototype. No caso de funções definidas através de literais, elas herdam os membros definidos pelo objeto devolvido por Function.prototype (que, por sua vez, acabam também por referenciar Object.prototype).

3.1.1

INSTRUÇÕES VS. EXPRESSÕES

As funções definidas como literais podem ser construídas a partir de instruções ou expressões. Estas duas formas introduzem algumas pequenas diferenças sintáticas que podem afetar os resultados obtidos num programa. A função apresentada no exemplo da secção 2.1 foi construída a partir de uma instrução. O excerto seguinte mostra como podemos construir uma função através de uma expressão: var dizOla = function(nome){ return "Ola, " + nome; }; dizOla("Luis");//invocar função

As funções definidas através de expressões assemelham-se muito às definidas através de instruções (quer do ponto de vista de invocação, quer do ponto de vista de definição). Do ponto de vista de execução, existe uma diferença importante entre as funções definidas através de instruções (como a apresentada na secção 3.1) e as declaradas através de expressões (atribuídas a variáveis como

© FCA – Editora de Informática


74

JAVASCRIPT

a função anónima usada na expressão anterior) que se torna óbvia quando analisamos o excerto seguinte: function enviaEcra() { alert("global ecra"); } function enviaImpressora() { alert("global impressora"); } function baralha() { alert(typeof enviaEcra); //interior enviaEcra alert(typeof enviaImpressora);//undefined //não é possível executar enviaImpressora! //devido à forma como o hoisting é feito sobre //variável que referencia a função function enviaEcra() { alert("interior ecra"); } //função anónima definida através de expressão //atribuída a variável var enviaImpressora = function () { alert("interior impressora"); }; //já é possível executar função anónima atribuída //a enviaImpressora enviaImpressora(); //ok: devolve interior impressora } baralha();//executa método baralha

No interior da função baralha, criámos duas funções com os mesmos nomes do que as funções globais. Devido à forma como os âmbitos funcionam em JavaScript, estas funções locais escondem as globais no interior de baralha. enviaEcra identifica uma função que foi definida através de uma instrução e enviaImpressora identifica uma variável cujo valor é uma função © FCA – Editora de Informática


FUNÇÕES

75

construída à custa de uma expressão. À semelhança das variáveis, as funções declaradas através de instruções no interior de uma função estão também sujeitas ao processo de hoisting (apresentado no Capítulo 1). Portanto, no interior de baralha, todas as declarações de variáveis e funções (através de instruções) são automaticamente movidas para o topo do corpo da função. Como apenas as declarações são movidas para o início do corpo, então enviaImpressora possui o valor undefined até à linha do programa onde ela é inicializada com a função anónima a ser executada. Repare-se que isto não acontece com a função definida à custa de uma instrução: nesse caso, a função e respetivo corpo são movidos para o topo do âmbito. É devido a este comportamento que o operador typeof aplicado a enviaImpressora no início da função baralha devolve undefined e é também por isso que só podemos invocar essa função após o encerramento do seu corpo. As funções devem ser declaradas através de instruções ou declarações? Se tivermos em conta os pormenores apresentados nesta secção, então a escolha da estratégia a usar é quase uma questão de preferência pessoal. Neste livro, optámos por definir praticamente todas as funções através de expressões atribuídas a variáveis. Esta estratégia tende a esbater o mito de que uma função é uma construção especial suportada pela linguagem. Apesar de isto ser um facto em muitas linguagens, a verdade é que em JavaScript uma função é um objeto que pode ser usado em qualquer lugar onde é esperado um valor.

3.2

PARÂMETROS

Opcionalmente, podemos indicar um ou mais parâmetros aquando da definição de uma função. Os parâmetros podem ser vistos como variáveis locais que são automaticamente inicializados com os valores passados aquando da invocação da função: var junta = function(primeiroNome, ultimoNome){ return primeiroNome + “-“ + ultimoNome; }; //os valores passados à função //são automaticamente mapeados nos parâmetros junta("Luis", "Abreu"); © FCA – Editora de Informática


76

JAVASCRIPT

A definição de parâmetros serve apenas para simplificar o acesso aos valores passados a uma função. Isto deve-se ao facto de, no corpo de uma função, ser possível aceder a uma variável especial, designada por arguments, que disponibiliza um comportamento semelhante ao de um array e permite aceder aos vários valores passados aquando da invocação da função. No excerto seguinte, apresentamos um método que é capaz de somar todos os números que lhe são passados através de parâmetros: var soma = function () { var res = 0; for (var i = 0, total = arguments.length; i < total; i++) { res += arguments[i]; } return res; }; var soma1 = soma(1, 2); //3 var soma2 = soma(1, 2, 3, 4); //10 Funções invocadas com “excesso” de parâmetros Em JavaScript, os valores passados a uma função aquando da sua invocação não estão limitados ao número de parâmetros indicados pela declaração da função. Este comportamento contrasta com o de outras linguagens, onde os valores passados têm de ser compatíveis com o número e tipos de parâmetros declarados pela função. Portanto, nada nos impede de efetuar as seguintes invocações: junta(); //string undefined-undefined junta("Luis", "Miguel", "Abreu");//Luis Miguel No primeiro caso, os parâmetros primeiroNome e ultimoNome possuem o valor undefined já que a função foi invocada sem quaisquer valores. No segundo, os parâmetros são inicializados com duas strings e o acesso ao terceiro valor só é possível através da utilização do objeto arguments.

É importante notar que arguments não referencia um array, mas sim um objeto com comportamento semelhante ao disponibilizado por um array (esta conclusão torna-se óbvia quando notamos que não temos acesso às propriedades disponibilizadas pelo objeto Array.prototype).

© FCA – Editora de Informática


FUNÇÕES

77

Atribuição de valores predefinidos aos parâmetros e parâmetros opcionais Apesar de já existirem algumas ideias, a verdade é que a especificação ECMAScript5 ainda não prevê a atribuição de valores predefinidos a parâmetros aquando da sua definição. Mas nem tudo está perdido e, uma vez mais, podemos recorrer a um pequeno truque (baseado na forma como a linguagem executa a operação lógica ||) para garantir que os parâmetros possuem um valor válido: var imprime = function( nome ) { nome = nome || ""; //nome nunca é null ou undefined! }; Existem casos onde uma função pode receber vários parâmetros e muitos deles são opcionais. Em situações deste tipo, podemos recorrer a um objeto anónimo para simplificar a passagem de valores. O excerto seguinte ilustra a construção e definição deste tipo de funções: //neste exemplo, vamos partir do princípio //que os parâmetros são todos números var fn = function (par1, par2, par3, par4) { if (typeof par1 === "object") { par2 = par1.par2 || 0; par3 = par1.par3 || 0; par4 = par1.par4 || 0; par1 = par1.par1 || 0; } else { par1 = par1 || 0; par2 = par2 || 0; par3 = par3 || 0; par4 = par4 || 0; } //a partir desta altura, podemos usar os parâmetros //normalmente no interior da função

© FCA – Editora de Informática


78

JAVASCRIPT

} fn(0, 1, 1, 2); fn({ par4: 10 }); Internamente, a função fn começa por verificar o tipo do primeiro parâmetro. Como a função espera apenas números, então ela pode fazer algumas suposições relativas ao primeiro parâmetro. Se ele for do “tipo” object, sabemos que estamos na presença da utilização de um objeto anónimo que (supostamente) deve inicializar os parâmetros da função. Nesses casos, temos de extrair os valores desse objeto para inicializar os parâmetros usados pela função. Portanto, esta estratégia é recomendada para funções que esperam muitos parâmetros, já que permite-nos simplificar a invocação da função (repare-se no último exemplo da invocação da função fn, onde passamos apenas o valor do parâmetro par4).

3.3

VALORES DEVOLVIDOS POR UMA FUNÇÃO

A invocação de uma função resulta sempre no retorno de um valor. Tipicamente, esse valor retornado é indicado através de uma instrução return. Ao encontrar uma instrução destas no interior de uma função, o processamento da função é automaticamente interrompido, transferindo-se o controlo de execução para o local onde a função foi invocada: var faz = function(){ //instruções return 10; //eventuais instruções existentes a partir //deste local nunca são executadas //e o ambiente runtime devolve controlo de //execução para a linha a seguir à invocação da função } var t = faz();

Se não for indicado um valor através da instrução return, então o valor undefined é automaticamente devolvido. Este comportamento é ligeiramente diferente quando uma função é usada em conjunto com o operador new (mais informações na secção 3.4.2). Nesses casos, se o valor devolvido a partir da função não for um objeto, então o ambiente runtime substitui-o automaticamente pelo valor do termo this no interior dessa função: © FCA – Editora de Informática


FUNÇÕES

79

var Pessoa = function(nome){ this.nome = nome; //não existe utilização explícita de return //logo, é semelhante a termos return this } var p = new Pessoa("Luis"); //p referencia o object this da função Funções devolvidas de funções Como as funções são objetos, então também podem ser usadas como valor devolvido de uma função: var contador = function(){ var total = 0; return function(){ total++; return total; } }(); contador();//devolve 1 contador();//devolve 2 O código apresentado no exemplo anterior produz uma função que é definida e executada in-place. No exemplo anterior, a função exterior devolve uma outra função definida no seu interior que é capaz de manter em “memória” o resultado da sua última invocação. Pode parecer um pouco estranho à primeira vista, mas esta técnica é muito poderosa e serve de base a um comportamento designado por closure (analisado na secção 3.7.2), que possibilita a construção de cenários avançados (como veremos nas próximas secções).

3.4

INVOCAÇÃO DE FUNÇÕES

Como vimos, a invocação de funções é feita através do operador ( ). Ao encontrar uma instrução de invocação, os eventuais parâmetros definidos pela função são inicializados e o controlo é transferido para a primeira linha do corpo da função. Para além dos parâmetros, existem ainda duas variáveis especiais que são automaticamente passadas para uma função: arguments (apresentado na secção 3.2) e this. O valor do parâmetro this é importante e depende do tipo de invocação efetuado. © FCA – Editora de Informática


80

JAVASCRIPT

3.4.1

INVOCAÇÃO DE FUNÇÕES SIMPLES

Todos os exemplos apresentados até aqui foram invocados através desta sintaxe. É usada quando invocamos uma função que não é exposta como uma propriedade de um outro objeto: var junta = function( primeiro, ultimo ){ return primeiro + " " + ultimo; } var aux = junta( "um", "dois" );

Nestes casos, o valor do termo this no interior da função depende do modo de execução da unidade de script. Quando a função não é executada em modo strict (introduzido pela especificação ECMAScript5 e apresentado detalhadamente no Capítulo 8), this referencia o objeto global. Por outro lado, quando a função é executada em modo strict, this possui o valor undefined.

3.4.2

INVOCAÇÃO DE FUNÇÕES CONSTRUTORAS

Como veremos no Capítulo 4, o mecanismo preferido de reutilização de código em JavaScript passa pela utilização de protótipos. Este mecanismo representa uma quebra com a forma de construir objetos usada na maior parte das linguagens orientadas a objetos tradicionais. Com este mecanismo, um objeto reutiliza os membros de outro com base na atribuição de um objeto a uma propriedade da função construtora, dispensando, assim, a utilização de classes. Apesar disso, o JavaScript também permite a utilização de funções como construtores, simplificando assim a criação de novos objetos para aquelas pessoas que preferem uma abordagem mais clássica. Quando uma função é invocada em conjunto com o operador new, é criado um novo objeto cujo protótipo referencia a propriedade prototype dessa função: var Pessoa = function (nome) { this.nome = nome; } var p = new Pessoa("Luis"); //Object.getPrototypeOf(p) === Pessoa.prototype //true //p.constructor === Pessoa //true

© FCA – Editora de Informática


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.