Entendendo os operadores "==" e "==="

Por Eduardo Cobuci em 10/03/2011 JavaScript

Em JavaScript os operadores "==" e "===" determinam se dois valores são iguais utilizando definições diferentes de igualdade. Quem já deu uma olhada no código do jQuery já deve ter visto que o operador "===" é bastante utilizado, e deve ter se perguntado o que ele significa e por que não utilizar o mais comum "==". As diferenças são pequenas, porém, entendê-las bem pode te salvar um bom tempo de debug.

Igualdade segundo o operador "=="

A comparação de tipos primitivos (number, string e boolean) é feita por valor, isto é, se possuírem o mesmo tipo e valor eles são considerados iguais. Se os tipos forem diferentes e um dos operandos for do tipo string ou boolean, este é convertido para number implicitamente e depois comparado. Exemplo:

"abc" == "abc"    // true
5 == 7            // false
"42" == 42        // true (conversão implícita)
1 == true         // true (conversão implícita)
"0" == false      // true (conversão implícita)
null == undefined // true

Se os operandos forem de um tipo não-primitivo (objects, arrays e functions) eles somente serão considerados iguais se referenciarem ao mesmo objeto, mesmo que eles tenham suas propriedades ou valores (no caso de arrays) iguais. Exemplo:

var point1 = { x: 10, y: 5 };
var point2 = { x: 10, y: 5 };
var point3 = point1;

point1 == point2  // false
point1 == point3  // true
[1,2] == [1,2]    // false

Igualdade segundo o operador "==="

Este operador, chamado de operador de "identidade", verifica se os operandos são idênticos, sem aplicar qualquer tipo de conversão. Exemplo:

"abc" === "abc"     // true
5 === 7             // false
"42" === 42         // false
1 === true          // false
"0" === false       // false
null === undefined  // false

Para a comparação de objetos de tipos não-primitivos o comportamento do operador "===" é o mesmo do "==".

Existe ainda uma exceção para o caso de um dos operandos ser do tipo NaN (not-a-number): qualquer valor comparado a NaN é sempre false, seja com "==" ou "===", inclusive o próprio NaN!

Conclusão

Para quem programa em outras linguagens o comportamento do operador "===" deve parecer mais intuitivo. Esse operador é certamente mais seguro e por isso é comum vê-lo em bibliotecas como jQuery. A menos que você saiba exatamente os tipos envolvidos e quais conversões serão aplicadas (o que pode ser bem complicado em JavaScript), minha recomendação é que você sempre utilize o operador "===" para evitar surpresas :)

Orientação a objetos em JavaScript

Por Eduardo Cobuci em 22/02/2011 JavaScript

Ao contrário do que muitos pensam, JavaScript permite sim orientação à objetos. Um tipo um tanto diferente é verdade (sem classes), por outro lado, permite coisas muito interessantes como mixins e outras mágicas típicas de linguagens dinâmicas. Neste artigo vou mostrar como desenvolver em JavaScript utilizando orientação à objetos e outros truques de umas das linguagens mais utilizadas do mundo.

Classes

Em JavaScript não existe o conceito de classes como em Java ou C#, entretanto, é possível criar pseudo-classes, inclusive com suporte a herança. Para criar uma “classe” em JavaScript deve-se definir uma função construtor desta forma:

function Car(m, y) {
    this.model = m;
    this.year = y;
    this.getAge = function() {
        return (new Date()).getFullYear() - this.year;
    }
}

Ao invocar esta função com o operador “new”, uma instância/objeto desta classe é criada:

var myCar = new Car("Nissan Skyline", 2008);

Diferentemente do que acontece em outras linguagens, em JavaScript a simples atribuição de um valor à uma variável já é suficiente para declará-la e iniciá-la. Note também que a palavra-chave “this” funciona de forma parecida com Java e C#, ou seja, refere-se à instância que está sendo criada. Desta forma podemos acessar as variáveis criadas pelo construtor como propriedades do objeto:

myCar.model     // "Nissan Skyline"
myCar.year      // 2008
myCar.getAge()  // 2

Em nossa classe há um método chamado "getAge", este método é na verdade uma propriedade de cada objeto criado. Isso significa que cada objeto criado irá alocar uma parte da memória para armazená-lo, podendo consumir muita memória caso muitas instâncias desta classe sejam criadas. Para resolver este problema deve-se utilizar o prototype da classe para definir seus métodos.

Prototypes

Todo objeto em JavaScript possui uma referência interna a um objeto chamado “prototype”. Toda propriedade do prototype de uma classe é também uma propriedade dos objetos desta classe, ou seja, todo objeto herda as propriedades de seu prototype. Uma melhor versão da classe “Car” ficaria assim:

function Car(m, y) {
    this.model = m;
    this.year = y;
}

Car.prototype.getAge = function() {
    return (new Date()).getFullYear() - this.year;
}

Desta forma continuamos podendo acessar o método “getAge”, porém, sem ter de alocá-lo para cada objeto. Note que a definição do método é feita depois da classe, isso demonstra que podemos adicionar membros a uma classe a qualquer momento através de seu prototype. Para determinar se uma propriedade foi definida em seu construtor ou herdada de seu prototype basta chamar a função “hasOwnProperty()” do objeto:

var c = new Car("Honda Civic", 1995);
c.hasOwnProperty("model"); // true
c.hasOwnProperty("getAge");    // false - Herdada do prototype

Propriedades e métodos estáticos

Propriedades e métodos de classe, também conhecidos como “estáticos”, são bastante utilizados em Java e C#. São membros que não estão em uma instância/objeto especifico, mas sim diretamente na classe. Um exemplo de classe que possui este tipo de propriedade/método é a classe “Math”, onde temos a propriedade “Math.PI”, que retorna a constante PI, e o método “Math.max(a, b)”, que retorna o maior dos dois argumentos. Para criar um membros estático basta atribuí-los diretamente à classe:

Painter.paint = function(o, c)
{
    o.color = c;
}

Painter.paint(car, "black");
car.color; // "black"

Herança

Como eu disse no início, em JavaScript não há o conceito de classes como em Java ou C#. Ainda assim, isso não significa que não seja possível estabelecer herança entre tipos. Suponha que agora precisamos criar um carro que armazene sua velocidade e possa ser acelerado. Para isso, vamos criar uma nova classe que extenda a classe “Car”:

function AccelerableCar(m, y, s){
     Car.call(this, m, y); // chama o construtor da classe mãe
     this.maxSpeed = s;
     this.speed = 0;
}

AccelerableCar.prototype.accelerate = function(by){
     this.speed = Math.min(this.speed + by, this.maxSpeed);
}

AccelerableCar.prototype = new Car(); // realiza a herança!
AccelerableCar.prototype.contructor = AccelerableCar; // a propriedade 'constructor' do prototype referencia a função construtor da classe

var ac = new AccelerableCar('Porshe Carrera', 2005, 350);
ac.model                     // 'Porshe Carrera'
ac.year            // 2005
ac.speed                     // 0
ac.accelerate(150);
ac.speed                     // 150

Como você pode notar, criar uma sub-classe em JavaScript não é tão simples. Primeiro, em seu construtor é necessário ter certeza que o construtor da classe mãe é chamado através do método “call”. Em JavaScript toda função possui uma “sub-função” chamada “call”, esta função executa a função original trocando o objeto interno “this” pelo rimeiro argumento. Depois, deve-se atribuir ao prototype da classe filha uma instância da classe mãe, desta forma a classe filha adquire todos os membros da classe mãe. Por fim, deve-se atribuir a propriedade “constructor” do prototype da classe filha a função construtor da mesma. Isso é importante pois ao executar esta linha “AccelerableCar.prototype = new Car()” o prototype de “AccelerableCar” é alterado para “Car” e seu construtor passa a ser o de “Car”.

Módulos, extendendo sem usar herança

Algumas linguagens de programação como, por exemplo, Ruby, dispõem de um recurso muito interessante chamado de módulo. Um módulo é bastante parecido com uma classe, pode conter métodos, propriedades e até classes, porém, não pode ser instanciado. Em contra-partida, uma classe pode “herdar” de quantos módulos quiser e, diferentemente do que acontece com interfaces, os médotos de um módulos são implementados no próprio módulo. Isso é conhecido como “mixin”, uma forma de fazer de herança múltipla, mas sem seus problemas. Em JavaScript podemos simular módulos de forma parecida como fazemos para simular classes, com a diferênça de que por convensão os métodos de um módulo são estáticos, e seu construtor emite uma exceção caso seja invocado. Abaixo um exemplo de um módulo que define um método para serializar um objeto simples em JSON:

function Serializable(){
    throw "You can’t create an object from a module!";
}

Serializable.serialize = function(o) {
    obj = o || this;
    var members = [];
    for(m in obj) {
         if(typeof obj[m] != "function")
              members.push("'" + m + "':'" + obj[m].toString() + "'");
    }
    return "{" + members.join(",") + "}";
}

Agora podemos usar este módulo em qualquer classe que quisermos:

Car.prototype.serialize = Serializable.serialize;

var myCar = new Car("Porshe Carrera", 2010);
alert(myCar.serialize()); // {'model':'Porshe Carrera','year':'2010'}

Depois do mixin a classe classe “Car” passa a ter o médoto “serialize” de “Serializable”. Você também deve ter notado que este processo de mixin “manual” se torna inviável caso o módulo possua muitos métodos. Para resolver isso, podemos criar um helper para fazer o trabalho pesado:

function Module(){}
Module.mixin(source, target)
{
    for(m in source) {
         if(typeof source[m] == "function")
              target.prototype[m] = source[m];
    }
}

Module.mixin(Serializable, Car);

Namespaces

Uma boa-prática em programação orientada à objetos é definir namespaces para organizar o código e evitar conflitos, principalmente em projetos grandes. Ainda que não exista nenhum suporte à namespaces na linguagem JavaScript, é possível utilizar objetos para tal. No exemplo abaixo uma namespace "com.game" e define a classe “Car” dentro dela:

var com = {};
com.game = {};

com.game.Car = function(m, y) { /* ... */ }

var c = new com.game.Car("Audi TT", 2002);

Conclusão

JavaScript apesar não possuir suporte nativo a conceitos tradicionais de orientação à objetos, graças à sua natureza dinâmica e flexível, permite que tais funcionalidades sejam simuladas de maneira bastante satisfatória e, em alguns casos, melhor que em linguagens orientadas à objetos tradicionais. Ainda que seja importante entender como tudo isso é possível, é importante também frisar que existem diversos frameworks JavaScript (como Ext JS, Dojo, Google Closure entre outros) que possuem helpers que facilitam em muito a criação de aplicações orientadas à objetos em JavaScript

Categorias