Elementos da linguagem JavaScript.
Em constaste, nas linguagens de scripting assume-se que já existe uma coleção, pronta a ser usada, de componentes escritas noutras linguagens. As linguagens de scripting são concebidas para permitir ligar e organizar componentes existentes de forma simples, expedita e flexível. Exemplos: JavaScript, Bash, Tcl, Perl, PHP, Ruby, Python.
Por exemplo, quando se usa JavaScript para adicionar interatividade a uma página Web, as componentes em causa, são os objetos DOM que representam essa página Web.
Chama-se script a um programa escrito numa linguagem de scripting.
Muitas vezes escreve-se um script para automatizar uma tarefa que de outra forma seria executada manualmente por um colaborador da empresa. Esse tarefa não justificaria o esforço de escrever um programa usando uma liguagem clássica.
Outra diferença é o facto das linguagens de scripting serem geralmente interpretadas e não compiladas. Um dos objetivos disso é acelerar o desenvolvimento do código. Outro objetivo é facilitar a geração dinâmica de scripts que possam ser executados imediatamente pelo interpretador.
Nas linguagens de scripting dá-se pouca importância à questão eficiência. Em contrapartida dá-se a máxima importância à simplicidade, flexibilidade de utilização e a um grande poder expressivo que permita escrever scripts compactos. A questão da eficiência é pouco importante porque os scripts tendem a ser pequenos e porque a eficiência da linguagem é dominada pela eficiência das componentes, as quais são normalmente implementadas numa linguagens de programação clássica.
Algumas linguagem de scripting são também concebidas para serem usadas no interior duma aplicação de software, com o objetivo de fornecer ao utilizador um elevado grau de controlo do comportamento da aplicação, incluindo a adição de novas funcionalidades. Se é verdade que o utilizador não pode alterar o código de base da aplicação, ele pode escrever scripts para adaptar a aplicação às suas necessidades. Exemplos: Emacs Lisp é a linguagem de scripting do editor de texto emacs; JavaScript é linguagem de scripting mais usada nos browsers da WEB.
Quando estão em causa aplicações com algoritmos e estruturas de dados complexas, o melhor é usar uma linguagens de programação clássica. Usando uma linguagem de scripting, o script não ficaria mais pequeno, não haveria o benefício da tipificação estática para ajudar a apanhar antecipadamente erros subtis na utilização das estruturas de dados e, no final, o programa correria 10 vezes mais devagar.
As linguagens de scripting sempre tiveram alguma popularidade, mas ultimamente a sua importância tem aumentado. A principal razão é a tendência atual para escrever aplicações baseadas em componentes já disponíveis. É o que se passa, por exemplo, quando está em causa o desenvolvimento de interfaces gráficas e de aplicações que correm sobre a WEB.
Eis um exemplo de script em bash. Este script lista na consola o nome de todos os ficheiros HTML que se encontram na diretoria corrente. Além disso, escreve a primeira linha de cada um desses ficheiros num ficheiro chamado FILE_HEADS.
#!/bin/bash # This is a comment echo "List of files:" ls -lA FILE_LIST="`ls *.html`" echo FILE_LIST: ${FILE_LIST} RESULT="" for file in ${FILE_LIST} do FIRST_LINE=`head -1 ${file}` RESULT=${RESULT}${FIRST_LINE}$'\n' done echo ${RESULT} | cat > FILE_HEADS echo "${RESULT}" echo "Script done." |
Quando comunidade de utilizadores é grande, então justifica-se fazer o seguinte:
Regressemos ao estudo da linguagem JavaScrip, desta vez para apresentar os elementos de base mais importantes da linguagem.
JavaScript | Java |
Suporte para programação funcional | Desde o Java 8 suporta programação funcional limitada |
Tipificação dinâmica | Tipificação estática |
Baseada em protótipos | Baseada em classes |
Herança usando o mecanismo dos protótipos | Herança através da hierarquia de classes |
Adição dinâmica de novos membros a objetos | Não é possível a adição dinâmica de novos membros |
Estilo livre onde a maioria das declarações são opcionais | Estilo rígido para ser possível detetar erros em tempo de compilação |
break const delete for import new this void case continue do function in return typeof while default export if else switch var let with |
As restantes palavras reservadas do Java, que não aparecem na lista anterior, também estão reservadas em JavaScript, apesar de não serem usadas de momento. Todos os interpretadores de JavaScript deveriam proibir a utilização destas palavras. Contudo alguns não o fazem.
a = 5; v = 6;Mas se tivermos uma sucessão de instruções em linha diferentes, podemos omitir o ponto e vírgula porque o JavaScript insere implicitamente o terminador nesses casos. Exemplo:
a = 5 v = 6A insersão automática dos pontos e vírgula foi feita para ajudar o programador, mas infelizmente as regras são um pouco complicadas. O melhor será explicitar sempre os pontos e vírgula, como estamos habituados em Java e C.
Um exemplo confuso tirado da expecificação do ECMAScript® 2017. No seguinte caso
a = b + c (d + e).print() não é inserido ponto e vírgula, resultando o equivalente a
a = b + c(d + e).print()Variáveis
O JavaScript é uma linguagem dinamicamente tipificada. Uma consequência disso é o facto das variáveis não terem tipos associados. Os tipos ficam associados aos valores e não às variáveis. Exemplo:
let x = 34; // x contém um inteiro x = "Hello!" // agora x contém uma string |
As palavras var e let permitem definir variáveis. Nós preferimos usar let porque no caso de variáveis locais, o seu âmbito é determinado pelos blocos, como no Java.
Factos diversos sobre let:
let x = 10; function g(y) { return x + y; } { let x = 15; console.log(g(0)); } // Output: 10 |
{ let q = 2; } console.log(q); // Output: ReferenceError: x is not defined |
Exemplos sobre variáveis indefinidas:
console.log(zzz); // Lança a exceção ReferenceError se zzz nao estiver definida let zzz; if( zzz === undefined ) { // Testar se uma variável está indefinida. console.log("zzz is undefined"); } if( !zzz ) { // undefined comporta-se como false num contexto booleano console.log("zzz is undefined"); } |
A palavra const permite definir constantes.
const x = 5; |
Um literal de tipo string é uma porção de texto rodeada, ou por aspas ou plicas. Em código JavaScript, é comum usar plicas entre aspas e aspas entre plicas, como nestes exemplos: "He is called 'Neko-chan'"; 'He is called "Neko-chan"'.
O operador typeof pode ser usado para saber o tipo de qualquer valor.
São efetuadas conversões automáticas de tipo, entre os tipos primitivos. Exemplos:
"The answer is " + 42 // produz "The answer is 42" 42 + " is the answer" // produz "42 is the answer" "37" - 7 // produz 30 ... !? "37" + 7 // produz "377" ... !? true + 7 // produz 8 |
member . [] call / create instance () new negation/increment ! ~ - + ++ -- typeof void delete multiply/divide * / % addition/subtraction + - bitwise shift << >> >>> (o último faz shift sem sinal) relational < <= > >= in instanceof equality == != === !== bitwise-and & bitwise-xor ^ bitwise-or | logical-and && logical-or || conditional ?: assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= comma , |
== Igualdade, produz true se os argumentos forem iguais (após possíveis conversões automáticas de tipo). != Desigualdade, produz true se os argumentos forem diferentes. === Igualdade estrita, produz true se os argumentos forem iguais e do mesmo tipo. !== Desigualdade estrita, produz true se os argumentos forem diferentes ou se forem de tipos diferentes. |
O JavaScript pussui uma pequena biblioteca de objetos built-in com funcionalidades úteis. Eis alguns deles: Date, Array, Boolean, Function, Math, Number, RegExp, e String.
Mas no ambiente de execução envolvente, estão geralmente disponíveis mais objetos predefinidos. Por exemplo, no ambiente dum browser, todos os objetos previstos no DOM estão disponíveis: Document, Window, Form, Link, etc.
O JavaScript suporta expressões regulares semelhantes às da linguagem Perl.
A seguinte expressão regular representa dois "a"s seguidos de zero ou mais dígitos:
re = /aa\d*/ re = new RegExp("aa\\d*") // Equivalente |
O método test determina se uma string emparelha com a expressão regular.
O método exec produz um array com o resultado do emparelhamento na posição 0 do array, mais os resultados dos emparelhamentos das sub-expressões entre parêntesis. Se a expressão regular tiver a flag "g" ligada, então sucessivas chamadas de exec produzem sucessivos resultados de emparelhamentos até ser retornado null; sem a flag "g" apenas o resultado do primeiro emparelhamento é retornado.
let re = /d(b+)(d)/ig; re = new RegExp("d(b+)(d)", "ig"); // Equivalente let b = re.test("cdbBdbsbz"); // resultado: true let arr = re.exec("cdbBdbsdbdz"); // primeiro resultado: ["dbBd", "bB", "d"] |
let colors = ["Red", "Green", "Blue"]; let colors = new Array("Red", "Green", "Blue"); // Equivalente |
let len = colors.length; // Vale 3 |
let colors = ["Red", , , "Green", "Blue","Yellow"]; |
let colors = []; // Array vazio colors[2] = "Blue"; let len = colors.length; // Vale 3 let t = typeof(colors[0]); // Vale undefined |
colors[colors.length] = "Yellow"; colors.push("Yellow"); // Equivalente |
let last = colors.pop(); |
colors.length = 2; |
Para concatenar dois arrays, criando um novo array, existe a operação concat. Os arrays originais não são alterados. Exemplo:
let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; let arr3 = arr1.concat(arr2); // concatenacao |
Para percorrer os elementos dum array pode usar-se um for clássico, mas também se pode fazer assim:
let colors = ["Red", "Green", "Blue"]; colors.forEach(function(c) { console.log(c) }); // Iteração usando função anónima |
let table = [[0, 1, 2], [3, 4, 5]]; let r = table[0][2]; // Vale 2 |
Estão disponíveis úteis funções de ordem superior que podem ser aplicadas a arrays. Exemplos:
> let array = [0, 11, 2, 3, 4, 5]; undefined > array.map(function(x) { return x+1; }); [ 1, 12, 3, 4, 5, 6 ] > array.map(x => x+1); [ 1, 12, 3, 4, 5, 6 ] > array.filter(x => x % 2 == 0); [ 0, 2, 4 ] > array.reduce((acc,v) => acc+v, 0); 25 > array.every(x => x >= 0); true > array.some(x => x >= 0); true > array.find(x => x > 0); 11 > array.findIndex(x => x > 0); 1 |
Eis as sintaxes disponíveis para funções:
function square(n) { return n * n; } // Função com nome. let square = function(n) { return n * n; }; // Equivalente. Função anónima clássica. let square = n => n * n; // Equivalente. Função anónima simplificada (arrow function). |
A forma simplificada não define this e por isso não deve ser usada em métodos de classes. Eis um exemplo duma função de ordem superior, que depois é chamada usando uma função anónima como argumento:
function map(f, a) { let result = []; for (let i = 0 ; i < a.length ; i++) result[i] = f(a[i]); return result; } let a = map(function(x) { return x * x;}, [0, 1, 2, 3]); // Vale [0, 1, 4, 9] let a = map(x => x * x, [0, 1, 2, 3]); // Vale [0, 1, 4, 9] |
Dentro da cada função (exceto anónimas simplificadas) há um array predefinido chamado arguments que representa a sequência de argumentos realmente usados na chamada. Assim é fácil implementar funções com um número variável de argumentos, como no seguinte exemplo:
function allAll() { let result = 0; for( a of arguments ) result += a; return result; } let res = allAll(1,2,3,4,5); // Vale 15 |
Eis algumas funções predefinidas em JavaScript:
No caso de funções que não fazem parte de objetos, quando elas são chamadas, o argumento this fica associado a um objeto que depende da implementação. Por exemplo, no caso do Rhino e do Node.js, this fica associado a um objeto especial que guarda as ligações de todos os nomes globais. No seguinte exemplo, se a função f abaixo for chamada assim - f() - ela imprime-se a si própria de duas maneiras diferentes:
function f() { console.log(f); console.log(this.f); }No caso duma função f ser um método dum objeto obj e for chamada assim obj.f(), o argumento this fica associado ao próprio objeto (aliás, como também acontece em Java).
Todas as chamadas normais de funções são automaticamente traduzidas para invocações de call. Concretamente, a chamada duma função independente é assim traduzida, em Node.js:
f(1,2,3) -> f.call(global,1,2,3)A chamada do método dum objeto é assim traduzida:
obj.f(1,2,3) -> obj.f.call(obj,1,2,3)Cuidado com a seguinte subtileza. No código abaixo, as duas chamadas não são equivalentes. Só a primeira chamada posiciona this igual a obj.
obj.f(1,2,3); // uma chamada let my_f = obj.f; // Extrai a função f do objeto, e agora o objeto fica esquecido. my_f(1,2,3); // outra chamada, que não é equivalente (no valor de this dentro da função)Nesta outra forma, as duas chamadas já são equivalentes:
obj.f(1,2,3); // uma chamada let my_f = (a,b,c) => obj.f(a,b,c); my_f(1,2,3); // outra chamada, completamente equivalente
obj.f(1,2,3) <-> obj.f.call(obj,1,2,3) <-> obj.f.apply(obj,[1,2,3])
try { undefinedFunction(); alert('Afinal a função existe'); } catch(e) { alert('Erro: ' + e.message); } finally { alert('Chega sempre aqui, independentemente do que aconteceu antes.'); } |