quarta-feira, 15 de outubro de 2014

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 :)

Nenhum comentário:

Postar um comentário

Postagens populares

Marcadores

delphi (60) C# (31) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) .Net (9) Humor (9) javascript (9) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) Web (4) banco de dados (4) HTML (3) PHP (3) Python (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Ajax (2) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)