var_dump para JavaScript

Artigo que apresenta uma implementação em JavaScript da função var_dump do PHP, que permite visualizar de forma amigável o conteúdo de uma variável qualquer.

Acho que a função var_dump (ou similares, como print_r e debug_zval_dump) é uma das mais úteis para se depurar alguns problemas em PHP. Ela avalia uma ou mais variáveis ou valores, mostrando informações sobre elas (tipo, classe, etc). O var_dump, no entanto, não existe nativamente em JavaScript. Para prover esta solução em JavaScript, tentei implementar uma var_dump parecida com a de PHP. Como ela não consegue detectar recursividade infinita (quando um elemento aponta para algum que já foi mostrado), então criei o atributo "max_iteracoes", que pode ser configurado antes de se chamar a função. Com isso, a função previne loops infinitos.

Linguagem: JavaScript

Copyright 2011 Rubens Takiguti Ribeiro

Licença: LGPL 3 ou superior

/**
 * Versao JavaScript da funcao var_dump do PHP
 * @param mixed ... Qualquer valor
 * @return string Informacoes do valor
 */
function var_dump(/* ... */) {
    
    /**
     * Recursao do metodo var_dump
     * @param midex item Qualquer valor
     * @param int nivel Nivel de indentacao
     * @return string Informacoes do valor
     */
    this.var_dump_rec = function(item, nivel) {
        if (var_dump.max_iteracoes > 0 && var_dump.max_iteracoes < nivel) {
            return this.indentar(nivel) + "*MAX_ITERACOES(" + var_dump.max_iteracoes+ ")*\n";
        }
        if (item === null) {
            return this.indentar(nivel) + "NULL\n";
        } else if (item === undefined) {
            return this.indentar(nivel) + "undefined\n";
        }

        var str = '';
        var tipo = typeof(item);
        switch (tipo) {
        case 'object':
            var classe = this.get_classe(item);
            switch (classe) {
            case 'Array':
                str += this.indentar(nivel) + "Array(" + item.length + ") {\n";
                for (var i in item) {
                    str += this.indentar(nivel + 1) + "[" + i + "] =>\n";
                    str += this.var_dump_rec(item[i], nivel + 1);
                }
                str += this.indentar(nivel) + "}\n";
                break;

            case 'Number':
            case 'Boolean':
                str += this.indentar(nivel) + classe + "(" + item.toString() + ")\n";
                break;

            case 'String':
                str += this.indentar(nivel) + classe + "(" + item.toString().length + ") \"" + item.toString() + "\"\n";
                break;
            
            default:
                str += this.indentar(nivel) + "object(" + classe + ") {\n";
                var exibiu = false;
                for (var i in item) {
                    exibiu = true;
                    str += this.indentar(nivel + 1) + "[" + i + "] =>\n";
                    try {
                        str += this.var_dump_rec(item[i], nivel + 1);
                    } catch (e) {
                        str += this.indentar(nivel + 1) + "(Erro: " + e.message + ")\n";
                    }
                }
                if (!exibiu) {
                    str += this.indentar(nivel + 1) + "JSON(" + JSON.stringify(item) + ")\n";
                }
                str += this.indentar(nivel) + "}\n";
                break;
            }
            break;
        case 'number':
            str += this.indentar(nivel) + "number(" + item.toString() + ")\n";
            break;
        case 'string':
            str += this.indentar(nivel) + "string(" + item.length + ") \"" + item + "\"\n";
            break;
        case 'boolean':
            str += this.indentar(nivel) + "boolean(" + (item ? "true" : "false") + ")\n";
            break;
        case 'function':
            str += this.indentar(nivel) + "function {\n";
            str += this.indentar(nivel + 1) + "[code] =>\n";
            str += this.var_dump_rec(item.toString(), nivel + 1);
            str += this.indentar(nivel + 1) + "[prototype] =>\n";
            str += this.indentar(nivel + 1) + "object(prototype) {\n";
            for (var i in item.prototype) {
                str += this.indentar(nivel + 2) + "[" + i + "] =>\n";
                str += this.var_dump_rec(item.prototype[i], nivel + 2);
            }
            str += this.indentar(nivel + 1) + "}\n";

            str += this.indentar(nivel) + "}\n";
            break;
        default:
            str += this.indentar(nivel) + tipo + "(" + item + ")\n";
            break;
        }
        return str;
    };

    /**
     * Devolve o nome da classe de um objeto
     * @param Object obj Objeto a ser verificado
     * @return string Nome da classe
     */
    this.get_classe = function(obj) {
        if (obj.constructor) {
            return obj.constructor.toString().replace(/^.*function\s+([^\s]*|[^\(]*)\([^\x00]+$/, "$1");
        }
        return "Object";
    };

    /**
     * Retorna espacos para indentacao
     * @param int nivel Nivel de indentacao
     * @return string Espacos de indentacao
     */
    this.indentar = function(nivel) {
        var str = '';
        while (nivel > 0) {
            str += '  ';
            nivel--;
        }
        return str;
    };

    var str = "";
    var argv = var_dump.arguments;
    var argc = argv.length;
    for (var i = 0; i < argc; i++) {
        str += this.var_dump_rec(argv[i], 0);
    }
    return str;
}
var_dump.prototype.max_iteracoes = 0;

A forma de usar o var_dump para JavaScript é parecida com a de PHP: basta passar um ou mais valores ou variáveis para a função. A diferença é que, ao invés de a função exibir o valor (como faz o var_dump do PHP), ela retorna o valor na forma de string. Ele pode ser mostrado com uma mensagem em window.alert(retorno), ou colocado no documento HTML (dentro de uma tag <pre> para facilitar a visualização). Veja um exemplo e, em seguida, seu resultado:

JavaScript:

window.onload = function() {

    // Objeto a ser avaliado
    var obj = new Object();
    obj.indef = undefined;
    obj.vazio = null;
    obj.num1 = 3.5;
    obj.inf1 = Number.POSITIVE_INFINITY;
    obj.inf2 = Number.NEGATIVE_INFINITY;
    obj.str1 = 'teste';
    obj.b1 = true;
    obj.b2 = false;
    obj.vet1 = ['a','b'];

    obj.data = new Date();
    obj.num2 = new Number(2.5);
    obj.str2 = new String('testando');
    obj.bool1 = new Boolean(true);
    obj.bool2 = new Boolean(false);
    
    obj.vet2 = new Array();
    obj.vet2.push('a');
    obj.vet2.push(5);

    obj.f = function() { window.alert('oi'); };
    obj.f.prototype.pi = 3.14;
    obj.f.prototype.msg = 'oi';
    obj.f.prototype.func = function() { var x = 1; };

    // Obtendo informacoes de obj
    var dump = var_dump(obj);

    // Criando uma tag PRE no documento e inserindo o resultado
    var pre = document.createElement("pre");
    pre.appendChild(document.createTextNode(dump));
    document.getElementsByTagName("body").item(0).appendChild(pre);
}

Resultado:

object(Object) {
  [indef] =>
  undefined
  [vazio] =>
  NULL
  [num1] =>
  number(3.5)
  [inf1] =>
  number(Infinity)
  [inf2] =>
  number(-Infinity)
  [str1] =>
  string(5) "teste"
  [b1] =>
  boolean(true)
  [b2] =>
  boolean(false)
  [vet1] =>
  Array(2) {
    [0] =>
    string(1) "a"
    [1] =>
    string(1) "b"
  }
  [data] =>
  object(Date) {
    JSON("2011-03-12T22:10:13.716Z")
  }
  [num2] =>
  Number(2.5)
  [str2] =>
  String(8) "testando"
  [bool1] =>
  Boolean(true)
  [bool2] =>
  Boolean(false)
  [vet2] =>
  Array(2) {
    [0] =>
    string(1) "a"
    [1] =>
    number(5)
  }
  [f] =>
  function {
    [code] =>
    string(39) "function () {
    window.alert("oi");
}"
    [prototype] =>
    object(prototype) {
      [pi] =>
      number(3.14)
      [msg] =>
      string(2) "oi"
      [func] =>
      function {
        [code] =>
        string(30) "function () {
    var x = 1;
}"
        [prototype] =>
        object(prototype) {
        }
      }
    }
  }
}

Para especificar o número de recursões, basta atribuir um valor ao atributo max_recursoes da função var_dump, como no exemplo:

var_dump.max_recursoes = 3;
var x = var_dump(obj);

7 comentários