jQuery w/o eval()

Parantatatam

mag Cookies & Kekse
Hallo Tutorianer,

ich möchte euch mal den Stack meines Problems auflisten, damit ihr versteht, was ich vor habe: ich arbeite gerade daran, Brackets in eine Packaged App umzuwandeln. Dieser Editor basiert eigentlich nur auf HTML5, JavaScript und CSS. Dabei setzen sie beispielsweise jQuery und RequiredJS ein. Jedoch bauen dies Frameworks teilweise auf der bösen Funktion eval() auf. Ich denke, dass jeder hier weiß, warum man diese nicht verwenden sollte – Google weiß das auch. Und daher ist die Verwendung dieser Funktion nicht erlaubt, es sei denn in einer Sandbox. Letzteres ist in diesem Fall aber nicht möglich. Daher habe ich mir überlegt, wie ich eine "sichere" eval()-Funktion bauen könnte, so dass ich sie in einer Packaged App nutzen kann. Und das sieht bei mir momentan wie folgt aus:
Javascript:
(function (window) {
  // definiere Funktion seceval
  window.seceval = function (data) {
    // generiere einen Namen für die Callback-Funktion
    var evalcallback = 'eval' + (new Date).valueOf();
    // setze den Rückgabe-Status auf "false" (noch nichts zurück gegeben)
    window[evalcallback + "state"] = false;
    // definiere die Callback-Funktion
    data = [
      'window.',
      evalcallback,
      ' = function () {', "\n",
        'return (',
        data,
        ');', "\n",
      '};'
    ].join("");
    // generiere eine BLOB-Url für das Skript, was jetzt die Callback-Funktion enthält
    var url    = window.URL.createObjectURL( new Blob([ data ], {"type" : "application/javascript"}) );
    var head   = document.getElementsByTagName( "head" )[0];
    var script = document.createElement( "script" );
    script.type  = "application/javascript";
    script.src   = url;
    script.async = true;
    // führe folgendes aus, wenn das Skript vollständig geladen wurde
    script.onload = function () {
      console.log( evalcallback + " is ready" );
      console.log( data );
      // Callback-Funktion wurde nicht definiert => Fehler
      if (!window[evalcallback]) {
        window[evalcallback + "ret"] = undefined;
        window[evalcallback + "state"] = true;
      }
  
      try {
        // Callback-Funktion wurde definiert und ist syntaktisch korrekt
        window[evalcallback + "ret"] = window[evalcallback]();
        window[evalcallback + "state"] = true;
      } catch ( e ) {
        // Callback-Funktion wurde definiert und ist syntaktisch inkorrekt
        window[evalcallback + "ret"] = undefined;
        window[evalcallback + "state"] = true;
      }
      // lösche BLOB-Url
      window.URL.revokeObjectURL( url );
    };
    
    // füge Skript hinzu und führe es somit aus
    head.appendChild( script );
    // warte darauf, dass der Status sich ändert
    while ( !window[evalcallback + "state"] ) {}
    // erhalte Rückgabewert
    var ret = window[evalcallback + "ret"];
    // lösche alle temporären Eigenschaften
    delete window[evalcallback];
    delete window[evalcallback + "ret"];
    delete window[evalcallback + "state"];
    return ret;
  };
})(window);
Das Hauptproblem, was momentan besteht, ist, dass ich darauf warten muss, dass das Skript geladen wurde, und erst danach einen Rückgabewert erhalte. Es läuft also asynchron ab. Jedoch ist eval() eine synchrone Funktion, welche sofort einen Rückgabewert liefern muss. Die Frage ist nun, wie ich den Rückgabewert entgegeben nehme und ihn dann als Rückgabewert von seceval() zurück gebe.

PS: Entschuldigt, dass mein Skript so ungeordnet aussieht: ich habe jetzt erstmal alles zusammen geschrieben, was mir in den Sinn kam, im Wissen, dass ich es dann aufräume, wenn es funktioniert.
 
Ich würde behaupten, dass man das Ergebnis einer asynchronen Funktion nicht als Ergebnis einer synchronen Funktion ausgeben kann. Daher habe ich es jetzt so umgebaut, dass es rein asynchron arbeitet und den ersten script-Tag auch wieder entfernt, nach dem das Skript geladen wurde:
Javascript:
(function ( window ) [
  window.ceval = function ( data, callback ) {
    var evalcallback = 'eval' + ( new Date ).valueOf();
    data = [
      "window.",
      evalcallback,
      " = function () {", "\n",
        "return (",
        data,
        ");", "\n",
      "};"
    ].join( "" );

    var url    = window.URL.createObjectURL( new Blob( [ data ], {"type" : "application/javascript"} ) );
    var head   = document.getElementsByTagName( "head" )[0];
    var script = document.createElement( "script" );

    script.type  = "application/javascript";
    script.src   = url;
    script.async = true;

    script.onload = function() {
      if ( !window[ evalcallback ] ) {
        return;
      }
  
      try {
        var ret = window[ evalcallback ]();
        if ( callback && typeof callback === "function" ) {
          callback( ret );
        }
      } catch ( e ) {}
      
      var scripts = (function ( nodes ) {
        var tmp = [];
        var i   = 0;

        for ( ; i < nodes.length; ++i ) {
          tmp[i] = nodes[i];
        }
        return tmp;
      })( document.getElementsByTagName( "head" )[0].getElementsByTagName( "script" ) );

      var script = scripts.filter( function ( node ) {
        return ( node.src === url );
      })[0];

      document.getElementsByTagName( "head" )[0].removeChild( script );

      delete window[ evalcallback ];
      window.URL.revokeObjectURL( url );
    };

    head.appendChild( script );
  };
})( window );
 
Hi,

ich hatte mir gestern dein Problem angesehen und mir ist auch keine andere Lösung eingefallen als die Verwendung einer callback-Funktion.

Ciao
Quaese
 
Mir kam noch in den Sinn, dass man WebWorker nutzen könnte, allerdings arbeiten die auch wieder asynchron. Das Problem ist ja, dass die while-Schleife das Skript blockiert und somit nichts ausgeführt wird, auch nicht die Callback-Funktion. Und somit tritt das Abbruchkriterium nie ein. Mit WebWorkern könnte das vielleicht funktionieren, aber ich denke, dass dies zu umständlich für so ein Problem ist. Daher ist es wohl am sinnvollsten, wenn man alles andere um diese Funktion herumbaut, und nicht andersherum.
 

Neue Beiträge

Zurück