Keep on moving

あんまりまとまってないことを書きますよ

若手IT勉強会に参加してきました。(JavaScript Ninja9章コード評価)

主催のid:cimadai さんお疲れさまでした。

Secrets of the Javascript Ninja

Secrets of the Javascript Ninja

今回は[Chapter 9. Code Evaluation]を読みました。
evalといえばJavaScript: The Good PartsでBad partsにあげられてたりしますが、使いこなすと面白いことができるということが分かりましたよ。

コード評価基礎編

JavaScriptでコード評価を行うには2種類の方法があります。

eval

evalはトップレベル関数であり、引数に指定された文字列を評価します。
評価する際に今のスコープで実行されます。

var assert= function(expr,msg){
  console.log(expr?msg:"");
};

assert(eval("var t=5;")===undefined, '返り値なし');
(function(){
  eval("var t = 6;");
  assert( t === 6, '今のスコープで実行');
})();
assert( t === 5, '外のスコープの値は変更されない' );
new Function

function name(){} で関数定義できますが、new Functionでも関数の定義が可能です。

var add = new Function("a", "b", "return a + b;");
assert( add(3, 4) === 7, "Functionが定義され、実行もできる" );

evalとnew Functionの違いはスコープの違いです。下の例を見てみましょう。

var a = 1;

(function(num){
  var a = 10;

  // function literal
  var functionLiteral = function(b){return a + b;};
  console.log("functionLiteral: " + functionLiteral(num));
  // => 110

  // eval
  eval("var functionEval = function(b){return a + b;}");
  console.log("eval: " +  functionEval(num));
  // => 110

  // Function object
  var functionObject = new Function("b","return a + b;");
  console.log("new Function: " + functionObject(num));
  // => 101 <- window.aが使われる
}
)(100);
globalでevalを実行

globalでevalを実行したいときがあります。
script 要素を生成し、head 要素へ動的に追加し、削除します。
この実装はjQueryjQuery.globalEval(code) - jQuery API 1.4.4 日本語リファレンス - StackTraceでも使われてます。

<script>
function globalEval( data ) {
 data = data.replace(/^\s*|\s*$/g, "");
 if ( data ) {
  var head = document.getElementsByTagName("head")[0] || document.documentElement,
  script = document.createElement("script");
  script.type = "text/javascript";
  script.text = data; head.appendChild( script );
  head.removeChild( script );
 }
}
window.onload = function(){ (function(){
globalEval("var test = 5;"); })();
assert( test === 5, "The code is evaluated globally." ); };
</script>

ちなみにid:hagino3000さんに教えていただいたのですが、Chromeのdeveloper consoleにはバグがあって
evalで実行すると、globalスコープで実行されるそうです。
Issue 51496 - chromium - Eval does not have access to scoped variables in WebKit console - An open-source browser project to help move the web forward. - Google Project Hosting

JSON

JSON文字列を評価するときに一番使われていたのがevalを使う方法です。
いまでも一番速いのがevalを使う方法です。

var json = '{"key":"value"}';
var object = eval("(" + json + ")");
assert( object.key === "value","JSONが評価されました");

でも、他のサーバーからくるJSON文字列をそのままevalするのは危険です。
JSONの仕様作成者Douglas Crockfordのjson2.jsを使いましょう。

JavaScript Ninja内でjson2.jsが内部でどのようにコードチェックをしているかが載っています。
詳しくは @kzys さんが英文ブログで書かれてますので一読するといいと思います。
json2.js - Kato Kazuyoshi

script要素に新しいtypeを追加する

scriptタグのtype要素が"text/javascript"以外の場合実行されないことを利用して機能を追加することができます。
ページがロードされたら実行するスクリプトタグのタイプを作成する例は以下です。

<script>
window.onload = function(){
 var scripts = document.getElementsByTagName("script");
 for ( var i = 0; i < scripts.length; i++ ) {
  if ( scripts[i].type == "onload" ) {
   // You may want to use a globalEval implementation here
   eval( scripts[i].innerHTML );
  }
 }
};
</script>
<script type="onload">
ok(true, "I will only execute after the page has loaded.");
</script>

この技法はファイルの非同期読込で使われていたりします。
ControlJSはtypeを"text/cjs"とすることで非同期読み込みされるそうです。
この辺りの話は id:os0xさんがBPSTUDY#41でされていました。
http://ss-o.net/event/js20110128/

メタ言語 processing.js

Processing.jsはJohn ResigによるProcessing Visualization LanguageのJavaScriptでの実行環境です。
John Resig - Processing.js

Processing.jsはProcessingのコードを実行時にJavaScriptに変換し、evalして実行しています。
ちなみに、jsfiddle,jsdo.itでも実行可能ですよ。

私が作った例を紹介しときます。

jsdo.itはtwitterに"jsdo.itでもprocessing.jsの最新バージョン使いたい"とつぶやいたところ @kyo_ago さんがみてくれて最新バージョンに
アップデートしていただけました。 @kyo_ago さんありがとうございました。

まとめ

evalなどのコード評価はライブラリ内で使うといいことがおおいですね。
ただしリモートサーバーからきた文字列をそのまま実行するのは危険なのでこういう場合には使わないのが
幸せになれそうです。

懇親会

新宿X'ianにて行いました。ここの料理は辛くておいしいです(^^)
以上です、押忍。