Reflexão no Javascript

Dado um objeto javascript, é possível varrer seus métodos e propriedades e até mesmo serializá-lo.
Para isso devemos fazer uma conversão forçada do objeto para um vetor, e varre-lo. Cada item desse vetor pode ser um método ou uma propriedade.
O que vai dizer se ele é um método ou uma propriedade é que seu tipo, dado pela função typeof():
  • quando, como string, for igual a "function" significa que ele é um método ou função.
  • caso contrário é uma propriedade.

Código da função de serialização
   ///Transforma um objeto em string considerandp se é null ou undefined
   function ObjToString(obj)
   {
    if(obj === null)
    {
     return "null";
    }
    
    if(typeof obj === "undefined")
    {     
     return "undefined";
    }
     
    return obj.toString();
   }
  
   ///escreve as propriedades do objeto na página
   function serialize(obj)
   {
    //coleção de métodos
    var metodos = [];
    //coleção de propriedades
    var propriedades = [];
    
    //varre as propriedades
    for (i in obj) 
    {
     //verifica se não é a própria função
     if (i != "serialize")
     {
      if (typeof(obj[i]) == "function") 
      {
       //se for método adiciona em uma coleção
       metodos.push(typeof(obj[i]) + ": " + i + ": " + ObjToString(obj[i]) );
      }
      else
      {
       //senão adiciona na outra
       propriedades.push(typeof(obj[i]) + ": " + i + ": " + ObjToString(obj[i]) );
      }
     }
    }
    
    //varre as coleções escrevendo na página
    document.write("metodos
");
    for (m in metodos) 
    {
     document.write(metodos[m]+"
");
    }    
    document.write("

propriedades
");
    for (p in propriedades) 
    {
     document.write(propriedades[p]+"
");
    }    
   } 
No caso dessa função, ela simplesmente lista as propriedades e métodos e os concatena a seus devidos valores, pulando linha. Isso poderia ser feito de outra forma, por exemplo, a função de serialização poderia gerar um JSON e usá-lo em uma requisição ajax, ou interpretá-lo com eval() e gerar um clone do objeto em questão (apenas as propriedades, um DTO), ou ainda serializá-lo em XML. Por enquanto vamos listar os métodos e propriedades na página mesmo. Depois tentamos gravar em uma string ou objeto json.
Note o uso do comando for ... in. for (i in obj)
Ele equivale ao foreach do c# e de outras linguagens. Ele varre todos os elementos de um array atribuindo a I o elemento do array de indice x quando for um array, ou a propriedade quando for um ojeto (como se fosse um hashtable/dictionay). Usando esse comando, para uma varredura geral sem armazenar ou utilizar o índice, você pode fazer uma varredura geral em um array sem se preocupar com os limites inferior e superior.
A função ObjToString apenas verifica se o objeto é nulo ou undefined antes de chamar .toString() nele.
Abaixo um exemplo de serialização do objeto window:
 serialize(window);
Com a saída:
 metodos
 function: postMessage: function () { [native code] }
 function: close: function () { [native code] }
 function: blur: function () { [native code] }
 function: focus: function () { [native code] }
 function: getSelection: function getSelection() { [native code] }
 function: print: function print() { [native code] }
 function: stop: function stop() { [native code] }
 function: open: function open() { [native code] }
 function: alert: function alert() { [native code] }
 function: confirm: function confirm() { [native code] }
 function: prompt: function prompt() { [native code] }
 function: find: function find() { [native code] }
 function: scrollBy: function scrollBy() { [native code] }
 function: scrollTo: function scrollTo() { [native code] }
 function: scroll: function scroll() { [native code] }
 function: moveBy: function moveBy() { [native code] }
 function: moveTo: function moveTo() { [native code] }
 function: resizeBy: function resizeBy() { [native code] }
 function: resizeTo: function resizeTo() { [native code] }
 function: matchMedia: function matchMedia() { [native code] }
 function: getComputedStyle: function getComputedStyle() { [native code] }
 function: getMatchedCSSRules: function getMatchedCSSRules() { [native code] }
 function: webkitConvertPointFromPageToNode: function webkitConvertPointFromPageToNode() { [native code] }
 function: webkitConvertPointFromNodeToPage: function webkitConvertPointFromNodeToPage() { [native code] }
 function: requestAnimationFrame: function requestAnimationFrame() { [native code] }
 function: cancelAnimationFrame: function cancelAnimationFrame() { [native code] }
 function: webkitRequestAnimationFrame: function webkitRequestAnimationFrame() { [native code] }
 function: webkitCancelAnimationFrame: function webkitCancelAnimationFrame() { [native code] }
 function: webkitCancelRequestAnimationFrame: function webkitCancelRequestAnimationFrame() { [native code] }
 function: captureEvents: function captureEvents() { [native code] }
 function: releaseEvents: function releaseEvents() { [native code] }
 function: btoa: function btoa() { [native code] }
 function: atob: function atob() { [native code] }
 function: setTimeout: function setTimeout() { [native code] }
 function: clearTimeout: function clearTimeout() { [native code] }
 function: setInterval: function setInterval() { [native code] }
 function: clearInterval: function clearInterval() { [native code] }
 function: webkitRequestFileSystem: function webkitRequestFileSystem() { [native code] }
 function: webkitResolveLocalFileSystemURL: function webkitResolveLocalFileSystemURL() { [native code] }
 function: openDatabase: function openDatabase() { [native code] }
 function: addEventListener: function addEventListener() { [native code] }
 function: removeEventListener: function removeEventListener() { [native code] }
 function: dispatchEvent: function dispatchEvent() { [native code] }


 propriedades
 object: top
 object: window
 object: location
 object: external
 object: chrome
 object: document
 object: speechSynthesis
 object: localStorage
 object: sessionStorage
 object: applicationCache
 object: webkitStorageInfo
 object: indexedDB
 object: webkitIndexedDB
 object: crypto
 object: CSS
 object: performance
 object: console
 number: devicePixelRatio
 object: styleMedia
 object: parent
 object: opener
 object: frames
 object: self
 string: defaultstatus
 string: defaultStatus
 string: status
 string: name
 number: length
 boolean: closed
 number: pageYOffset
 number: pageXOffset
 number: scrollY
 number: scrollX
 number: screenTop
 number: screenLeft
 number: screenY
 number: screenX
 number: innerWidth
 number: innerHeight
 number: outerWidth
 number: outerHeight
 boolean: offscreenBuffering
 object: frameElement
 object: clientInformation
 object: navigator
 object: toolbar
 object: statusbar
 object: scrollbars
 object: personalbar
 object: menubar
 object: locationbar
 object: history
 object: screen
 object: onautocompleteerror
 object: onautocomplete
 object: ondeviceorientation
 object: ondevicemotion
 object: onunload
 object: onstorage
 object: onpopstate
 object: onpageshow
 object: onpagehide
 object: ononline
 object: onoffline
 object: onmessage
 object: onlanguagechange
 object: onhashchange
 object: onbeforeunload
 object: onwaiting
 object: onvolumechange
 object: ontoggle
 object: ontimeupdate
 object: onsuspend
 object: onsubmit
 object: onstalled
 object: onshow
 object: onselect
 object: onseeking
 object: onseeked
 object: onscroll
 object: onresize
 object: onreset
 object: onratechange
 object: onprogress
 object: onplaying
 object: onplay
 object: onpause
 object: onmousewheel
 object: onmouseup
 object: onmouseover
 object: onmouseout
 object: onmousemove
 object: onmouseleave
 object: onmouseenter
 object: onmousedown
 object: onloadstart
 object: onloadedmetadata
 object: onloadeddata
 object: onload
 object: onkeyup
 object: onkeypress
 object: onkeydown
 object: oninvalid
 object: oninput
 object: onfocus
 object: onerror
 object: onended
 object: onemptied
 object: ondurationchange
 object: ondrop
 object: ondragstart
 object: ondragover
 object: ondragleave
 object: ondragenter
 object: ondragend
 object: ondrag
 object: ondblclick
 object: oncuechange
 object: oncontextmenu
 object: onclose
 object: onclick
 object: onchange
 object: oncanplaythrough
 object: oncanplay
 object: oncancel
 object: onblur
 object: onabort
 object: onwheel
 object: onwebkittransitionend
 object: onwebkitanimationstart
 object: onwebkitanimationiteration
 object: onwebkitanimationend
 object: ontransitionend
 object: onsearch
 number: TEMPORARY
 number: PERSISTENT
Agora imagine um objeto json, por exemplo pessoa:
   var pessoa = {
    nome:"Vitor",
    sobrenome:"Rubio",
    email:"vitorrubio@gmail.com",
    idade:30,
    dataCadastro:"14/10/2014",
    dependente:null,
    hobby:undefined};
Com a saída:
 metodos


 propriedades
 string: nome: Vitor
 string: sobrenome: Rubio
 string: email: vitorrubio@gmail.com
 number: idade: 30
 string: dataCadastro: 14/10/2014
 object: dependente: null
 undefined: hobby: undefined
Repare também que foi necessário colocar uma excessão quanto ao método "serialize" para ele não serializar a si mesmo. Se um método for nativo do browser ele é serializado como [native code], mas se for um método seu então todo o source do método é serializado. Veja o poder disso: nas linguagens compiladas como C ou Delphi, ou nas linguagens com compilação Just In Time como C# e Java, os objetos podem ter suas propriedades e valores varridos, serializando-os para xml, json e outros formatos. Mas apenas seus campos e propriedades. NUNCA SEUS MÉTODOS. Então nas aplicações, não importa se você usa um banco de dados relacional ou orientado a objeto, você nunca poderia armazenar o código executável (métodos) no banco de dados. Apenas os dados poderiam ser armazenados. Isso significaria ter que tratar os dados com functions e stored procedures no banco de dados ou reconstituir o objeto e tratar na aplicação. Com javascript é diferente. Você pode salvar o SOURCE de métodos e argumentos em um banco de dados, trazêlos de volta a vida com eval(), JSON.parse ou jQuery.parseJSON e executar as mesmas funções que eles já possuiam. Não é possível fazer isso com JSON.parse(), apenas com eval, pois entende-se que o JSON é apenas para transferência de DADOS. Mesmo assim você pode embutir o eval como mostrado nesse link
O método abaixo faz o mesmo que o serialize, mas joga o resultado em uma string.
   ///o mesmo que serialize só que retornando uma string
   function SerializeObjToString(obj)
   {
    var metodos = [];
    var propriedades = [];
    var resultado = "";
    
    for (i in obj) 
    {
     //tirada a restrição do "serialize". Deixa trazer tudo. 
      if (typeof(obj[i]) == "function") 
      {
       metodos.push(typeof(obj[i]) + ": " + i + ": " + ObjToString(obj[i]) );
      }
      else
      {
       propriedades.push(typeof(obj[i]) + ": " + i + ": " + ObjToString(obj[i]) );
      }
    }
    
    //a string já é formatada
    resultado+="metodos
";
    for (m in metodos) 
    {
     resultado+=metodos[m]+"
";
    }    
    resultado+="

propriedades
";
    for (p in propriedades) 
    {
     resultado+=propriedades[p]+"
";
    } 

    return resultado;
   }
   
O método abaixo transforma o objeto javascript em uma string JSON.
   ///gera uma string json para ser interpretada com eval ou json.parse
   ///não é segura quanto à recursividade, referência cíclica
   function SerializeObjToJSON(obj)
   {
    var metodos = [];
    var propriedades = [];
    var resultado = "";
    
    for (i in obj) 
    {
     //tirada a restrição do "serialize". Deixa trazer tudo. 
      
      //verifica primeiro se o objeto é undefined, para retornar undefined 
      if(typeof (obj[i]) === "undefined")
      {       
       metodos.push("\""+i+"\"" + ":" + "\"undefined\"" );
      }
      //se for uma function retorna o corpo da mesma com ObjToString sem aspas
      else if (typeof(obj[i]) == "function") 
      {
       metodos.push("\""+i+"\""  + ":"  + ObjToString(obj[i]) );
      }
      //se for um número usa ObjToString para retornar sem aspas
      else if (typeof(obj[i]) == "number") 
      {
       metodos.push("\""+i+"\""  + ":" + ObjToString(obj[i]) );
      }
      //se for objeto verifica se é nulo ou não
      else if (typeof(obj[i]) == "object") 
      {
       //se for nulo simplesmente concatena null
       if(obj[i] === null)
       {
        metodos.push("\""+i+"\""  + ":" + "\"null\"" );
       }
       //senão chama a propria função e serializa recursivamente
       else
       {
        metodos.push("\""+i+"\""  + ":" + SerializeObjToJSON(obj[i]) );
       }
      }
      else
      {
       //senão assume que é string e poe entre aspas. Se tiver errado é fácil alterar. 
       propriedades.push("\""+i+"\""  + ":\"" + ObjToString(obj[i])+"\"" );
      }
    }
    
    //começa e termina entre chaves
    resultado+="{";
    for (var m = 0; m 0)
      {
       resultado+=",";
      }
     }
     else
     {
      resultado+=",";
     }
     resultado+="\r\n";
    }    
    
    for (var p = 0; p < propriedades.length; p++) 
    {
     resultado+=propriedades[p];
     
     if(p < propriedades.length-1)
     {
      resultado+=",";
     }
     
     resultado+="\r\n";
     
    }
    resultado+="}";  

    return resultado;
   }
Que pode ser transformada de volta em objeto da seguinte maneira.
    function evalJson()
    { try
     {
      //poe a string entre parenteses antes do eval      
      var txt = SerializeObjToJSON(pessoa);
      alert(txt);
      //var obj = eval("(" + txt + ")"); //para usar eval
      var obj = JSON.parse(txt); //para usar JSON.parse nativo
      //var obj = JSON && JSON.parse(txt) || $.parseJSON(txt); //para decidir de acordo com o browser se vai usar JSON nativo ou a versão do jQuery
      alert(obj.email);
     }
     catch(e)
     {
      alert(e.message);
     }
    }
Bom, por enquanto é isso. Acredito que esse é o meu maior post sobre javascript. Pretendo postar amanhã uma galeria com jQuery e dar uma organizada nesse blog.
Baixar o Exemplo
Have fun :)

Comentários

Postagens mais visitadas deste blog

Detectar o encoding de um arquivo para não corromper ao transformá-lo

erro "ora-12154: tns: não foi possível resolver o identificador de conexão especificado"

Quebras de linha no Delphi 2010