Recursos ocultos do JavaScript?

O que “resources ocultos” de JavaScript você acha que todo programador deveria saber?

Depois de ter visto a excelente qualidade das respostas às seguintes perguntas, achei que era hora de pedir JavaScript.

  • Recursos ocultos do HTML
  • Recursos ocultos do CSS
  • Recursos ocultos do PHP
  • Recursos ocultos do ASP.NET
  • Recursos ocultos do C #
  • Recursos ocultos do Java
  • Recursos ocultos do Python

Mesmo que JavaScript seja indiscutivelmente a linguagem mais importante do Client Side agora (apenas pergunte ao Google), é surpreendente o quão pouco os desenvolvedores da Web apreciam o quão poderoso ele realmente é.

Você não precisa definir nenhum parâmetro para uma function. Você pode simplesmente usar o object array de arguments da function.

 function sum() { var retval = 0; for (var i = 0, len = arguments.length; i < len; ++i) { retval += arguments[i]; } return retval; } sum(1, 2, 3) // returns 6 

Eu poderia citar a maioria do excelente livro de Douglas Crockford, JavaScript: The Good Parts .

Mas vou levar apenas um para você, sempre use === e !== invés de == e !=

 alert('' == '0'); //false alert(0 == ''); // true alert(0 =='0'); // true 

== não é transitivo. Se você usar === isso resultará em falso para todas essas instruções, conforme esperado.

Funções são cidadãos de primeira class em JavaScript:

 var passFunAndApply = function (fn,x,y,z) { return fn(x,y,z); }; var sum = function(x,y,z) { return x+y+z; }; alert( passFunAndApply(sum,3,4,5) ); // 12 

Técnicas de functional programming podem ser usadas para escrever um javascript elegante .

Particularmente, funções podem ser passadas como parâmetros, por exemplo, Array.filter () aceita um retorno de chamada:

 [1, 2, -1].filter(function(element, index, array) { return element > 0 }); // -> [1,2] 

Você também pode declarar uma function “privada” que só existe dentro do escopo de uma function específica:

 function PrintName() { var privateFunction = function() { return "Steve"; }; return privateFunction(); } 

Você pode usar o operador in para verificar se existe uma chave em um object:

 var x = 1; var y = 3; var list = {0:0, 1:0, 2:0}; x in list; //true y in list; //false 1 in list; //true y in {3:0, 4:0, 5:0}; //true 

Se você achar os literais de objects muito feios, você pode combiná-los com a dica de function sem parâmetros:

 function list() { var x = {}; for(var i=0; i < arguments.length; ++i) x[arguments[i]] = 0; return x } 5 in list(1,2,3,4,5) //true 

Atribuindo Valores Padrão às Variáveis

Você pode usar o lógico ou operador || em uma expressão de atribuição para fornecer um valor padrão:

 var a = b || c; 

A variável irá obter o valor de c apenas se b for falso (se for null , false , undefined , 0 , empty string ou NaN ), caso contrário a receberá o valor de b .

Isso geralmente é útil em funções, quando você deseja fornecer um valor padrão para um argumento no caso de não ser fornecido:

 function example(arg1) { arg1 || (arg1 = 'default value'); } 

Exemplo de fallback do IE em manipuladores de events:

 function onClick(e) { e || (e = window.event); } 

Os seguintes resources de idioma estão conosco há muito tempo, todas as implementações de JavaScript os suportam, mas eles não faziam parte da especificação até o ECMAScript 5th Edition :

A declaração do debugger

Descrito em: § 12.15 A declaração do debugger

Esta declaração permite que você coloque pontos de interrupção programaticamente em seu código apenas por:

 // ... debugger; // ... 

Se um depurador estiver presente ou ativo, ele será interrompido imediatamente, diretamente na linha.

Caso contrário, se o depurador não estiver presente ou ativo, essa instrução não terá efeito observável.

Literais de string multilinha

Descrito em: § 7.8.4 Literatura de Seqüências

 var str = "This is a \ really, really \ long line!"; 

Você tem que ter cuidado porque o caractere próximo a \ deve ser um terminador de linha, se você tiver um espaço após o \ por exemplo, o código parecerá exatamente o mesmo, mas irá gerar um SyntaxError .

JavaScript não tem escopo de bloco (mas tem fechamento, então vamos chamá-lo mesmo?).

 var x = 1; { var x = 2; } alert(x); // outputs 2 

Você pode acessar as propriedades do object com [] vez de .

Isso permite que você procure uma propriedade que corresponda a uma variável.

 obj = {a:"test"}; var propname = "a"; var b = obj[propname]; // "test" 

Você também pode usar isso para obter / definir propriedades de objects cujo nome não é um identificador legal.

 obj["class"] = "test"; // class is a reserved word; obj.class would be illegal. obj["two words"] = "test2"; // using dot operator not possible with the space. 

Algumas pessoas não sabem disso e acabam usando eval () assim, o que é uma péssima idéia :

 var propname = "a"; var a = eval("obj." + propname); 

Isso é mais difícil de ler, mais difícil de encontrar erros (não pode usar jslint), mais lento para executar e pode levar a explorações de XSS.

Se você estiver pesquisando uma referência JavaScript decente em um determinado tópico, inclua a palavra-chave “mdc” em sua consulta e seus primeiros resultados serão do Mozilla Developer Center. Eu não tenho nenhuma referência offline ou livros comigo. Eu sempre uso o truque de palavra-chave “mdc” para obter diretamente o que estou procurando. Por exemplo:

Google: javascript array sort mdc
(na maioria dos casos você pode omitir “javascript”)

Atualização: O Mozilla Developer Center foi renomeado para Mozilla Developer Network . O truque de palavra-chave “mdc” ainda funciona, mas em breve poderemos começar a usar o “mdn” .

Talvez um pouco óbvio para alguns …

Instale o Firebug e use o console.log (“olá”). Muito melhor do que usar alerta random (), que eu me lembro de fazer muito há alguns anos atrás.

Métodos Particulares

Um object pode ter methods privados.

 function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; // A private method only visible from within this constructor function calcFullName() { return firstName + " " + lastName; } // A public method available to everyone this.sayHello = function () { alert(calcFullName()); } } //Usage: var person1 = new Person("Bob", "Loblaw"); person1.sayHello(); // This fails since the method is not visible from this scope alert(person1.calcFullName()); 

Também mencionado no “Javascript: The Good Parts” de Crockford:

parseInt() é perigoso. Se você passar uma string sem informá-la da base apropriada, ela poderá retornar números inesperados. Por exemplo, parseInt('010') retorna 8, não 10. Passar uma base para parseInt faz com que funcione corretamente:

 parseInt('010') // returns 8! (in FF3) parseInt('010', 10); // returns 10 because we've informed it which base to work with. 

Funções são objects e, portanto, podem ter propriedades.

 fn = function (x) {
    // ...
 }

 fn.foo = 1;

 fn.next = function (y) {
   //
 }

Eu tenho que dizer funções auto-executáveis.

 (function() { alert("hi there");})(); 

Como o Javascript não tem escopo de bloco , você pode usar uma function auto-executável se quiser definir variables ​​locais:

 (function() { var myvar = 2; alert(myvar); })(); 

Aqui, myvar não interfere ou polui o escopo global e desaparece quando a function termina.

Sabe quantos parâmetros são esperados por uma function

 function add_nums(num1, num2, num3 ){ return num1 + num2 + num3; } add_nums.length // 3 is the number of parameters expected. 

Sabe quantos parâmetros são recebidos pela function

 function add_many_nums(){ return arguments.length; } add_many_nums(2,1,122,12,21,89); //returns 6 

Aqui estão algumas coisas interessantes:

  • Comparar NaN com qualquer coisa (mesmo NaN ) é sempre falso, isso inclui == , < e > .
  • NaN não um número, mas se você pedir o tipo, ele retorna um número.
  • Array.sort pode ter uma function de comparação e é chamado por um driver do tipo quicksort (depende da implementação).
  • A expressão regular "constantes" pode manter o estado, como a última coisa que eles combinaram.
  • Algumas versões do JavaScript permitem que você acesse $0 , $1 , $2 membros em um regex.
  • null é diferente de qualquer outra coisa. Não é nem um object, nem um booleano, nem um número, nem uma string, nem undefined . É um pouco como um "alternativo" undefined . (Nota: typeof null == "object" )
  • No contexto mais externo, this produz o object [Global] que, de outro modo, não pode ser modificado.
  • Declarar uma variável com var , em vez de confiar apenas na declaração automática da variável, dá ao tempo de execução uma chance real de otimizar o access a essa variável.
  • O with construção destruirá essas otimizações
  • Os nomes das variables ​​podem conter caracteres Unicode.
  • Expressões regulares JavaScript não são realmente regulares. Eles são baseados nos regexs de Perl, e é possível construir expressões com lookaheads que levam muito, muito tempo para serem avaliadas.
  • Blocos podem ser rotulados e usados ​​como alvos de break . Os loops podem ser rotulados e usados ​​como o destino de continue .
  • Matrizes não são esparsas. Definir o elemento 1000 de uma matriz vazia deve preenchê-lo com undefined . (depende da implementação)
  • if (new Boolean(false)) {...} executará o bloco {...}
  • O mecanismo de expressão regular do Javascript é específico da implementação: por exemplo, é possível escrever expressões regulares "não portáteis".

[atualizado um pouco em resposta a bons comentários; por favor veja os comentários]

Eu sei que estou atrasado para a festa, mas não consigo acreditar que a utilidade do operador + não tenha sido mencionada além de “converter qualquer coisa em um número”. Talvez seja assim que um recurso é bem escondido?

 // Quick hex to dec conversion: +"0xFF"; // -> 255 // Get a timestamp for now, the equivalent of `new Date().getTime()`: +new Date(); // Safer parsing than parseFloat()/parseInt() parseInt("1,000"); // -> 1, not 1000 +"1,000"; // -> NaN, much better for testing user input parseInt("010"); // -> 8, because of the octal literal prefix +"010"; // -> 10, `Number()` doesn't parse octal literals // A use case for this would be rare, but still useful in cases // for shortening something like if (someVar === null) someVar = 0; +null; // -> 0; // Boolean to integer +true; // -> 1; +false; // -> 0; // Other useful tidbits: +"1e10"; // -> 10000000000 +"1e-4"; // -> 0.0001 +"-12"; // -> -12 

Claro, você pode fazer tudo isso usando Number() , mas o operador + é muito mais bonito!

Você também pode definir um valor de retorno numérico para um object substituindo o método valueOf() do protótipo. Qualquer conversão numérica executada nesse object não resultará em NaN , mas no valor de retorno do método valueOf() :

 var rnd = { "valueOf": function () { return Math.floor(Math.random()*1000); } }; +rnd; // -> 442; +rnd; // -> 727; +rnd; // -> 718; 

” Métodos de extensão em JavaScript ” por meio da propriedade prototype.

 Array.prototype.contains = function(value) { for (var i = 0; i < this.length; i++) { if (this[i] == value) return true; } return false; } 

Isso adicionará um método contains a todos os objects Array . Você pode chamar este método usando esta syntax

 var stringArray = ["foo", "bar", "foobar"]; stringArray.contains("foobar"); 

Para remover corretamente uma propriedade de um object, você deve excluir a propriedade em vez de apenas defini-la como indefinida :

 var obj = { prop1: 42, prop2: 43 }; obj.prop2 = undefined; for (var key in obj) { ... 

A propriedade prop2 ainda fará parte da iteração. Se você quiser se livrar completamente do prop2 , você deve fazer:

 delete obj.prop2; 

A propriedade prop2 não fará mais uma aparição quando você estiver percorrendo as propriedades.

with .

É raramente usado e, francamente, raramente é útil … Mas, em circunstâncias limitadas, ele tem seus usos.

Por exemplo: literais de object são bastante úteis para configurar rapidamente propriedades em um novo object. Mas e se você precisar alterar metade das propriedades em um object existente?

 var user = { fname: 'Rocket', mname: 'Aloysus', lname: 'Squirrel', city: 'Fresno', state: 'California' }; // ... with (user) { mname = 'J'; city = 'Frostbite Falls'; state = 'Minnesota'; } 

Alan Storm aponta que isso pode ser um pouco perigoso: se o object usado como contexto não tiver uma das propriedades sendo atribuída, ele será resolvido no escopo externo, possivelmente criando ou substituindo uma variável global. Isso é especialmente perigoso se você estiver acostumado a escrever código para trabalhar com objects em que as propriedades com valores padrão ou vazios são deixadas indefinidas:

 var user = { fname: "John", // mname definition skipped - no middle name lname: "Doe" }; with (user) { mname = "Q"; // creates / modifies global variable "mname" } 

Portanto, é provavelmente uma boa ideia evitar o uso da instrução with para essa atribuição.

Veja também: Há usos legítimos para a instrução “with” do JavaScript?

Métodos (ou funções) podem ser chamados em objects que não são do tipo com o qual foram projetados para trabalhar. Isso é ótimo para chamar methods nativos (rápidos) em objects personalizados.

 var listNodes = document.getElementsByTagName('a'); listNodes.sort(function(a, b){ ... }); 

Esse código falha porque listNodes não é uma Array

 Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]); 

Esse código funciona porque listNodes define propriedades parecidas com uma matriz (length, operador []) para serem usadas por sort() .

Prototypal inheritance (popularizada por Douglas Crockford) revoluciona completamente a maneira como você pensa sobre um monte de coisas em JavaScript.

 Object.beget = (function(Function){ return function(Object){ Function.prototype = Object; return new Function; } })(function(){}); 

É um assassino! Pena que quase ninguém usa isso.

Ele permite “gerar” novas instâncias de qualquer object, estendê-las e, ao mesmo tempo, manter um link de inheritance protótipo (ao vivo) para suas outras propriedades. Exemplo:

 var A = { foo : 'greetings' }; var B = Object.beget(A); alert(B.foo); // 'greetings' // changes and additionns to A are reflected in B A.foo = 'hello'; alert(B.foo); // 'hello' A.bar = 'world'; alert(B.bar); // 'world' // ...but not the other way around B.foo = 'wazzap'; alert(A.foo); // 'hello' B.bar = 'universe'; alert(A.bar); // 'world' 

Some would call this a matter of taste, but:

 aWizz = wizz || "default"; // same as: if (wizz) { aWizz = wizz; } else { aWizz = "default"; } 

The trinary operator can be chained to act like Scheme’s (cond …):

 (cond (predicate (action ...)) (predicate2 (action2 ...)) (#t default )) 

can be written as…

 predicate ? action( ... ) : predicate2 ? action2( ... ) : default; 

This is very “functional”, as it branches your code without side effects. Então, ao invés de:

 if (predicate) { foo = "one"; } else if (predicate2) { foo = "two"; } else { foo = "default"; } 

You can write:

 foo = predicate ? "one" : predicate2 ? "two" : "default"; 

Works nice with recursion, too 🙂

Numbers are also objects. So you can do cool stuff like:

 // convert to base 2 (5).toString(2) // returns "101" // provide built in iteration Number.prototype.times = function(funct){ if(typeof funct === 'function') { for(var i = 0;i < Math.floor(this);i++) { funct(i); } } return this; } (5).times(function(i){ string += i+" "; }); // string now equals "0 1 2 3 4 " var x = 1000; x.times(function(i){ document.body.innerHTML += '

paragraph #'+i+'

'; }); // adds 1000 parapraphs to the document

How about closures in JavaScript (similar to anonymous methods in C# v2.0+). You can create a function that creates a function or “expression”.

Example of closures :

 //Takes a function that filters numbers and calls the function on //it to build up a list of numbers that satisfy the function. function filter(filterFunction, numbers) { var filteredNumbers = []; for (var index = 0; index < numbers.length; index++) { if (filterFunction(numbers[index]) == true) { filteredNumbers.push(numbers[index]); } } return filteredNumbers; } //Creates a function (closure) that will remember the value "lowerBound" //that gets passed in and keep a copy of it. function buildGreaterThanFunction(lowerBound) { return function (numberToCheck) { return (numberToCheck > lowerBound) ? true : false; }; } var numbers = [1, 15, 20, 4, 11, 9, 77, 102, 6]; var greaterThan7 = buildGreaterThanFunction(7); var greaterThan15 = buildGreaterThanFunction(15); numbers = filter(greaterThan7, numbers); alert('Greater Than 7: ' + numbers); numbers = filter(greaterThan15, numbers); alert('Greater Than 15: ' + numbers); 

You can also extend (inherit) classs and override properties/methods using the prototype chain spoon16 alluded to.

In the following example we create a class Pet and define some properties. We also override the .toString() method inherited from Object.

After this we create a Dog class which extends Pet and overrides the .toString() method again changing it’s behavior (polymorphism). In addition we add some other properties to the child class.

After this we check the inheritance chain to show off that Dog is still of type Dog, of type Pet, and of type Object.

 // Defines a Pet class constructor function Pet(name) { this.getName = function() { return name; }; this.setName = function(newName) { name = newName; }; } // Adds the Pet.toString() function for all Pet objects Pet.prototype.toString = function() { return 'This pets name is: ' + this.getName(); }; // end of class Pet // Define Dog class constructor (Dog : Pet) function Dog(name, breed) { // think Dog : base(name) Pet.call(this, name); this.getBreed = function() { return breed; }; } // this makes Dog.prototype inherit from Pet.prototype Dog.prototype = new Pet(); // Currently Pet.prototype.constructor // points to Pet. We want our Dog instances' // constructor to point to Dog. Dog.prototype.constructor = Dog; // Now we override Pet.prototype.toString Dog.prototype.toString = function() { return 'This dogs name is: ' + this.getName() + ', and its breed is: ' + this.getBreed(); }; // end of class Dog var parrotty = new Pet('Parrotty the Parrot'); var dog = new Dog('Buddy', 'Great Dane'); // test the new toString() alert(parrotty); alert(dog); // Testing instanceof (similar to the `is` operator) alert('Is dog instance of Dog? ' + (dog instanceof Dog)); //true alert('Is dog instance of Pet? ' + (dog instanceof Pet)); //true alert('Is dog instance of Object? ' + (dog instanceof Object)); //true 

Both answers to this question were codes modified from a great MSDN article by Ray Djajadinata.

You may catch exceptions depending on their type. Quoted from MDC :

 try { myroutine(); // may throw three exceptions } catch (e if e instanceof TypeError) { // statements to handle TypeError exceptions } catch (e if e instanceof RangeError) { // statements to handle RangeError exceptions } catch (e if e instanceof EvalError) { // statements to handle EvalError exceptions } catch (e) { // statements to handle any unspecified exceptions logMyErrors(e); // pass exception object to error handler } 

NOTE: Conditional catch clauses are a Netscape (and hence Mozilla/Firefox) extension that is not part of the ECMAScript specification and hence cannot be relied upon except on particular browsers.

Off the top of my head…

Funções

arguments.callee refers to the function that hosts the “arguments” variable, so it can be used to recurse anonymous functions:

 var recurse = function() { if (condition) arguments.callee(); //calls recurse() again } 

That’s useful if you want to do something like this:

 //do something to all array items within an array recursively myArray.forEach(function(item) { if (item instanceof Array) item.forEach(arguments.callee) else {/*...*/} }) 

Objetos

An interesting thing about object members: they can have any string as their names:

 //these are normal object members var obj = { a : function() {}, b : function() {} } //but we can do this too var rules = { ".layout .widget" : function(element) {}, "a[href]" : function(element) {} } /* this snippet searches the page for elements that match the CSS selectors and applies the respective function to them: */ for (var item in rules) { var elements = document.querySelectorAll(rules[item]); for (var e, i = 0; e = elements[i++];) rules[item](e); } 

Cordas

String.split can take regular expressions as parameters:

 "hello world with spaces".split(/\s+/g); //returns an array: ["hello", "world", "with", "spaces"] 

String.replace can take a regular expression as a search parameter and a function as a replacement parameter:

 var i = 1; "foo bar baz ".replace(/\s+/g, function() {return i++}); //returns "foo1bar2baz3" 

You can use objects instead of switches most of the time.

 function getInnerText(o){ return o === null? null : { string: o, array: o.map(getInnerText).join(""), object:getInnerText(o["childNodes"]) }[typeis(o)]; } 

Update: if you’re concerned about the cases evaluating in advance being inefficient (why are you worried about efficiency this early on in the design of the program??) then you can do something like this:

 function getInnerText(o){ return o === null? null : { string: function() { return o;}, array: function() { return o.map(getInnerText).join(""); }, object: function () { return getInnerText(o["childNodes"]; ) } }[typeis(o)](); } 

This is more onerous to type (or read) than either a switch or an object, but it preserves the benefits of using an object instead of a switch, detailed in the comments section below. This style also makes it more straightforward to spin this out into a proper “class” once it grows up enough.

update2: with proposed syntax extensions for ES.next, this becomes

 let getInnerText = o -> ({ string: o -> o, array: o -> o.map(getInnerText).join(""), object: o -> getInnerText(o["childNodes"]) }[ typeis o ] || (->null) )(o); 

Be sure to use the hasOwnProperty method when iterating through an object’s properties:

 for (p in anObject) { if (anObject.hasOwnProperty(p)) { //Do stuff with p here } } 

This is done so that you will only access the direct properties of anObject , and not use the properties that are down the prototype chain.

Private variables with a Public Interface

It uses a neat little trick with a self-calling function definition. Everything inside the object which is returned is available in the public interface, while everything else is private.

 var test = function () { //private members var x = 1; var y = function () { return x * 2; }; //public interface return { setx : function (newx) { x = newx; }, gety : function () { return y(); } } }(); assert(undefined == test.x); assert(undefined == test.y); assert(2 == test.gety()); test.setx(5); assert(10 == test.gety()); 
Intereting Posts