タグ「javascript」が付けられているもの

[javascript] Q. 関数とは?

A.
プログラムを書いていると同じような処理を何回も記述しなければ
いけない場面に遭遇する事があります。

同じような処理を数カ所に分散して記述した場合
修正を行いたい時、分散しているすべての箇所を直す必要があります。
この場合は多くの作業時間がかかってしまいます。
また一箇所だけ修正忘れてしまったりすると思いがけないエラーの原因にもなります。

JavaScriptでは 与えられたパラメータを処理しその結果を返す仕組みである
関数が実装されています。

関数を使う事で、同じような処理を1箇所にまとめて記述する事が可能になります。

実際に関数を作成する方法を確認してみましょう。

関数を作成する方法は大きく3つあります。

1. function命令で作成する
2. 関数リテラルで作成する
3. Functionオブジェクトで作成する

それぞれの書式を確認してみましょう。

1. function命令で作成する
function 関数名(引数1, 引数2, ・・・){
  関数内で実行する命令文を記述
  return 戻り値; 
}
2. Functionオブジェクトで作成する
var 変数名 = new Function(引数1, 引数2,・・・, 関数の本体 );
3. 関数数リテラルで作成する
var 変数名 = function(引数1, 引数2,・・・) {
  return 戻り値; 
};
このfunctionの後に関数名がない関数を"匿名関数"と呼びます。

引数とは
関数を実行する際に渡す値を引数と呼びます。
引数はカンマ(,)区切りで複数渡すことが可能です。
関数名(引数1, 引数2, 引数3)
引数がない場合は関数名()のように指定します。()の省略はできません。

戻り値とは
関数を実行した結果を返す値を戻り値と呼びます。
return 戻り値の形式で指定します。
引数と違い戻り値には値をひとつしか指定できません。
returnを実行すると関数内のそれ以降の命令は呼び出されないので注意が必要です。
戻り値が必要ない場合はreturnの省略が可能です。
その場合はundefinedをデフォルトで返します。

ここで実際に関数を作ってみましょう。
商品の消費税を計算する関数を作ってみます。

引数に商品の単価(変数: piece)と商品の個数(変数: unit)を渡すと
消費税率(1.05) を掛けた値を返却するというものです。
function totalprice(商品の単価, 商品の個数){
  return 商品の単価 * 商品の個数 * 消費税率;
}

function命令で作成
// 関数を作成
function totalprice(price, unit){
  return price * unit * 1.05;
}
// 関数を使う
var sum = totalprice(1000, 5); // 5250
Functionコンストラクタで作成
// 関数を作成
var totalprice = new Function('price', 'unit', 'return price * unit * 1.05;');
// 関数を使う
var sum = totalprice(1000, 5); // 5250
関数数リテラルで作成
// 関数を作成
var totalprice = function(price, unit){
  return price * unit * 1.05;
};
// 関数を使う
var sum = totalprice(1000, 5); // 5250


関数を利用するメリット
関数を使うと計算の処理を一箇所にまとめられます
// 関数を作成
var totalprice = function(price, unit){
  return price * unit * 1.05;
};
// 関数を使う
console.log( totalprice(1000, 5) ); // 5250
console.log( totalprice(200, 3) ); // 630
console.log( totalprice(800, 2) ); // 1680
消費税率を一箇所変更すると全てに反映されます
// 関数を作成
var totalprice = function(price, unit){
  return price * unit * 1.2;
};
// 関数を使う
console.log( totalprice(1000, 5) ); // 6000
console.log( totalprice(200, 3) ); // 720
console.log( totalprice(800, 2) ); // 1920

3つの作成の方法を見てきましたが厳密にはいくつかの違いがあります。
ここからは作成方法による違いを確認してみましょう。

a. 関数の定義と実行順序
関数の実行を先に、関数の定義を後に記述してみます。
// 関数を使う
var sum = totalprice(1000, 5); // 5250

// 関数を作成
function totalprice(price, unit){
  return price * unit * 1.05;
}
これは正常に実行されます。
functionはプログラムの実行を開始する前の解析段階で関数の登録を行います。
この事を"静的な宣言"と言います。
function 関数名で関数を定義した場合 定義の場所に関係なく呼べます。

関数リテラルの場合ではどうでしょう。
// 関数を使う
var sum = totalprice(1000, 5); // 5250

// 関数を作成
var totalprice = function(price, unit){
  return price * unit * 1.05;
};
この場合は totalpriceがundefinedというエラーが出て実行できません。

b. Functionコンストラクタで作成した場合スコープ
Functionコンストラクタで関数を作成する場合グローバル変数が使われます。
以下の例を確認してみましょう。
var a = 'global';
function makefunc(){
  var a = 'local';
  var func = new Function('return a');
  console.log(func()); // global
}
makefunc();
ローカル変数で a = 'local' を指定しているのに関わらず、グローバル変数を参照しています。
この動作には注意をした方がよいでしょう。
A.
デフォルトのイベントを停止したい場合は
以下のように指定します。
// addEventListenerの時
evt.preventDefault();

// attachEventの時
evt.returnValue = false;

// 埋め込み時
return false;

実際に確認してみましょう。
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">
  function addListener(element, type, handler){
    if(document.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (document.attachEvent) {
      element.attachEvent('on' + type, function(evt){
        handler.call(element, evt);
      });
    }
  }

  function prevent(evt) {
    if(evt.preventDefault) {
      evt.preventDefault();
    } else if(evt) {
      evt.returnValue = false;
    } else if(window.event.cancelBubble){
      window.event.returnValue = false;
    }
  }

  addListener(window, 'load', function() {
    // フォームのsubmitのデフォルト動作を中止する
    addListener(document.getElementById('registform'), 'submit', function(event){
      prevent(event);
    });

    // 右メニューを禁止する
    addListener(document, 'contextmenu', function(event){
      prevent(event);
    });

  });
  </script>
</head>
<body>
<form id="registform" name="registform" action="1.html">
  <input type="text" name="text">
  <input type="submit" value="regist">
</form>
<form id="registform2" name="registform2" action="2.html" onsubmit="alert('TEST'); return false;">
  <input type="text" name="text">
  <input type="submit" value="regist">
</form>
</body>
</html>
フォームの送信前にバリデーションチェックを行ったり
web上などのゲームなどで右クリックを別のコンテクストメニュー
ではなく操作ボタンに対応させたり、独自なメニューを表示したいなどに利用できます。

[javascript] Q. イベントバブリングとは?

A.
イベントのリスナーが入れ子になってる場合
親要素からイベントが伝わるか子要素から伝わるかの指定ができます。

js_event_b1.png
// 書式
// DOMイベント
対象オブジェクト.addEventListener(イベント名, 実行したい関数, キャプチャフェーズ);
// 第3引数がfalseの場合はバブリングフェーズ, trueの場合はキャプチャフェーズ

実際に確認してみましょう。
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">
  document.addEventListener('DOMContentLoaded', function() {
    document.getElementById('a_div').addEventListener('click', function(event){
      console.log('a:outer');
    }, false);
    document.getElementById('b_div').addEventListener('click', function(event){
      console.log('b:middle');
    }, fale);
    document.getElementById('c_div').addEventListener('click', function(event){
      console.log('c:inner');
    }, false);
  }, false);
  </script>
  <style type="text/css">
    div { border: 1px solid #F00; }
    #a_div { width:150px; height:150px; }
    #b_div { width:100px; height:100px; }
    #c_div { width:50px; height:50px; }
  </style>
</head>
<body>
<div id="a_div">
  a:outer
  <div id="b_div">
    b:middele
    <div id="c_div">c:inner</div>
  </div>
</div>
</body>
</html>

第三引数がfalseの場合(バブリング) cをクリック
js_event_b2.png

第三引数がtrueの場合(キャプチャ) cをクリック
js_event_b3.png

キャプチャ、バブリングに関わらず、
いずれの場合も入れ子にイベントが設定されている場合
すべてのイベント発生してしまいます。

バブリングの場合、クリックした要素からイベントが
始まるのでその時点で停止する事でイベントが発生した
要素のみのイベントを取得できます。

イベントのバブリングを停止する
// IE8以前
window.event.cancelBubble = true;
event.cancelBubble = true;

// Safari
event.stopPropagation();


event.stopPropagationを入れてみます
  document.addEventListener('DOMContentLoaded', function() {
    document.getElementById('a_div').addEventListener('click', function(event){
      console.log('a:outer');
      event.stopPropagation();
    }, false);
    document.getElementById('b_div').addEventListener('click', function(event){
      console.log('b:middle');
      event.stopPropagation();
    }, false);
    document.getElementById('c_div').addEventListener('click', function(event){
      console.log('c:inner');
      event.stopPropagation();
    }, false);
  }, false);

バブリングが停止している cをクリック
js_event_b4.png

ここでクロスブラウザでの動作を確認します。
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">
  function addListener(element, type, handler){
    if(document.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (document.attachEvent) {
      element.attachEvent('on' + type, function(evt){
        handler.call(element, evt);
      });
    }
  }

  function stopBubble(evt) {
    if(evt.stopPropagation) {
      evt.stopPropagation();
    } else if(evt) {
      evt.cancelBubble = true;
    } else if(window.event.cancelBubble){
      window.event.cancelBubble = true;
    }
  }

  addListener(window, 'load', function() {
    addListener(document.getElementById('a_div'), 'click', function(event){
      console.log('a:outer');
      stopBubble(event);
    });
    addListener(document.getElementById('b_div'), 'click', function(event){
      console.log('b:middle');
      stopBubble(event);
    });
    addListener(document.getElementById('c_div') ,'click', function(event){
      console.log('c:inner');
      stopBubble(event);
    });
  });
  </script>
  <style type="text/css">
    div { border: 1px solid #F00; }
    #a_div { width:150px; height:150px; }
    #b_div { width:100px; height:100px; }
    #c_div { width:50px; height:50px; }
  </style>
</head>
<body>
<div id="a_div">
  a:outer
  <div id="b_div">
    b:middele
    <div id="c_div">c:inner</div>
  </div>
</div>
</body>
</html>

IE8で確認してみます。
js_event_b5.png

[javascript] Q. ブラウザのイベント

A. ブラウザで動くJavaScriptについて考えてみましょう。
ブラウザでJavaScriptを使う場合どのような目的で利用するでしょうか?

1. マウスのカーソルが商品画像の上にきたら詳細を表示する
2. 入力フォームで同意をチェックしないと登録が行えない
3. ユーザ登録で既にニックネームが利用されていないか入力とリアルタイムに確認する

などがあるとおもいます。

これらの機能を実現する為には、ユーザーがマウスやキーボードを操作を行った時に
JavaScript側で把握する必要があります。

このように "ボタンがクリックされた" "画像の上にマウスがフォーカスされた"など
イベントに対応してプログラムを記述する事を "イベント駆動プログラミング" と言います。

ここでブラウザで取得できるイベントについて確認してみましょう。

読み込みに関するイベント
イベント名 発生するタイミング
abort 読み込みが中断された時
load 読み込みが完了した時
unload 他のページに遷移する時

window操作に関するイベント
イベント名 発生するタイミング
resize ウインドウサイズが変更された時
scroll スクロールされた時

マウスの操作に関するイベント
イベント名 発生するタイミング
click マウスがクリックされた時
dbclick マウスがダブルクリックされた時
mousedown マウスボタンが押された時
mousemove マウスポインタが移動したた時
mouseout 要素からマウスポインタが離れた時
mouseover 要素にマウスポインタが重なった時
mouseup マウスのボタンが話された時

キーボードの操作に関するイベント
イベント名 発生するタイミング
keydown キーが押された時
keypress キーが押されて放された時
keyup キーが放された時

フォームに関するイベント
イベント名 発生するタイミング
submit フォームの送信ボタンが押された時
reset リセットボタンが押された時

フォーム要素に関するイベント
イベント名 発生するタイミング
change 値の変更時
blur フォーカスが外れた時
focus フォーカスされた時

基本的なイベントには上記に示したものになります。
具体的にJavaScriptでこれらのイベントを取り使う方法についてみてゆきましょう。

JavaScriptのイベントの取り扱い方法は大きく分けて2つの方法があります。

1. レガシー形式(onイベント形式)
2. DOM Level2,3イベント形式

1のレガシー形式のイベントというのは、イベントの取り扱いに関して
共通な仕様が制定される以前からある様々なブラウザが独自に実装している
ブラウザイベントです。書式がonイベント名で記述する事からonイベント形式
と呼ばれる事もあります。

タグの属性としてHTML内に記述する方法
// 書式
<タグ onイベント名="実行させたい処理">

// 例
<script type="text/javascript">
function init() {
  alert('読み込みが完了しました');
}
</script>
<body onload="init();">
<input id="btn" type="button" onclick="alert('ボタンが押されました');" value="実行">
JavaScript内でプロパティとして記述する方法
// 書式
HTML要素のオブジェクト.onイベント名 = function(){ 実装させたい処理 }

// 例
// ページが読み込まれた時のイベントを設定
// (ボタンにイベントを実装する際には設定する前にボタンが読み込まれている必要がある)
window.onload = function(){
  // ボタンが押された時のイベントを設定
  document.getElementById('btn').onclick = function(){
    alert('ボタンが押されました');
  };
  alert('読み込みが完了しました');
};
</head>
<body>
<input id="btn" type="button" value="実行">
2のDOM Levels2,3イベントというのは、
レガシー形式のイベントの欠点の克服と標準化された実装によるクロスブラウザでの
実装がより明確にかけるように制定されました。
しかしながらIE8まではDOM Levels2,3は実装されていない為
実際にクロスブラウザでDOM Levels2,3イベントを利用する為には
IE8までに実装されているIE独自の仕様であるattachEventをIE8までのIEとそれ以外の
ブラウザで使い分けて実装を行う必要があります。

こうした理由からここでは DOM Levels2,3イベントである "addEventLitener" の実装と
IE8までの独自の実装である "attachEvent" の実装を両方を確認してゆきましょう。

イベントを取得するには、イベントを取得したいオブジェクトに対して
イベントリスナーを登録します。
イベントを登録
// 書式
// DOMイベント
対象オブジェクト.addEventListener(イベント名, 実行したい関数, キャプチャフェーズ);
// IE8以下のイベント
対象オブジェクト.attachEvent('on' + イベント名, 実行したい関数 );

// ページが読み込まれた時のイベントを設定
// (ボタンにイベントを実装する際には設定する前にボタンが読み込まれている必要がある)
window.onload = function(){
  // ボタンが押された時のイベントを設定
  // DOM
  // 第3引数は false だとattachEventと同じバブリングフェーズに設定される
  document.getElementById('btn').addEventListener('click', function(){
    alert('ボタンが押されました');
  }, false);

  // IE8以下
  document.getElementById('btn').attachEvent('click', function(){
    alert('ボタンが押されました');
  });
};
</head>
<body>
<input id="btn" type="button" value="実行">

イベントを削除
// DOMイベント
対象オブジェクト.removeEventListener(イベント名, 実行したい関数, キャプチャフェーズ);
// IE8以下のイベント
対象オブジェクト.detachEvent('on' + イベント名, 実行したい関数 );

ここまでは、イベントリスナーの登録と削除をみてきました。
次にイベントを受け取る側の関数についてみてみましょう。

addEventListenerでのEventの受け取り
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">

  // イベントリスナーで実行したい関数
  var log = function(event){ console.log(event) };

  document.addEventListener('DOMContentLoaded', function() {

    // ボタンをクリックした際に呼ばれる
    document.getElementById('a_btn').addEventListener('click', log, false);

    // ボタンをクリックした際に呼ばれる
    document.getElementById('c_btn').onclick = log;

  }, false);
  </script>
</head>
<body>
<input id="a_btn" type="button" value="1:イベントリスナー">
<input id="b_btn" type="button" value="2:イベントハンドラ(属性)" onclick="log(event)">
<input id="c_btn" type="button" value="3:イベントハンドラ">
</body>
</html>

イベントリスナーで呼ばれる関数の第一引数にEventオブジェクトが渡ってきます。
実際にどのような値が渡ってくるか確認してみましょう。

Eventオブジェクト
js_event_safari_log.png

targetオブジェクトの内容

このEventオブジェクトからどの要素(target)からどのようなイベント(type)が
発生したか確認できます。

IE8以下で確認してみましょう、イベントハンドラで実装した場合
attachEventで呼ばれる関数には引数は渡されません。
window.eventプロパティからイベントを取得します。
それでは実際に確認してみましょう。

attachEventでのEventの受け取り
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">

  // イベントリスナーで実行したい関数
  var log = function(evt){
              // evtは2,3の時はundefinedになります
              console.log('ここから-------');
              console.log(evt);
              console.log(window.event);
              if(!evt) evt = window.event;
		console.log( evt.srcElement.value );
              };

    attachEvent('onload', function() {

    // ボタンをクリックした際に呼ばれる
    document.getElementById('a_btn').attachEvent('onclick', log);

    // レガシーイベントリスナー
    document.getElementById('c_btn').onclick = log;

  });
  </script>
</head>
<body>
<input id="a_btn" type="button" value="1:イベントリスナー">
<input id="b_btn" type="button" value="2:イベントハンドラ(属性)" onclick="log(event)">
<input id="c_btn" type="button" value="3:イベントハンドラ">
</body>
</html>

window.eventオブジェクト
js_event_ie8_log2.png

srcElementオブジェクト

コンソールログ
js_event_ie8_log.png

このEventオブジェクトの要素(srcElement)から
どのようなイベント(type)が発生したか確認できます。

addEventListenerとattachEventの違い
1. 第一引数のイベント名の違い on' + イベント名がattachEvent
ex) click / onclick
2. attachEventではキャプチャフェーズがバブリングフェーズのみなので
addEventListenerの第3引数はfalseだと同等になる

3. イベントを受け取る関数では、addEventListenerの場合は
第一引数にEventオブジェクトが渡される、IEの場合はattachEvent以外の場合Eventオブジェクトの代わりにwindow.eventを確認する。
クロスブラウザに対応したイベントリスナーを作成してみます。
  // イベントリスナの共通化
  function addListener(element, type, handler){
    if(document.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (document.attachEvent) {
      element.attachEvent('on' + type, function(evt){
        handler.call(element, evt);
      });
    }
  }
  // ハンドラ受け取りの共通化
  function handler(event) {
    if(!event) event = window.event;

実際に共通化した関数を使ってみます
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>event</title>
  <script type="text/javascript">

  function addListener(element, type, handler){
    if(document.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (document.attachEvent) {
      element.attachEvent('on' + type, function(evt){
        handler.call(element, evt);
      });
    }
  }

  // イベントリスナーで実行したい関数
  var log = function(evt){
              if(!evt) evt = window.event;
            };

    addListener(window, 'load', function() {
      // ボタンをクリックした際に呼ばれる
      addListener(document.getElementById('a_btn'), 'click', log);
    });
  </script>
</head>
<body>
<input id="a_btn" type="button" value="1:イベントリスナー">
</body>
</html>


JavaScriptを書く場合クロスブラウザの対応を
意識しなければいけませんが違いを覚えてしまえばよいでしょう。
またjQueryなどのフレームワークを利用すれば、
このようなクロスブラウザの違いをフレームワーク側で吸収してくれます。
A.
XML HTTP Request Level2では、異なるドメインに接続し通信する事ができます。

ブラウザからXHR2リクエストではリクエストヘッダーに
headers: 
   { host: 'dev.yoshimax.net:10001'
   , origin: 'http://localhost'
   , 'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4'
   , referer: 'http://localhost/~max/sandbox/js/testcode/14.html'
   , 'cache-control': 'max-age=0'
   , accept: '*/*'
   , 'accept-language': 'ja-jp'
   , 'accept-encoding': 'gzip, deflate'
   , connection: 'keep-alive'
   }
originが入ってきます。

こちらの判断を行い応答ヘッダーのAccess-Control-Allow-Origin
にoriginのドメインを入れて返却します。
'Access-Control-Allow-Origin' : 'ORIGINのドメインもしくは*'
応答ヘッダーにAccess-Control-Allow-Originを入れないと
js_xhr2_1.png

クライアントで読み込みが拒否されます。

ここで実際のコードを見てみましょう。
クライアントのJavaScript
document.addEventListener("DOMContentLoaded", function(){
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "http://dev.yoshimax.net:10001/events");
  xhr.send();

  xhr.onerror =	function(e) {
    console.log("ERROR");
    console.log(e);
  }

  xhr.onload = function(e) {
    console.log("LOAD");
    console.log(e);
    console.log(xhr.status);
    console.log(xhr.statusText);
    console.log(xhr.responseText);
  }
}, false);

サーバ側のソース(node.js)
テストの為 *(ワイルドカード)を設定しています。
var http = require('http');

http.createServer(function(req, res) {
    console.log(req);
    if (req.url == '/events') {
        res.writeHead(200, {
            'Content-Type'   : 'text/plain',
            'Access-Control-Allow-Origin' : '*'
        });
        res.write('TEST', 'utf8');
        res.end();
    }
}).listen(10001, '0.0.0.0');

XHR2でリクエストに使えるメソッドは、
W3C XMLHttpRequest Level 2 request

XHR2でリクエストに使えるメソッドは、
W3C XMLHttpRequest Level 2 response

で確認できますので、一度目を通しておくと良いと思います。

実行結果は以下のようになります。
js_xhr2_2.png

PUTやDELETEなどを使う場合はpreflightという仕組みを利用します。

以下のようにPUTにすると
xhr.open("PUT", "http://dev.yoshimax.net:10001/events");

PUTのリクエストヘッダーを送信する前にmethod OPTIONの
リクエストヘッダーをサーバーに送信しPUTが利用できるかまず確認にきます。
, headers: 
   { host: 'dev.yoshimax.net:10001'
   , 'access-control-request-method': 'PUT'
   , origin: 'http://localhost'
   , 'cache-control': 'max-age=0'
   , 'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4'
   , referer: 'http://localhost/~max/sandbox/js/testcode/15.html'
   , accept: '*/*'
   , 'accept-language': 'ja-jp'
   , 'accept-encoding': 'gzip, deflate'
   , 'content-length': '0'
   , connection: 'keep-alive'
   }
, url: '/events'
, method: 'OPTIONS'
OPTIONSのリクエストがきたら
サーバのレスポンスで許可をしているメソッドを以下の形式で返却します。
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods' : 'GET, POST, PUT, OPTIONS'


許可が行われると正式にクライアントからPUTmethodで再度サーバにアクセスにきます。

実際のソースは以下のようになります。
クライアントのJavaScript
  var xhr = new XMLHttpRequest();
  xhr.open("PUT", "http://dev.yoshimax.net:10001/events");
  xhr.send();

  xhr.onerror = function(e) {
    console.log("ERROR");
    console.log(e);
  }

  xhr.onload = function(e) {
    console.log("LOAD");
    console.log(e);
    console.log(xhr.status);
    console.log(xhr.statusText);
    console.log(xhr.responseText);
  }

}, false);

サーバー側のソース(node.js)
var http = require('http');

http.createServer(function(req, res) {
    console.log(req);
    if (req.url == '/events') {
        if(req.method == 'OPTIONS'){
            res.writeHead(200, {
                'Content-Type'   : 'text/plain',
                'Access-Control-Allow-Origin' : '*',
                'Access-Control-Allow-Methods' : 'GET, POST, PUT, OPTIONS'
            });
            res.write('PUT DONE', 'utf8');
            res.end();


        }else if(req.method == 'PUT'){
            res.writeHead(200, {
                'Content-Type'   : 'text/plain',
                'Access-Control-Allow-Origin' : '*'
            });
            res.write('PUT DONE', 'utf8');
            res.end();
        }
    }
}).listen(10001, '0.0.0.0');


またクライアントで
xhr.withCredentials = "true";
サーバ側で
'Access-Control-Allow-Origin' : req.headers.origin,
'Access-Control-Allow-Credentials' : true
と記述する事でCookieを送信する事が可能です。

実際にサーバ側にCookieが送信されている事が確認できます。
, headers: 
   { host: 'dev.yoshimax.net:10001'
   , origin: 'http://dev.yoshimax.net'
   , 'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4'
   , referer: 'http://dev.yoshimax.net/tmp/testcode/15.html'
   , 'cache-control': 'max-age=0'
   , accept: '*/*'
   , 'accept-language': 'ja-jp'
   , 'accept-encoding': 'gzip, deflate'
   , cookie: '_redmine_session=BAh7BzoPc2XXXXXXXX'
   , connection: 'keep-alive'
   }


参考サイト:
MDN HTTP access control Preflighted requests
XMLHttpRequest Level 2
Node.js Manual & Documentation
A. HTML5では再読込を行わないサイトでも個別リンク(permalink)を設定したり、
ブラウザのバックボタンで戻れるようにする為に
ブラウザの履歴やロケーション表示の操作が行えます。

ブラウザの履歴に追加するには
  window.history.pushState(DATA, TITLE, [URL]);
を利用します。

ブラウザのヒストリーが呼ばれた時に呼び出されるイベント
// eventListener style
window.addEventListener("popstate", function(event) {

},false );

// on style
onpopstate = function(event) {

}
event.stateに設定したデータが入っています。
設定したデータもしくはクエリーストリングから
ページの表示内容を適切に変更する事で
AJAXを利用した動的なサイトでも通常ページのような
使い勝手が実現可能です。

var now = 1;

document.addEventListener("DOMContentLoaded", function(){
  // query
  var query = window.location.search;
  var hash = window.location.hash;
  query.match(/^\?n=(\w*)/);
  now = RegExp.$1;
  document.getElementById('now').innerHTML = now;

  // add pushState
  document.getElementById('push').addEventListener("click", function(){
    now++;
    history.pushState(now, document.title, '?n=' + now);
    document.getElementById('now').innerHTML = now;
  }, false);

  // eventListener style
  //window.addEventListener("popstate", function(event) {
  //},false );

  // on style
  onpopstate = function(event) {
    console.log(event);
    if( event.state != null){
      document.getElementById('now').innerHTML = event.state;
      now = event.state;
    }else{
      var query = window.location.search;
      query.match(/^\?n=(\w*)/);
      now = RegExp.$1;
      document.getElementById('now').innerHTML = now;
    }
  }
}, false);


参考サイト:
MDN Manipulating the browser history
A.
typed arraysというものが追加されました。
バイナリデータを扱うときに活用します。

参考例
// -32768 +32767
var data = new Int16Array(3);
data[0] = 0;
data[1] = 30000;
data[2] = -30000;
console.dir(data);

// -2,147,483,648 +2,147,483,647
var data2 = new Int32Array(3);
data2[0] = 0;
data2[1] = 160000;
data2[2] = -160000;
console.dir(data2);

js_typed_array.png

参考サイト:
MDN JavaScript typed arrays
Typed Array Specification

[javascript HTML5] Q. Web Workersとは?

A.
HTML5では Web Workersが追加されました。
Web Workersを使うとバックグラウンドで表と平行に処理を
行う事が可能です。
バックグラウンドで動かしたいJavaScriptを別ファイルで作成し
var worker = new Worker("worker1.js");
で呼び出して実行します。

ワーカーに対して通信を行いたい場合
worker.postMessage("MESSAGE");
ワーカーからメッセージを受信するには
// EventListener Style
worker.addEventListener("message",function(event)) {
}, false);

// on Style
worker.onmessage = function(event) {
}
のようにWebMessaagesで通信を行います。

並列で処理を行うので操作の競合を避ける為に
WorkerのJavaScriptでは、DOMやWIndowオブジェクトに
アクセスする事はできません。
このようにWorkerで使えるAPIは限定されていますが
XMLHttpRequestやTimer系は利用することが可能です。

詳しくは以下のサイトで確認できます。
HTML5Rocks Features Available to Workers

実際にワーカーを使ってみます。
ワーカーに数字を渡し、1秒ごとに数字をインクリメントして返却する
という簡単なものです。
親 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebWorkers</title>
<script type="text/javascript">
var worker = new Worker("worker1.js");
var mes = "10";
worker.postMessage(mes);
worker.onmessage = function(event) {
  console.log(event);
  console.log(typeof event.data);
  document.getElementsByTagName("body")[0].innerHTML = event.data;
  if(event.data == parseInt(mes) + 2) worker.terminate();
}
</script>
</head>
<body>
</body>
</html>

worker1.js
onmessage = function(event) {
    var i = parseInt(event.data) || 0;
    self.setInterval( function() {
        postMessage(i++);
    }, 1000 );
};

;
実行結果
js_webworker_1.png

ワーカーをCPUのプロセス数に合わせて複数起動を行い計算処理を分散したり
共有ワーカーを利用して複数のiframeなどで処理を共有させる事も可能です。

参考サイト:
HTML5ROCKS The Basics of Web Worker
whatwg WEB WORKERS
Using web workers
Web Workers を使用して Web アプケーションのユーザビリティーを高める

[javascript HTML5] Q. Server-Sent Eventsとは?

A. Server-Sent Eventsは、サーバーにコネクションを張り続け
サーバーからの応答を待つ一連の動作を簡単に行えるAPIです。
var source = new EventSource(endpoint);
でサーバにアクセスを行い、イベントリスナーで応答を待ちます。
endpointは、オリジン(scheme, domain, portが同じ)でなければいけません。

実際にEventSourceをつかったサンプルをみてみます。

HTMLファイル index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ServerSentEvent</title>
<script type="text/javascript">

var source = new EventSource('/events');

// EventListener Style
source.addEventListener('message', function(e) {
  console.log("MESSAGE");
  console.log(e);
  document.body.innerHTML += e.data + '<br>';
}, false);

// On Style
/*
source.onmessage = function (e) {
  console.log(e.data);
  document.body.innerHTML += e.data + '<br>';
};
*/

source.addEventListener('open', function(e) {
  console.log("OPEN");
  console.log(e);
}, false);

source.addEventListener('error', function(e) {
  // CONNECTING = 0;
  // OPEN = 1;
  // CLOSED = 2;
  if (e.eventPhase == EventSource.CONNECTING) {
    console.log("CONNECTING");
    console.log(e);
  }
  if (e.eventPhase == EventSource.CLOSED) {
    console.log("ERROR");
    console.log(e);
  }
}, false);

</script>
</head>
<body>
</body>
</html>
イベントはaddEventListenerかonスタイル両方で記述する事が可能です。

サーバファイル app.js
var http = require('http');
var fs = require('fs');

http.createServer(function(req, res) {
    console.log(req);
    if (req.headers.accept && req.headers.accept == 'text/event-stream') {
        if (req.url == '/events') {
            res.writeHead(200, {
                'Content-Type': 'text/event-stream',
                'Cache-Control': 'no-cache'
            });
            res.write('data: Server time is: ' + (new Date()) + '\n\n');
            res.end();
        }
    } else {
        // 同じオリジンでないと通信できないので                                           
        // index.htmlもnode.js経由で返す                                            
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(fs.readFileSync('index.html'));
        // 次にコネクションを張る時間
        // res.write("retry: 5000");
        res.end();
    }
} ).listen(10001);
サーバー側はnode.jsで記述してみます。
ここではポート番号10001でサーバーを起動するので
オリジンの同一の為にindex.htmlのリソースもnode.js経由で
応答する事にします。

node.jsでapp.jsを起動する
$ node app.js
node.jsを起動し、ブラウザからindex.htmlにアクセスすると
EventSource('/events')で設定した /event に以下のような要求が来ます

ヘッダー情報(cookieも送信される)
   { host: 'dev.yoshimax.net:10001'
   , 'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4'
   , accept: 'text/event-stream'
   , referer: 'http://dev.yoshimax.net:10001/'
   , 'cache-control': 'max-age=0'
   , 'accept-language': 'ja-jp'
   , 'accept-encoding': 'gzip, deflate'
   , cookie: '_redmine_session=BAh7BjoPc2VzcXXXXXXXXX'
   , connection: 'keep-alive'
   }

実行結果は以下のようになります。
js_html5_seversentevent_3.png

ここで応答までの時間や再接続の時間を変更して試してみます。

サーバファイル リトライ5秒 app.js
  res.writeHead(200, {
                    'Content-Type': 'text/event-stream',
                    'Cache-Control': 'no-cache'
  });
  res.write('data: Server time is: ' + (new Date()) + '\n\n');
  // 次にコネクションを張りに来るまでの時間
  res.write("retry: 5000");
  res.end();
}
js_html5_seversentevent_2.png


サーバファイルレスポンス3秒、リトライ1秒 app.js
if (req.url == '/events') {
  // レスポンスまでの時間
  setTimeout(function () {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache'
    });
    res.write('data: Server time is: ' + (new Date()) + '\n\n');
    // 次にコネクションを張りに来るまでの時間
    res.write("retry: 1000");
    res.end();
  }, 3000);
}
js_html5_seversentevent_1.png

参考サイト:
Stream Updates with Server-Sent Events
W3C Server-Sent Events

[javascript HTML5] Q. Web Messageingとは?

A.
HTML5では Web Messageing APIが追加されました。
Web Messageingは、大きく分けてクロスドキュメントメッセージングと
チャンネルメッセージングで分けられています。
どちらも異なるオリジン間において
メッセージ通信が行うことができます。

Web Messageingの仕組みやMessageEventオブジェクトは
Web sockets,WebWorkersやServerSentEventでも利用されており
HTML5周辺技術を学ぶ際にはWeb Messageing APIの理解が役に
立ちますのでしっかりと学習する必要があります。

ここで親window(ドメイン:dev.yoshimax.net)内で読み込んだ
iframe (メイン:sandbox.yoshimax.net)にメッセージを送信し、
iframeから親windowにメッセージの返信を行ってみます。

メッセージを送信するには
window.postMessage(message,origin,port);
メッセージを受信するには
window.addEventListener("message", receiver, false);
の書式で行います。
ここで実際のソースコードを確認してみましょう。
親のHTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Parent</title>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {

  // クリックをしたらfirstFrameにメッセージを送信します。
  var firstBtn = document.getElementById("sendFirstBtn");
  firstBtn.addEventListener("click", function(){
    var fristFrame = document.getElementById("firstFrame");
    firstFrame.postMessage("[from Parent]message","http://sandbox.yoshimax.net");
  }, false);

  // firstFrameからメッセージを受信します。
  window.addEventListener("message", function(e){
    console.log("parent message-----");
    console.log(e);
    if(e.origin == "http://sandbox.yoshimax.net"){
      console.log("parent:" + e.data);
    }
  }, false);

}, false);
</script>
</head>
<body>
<div id="sendFirstBtn" style="border: 1px solid #F00; width:200px;">send message firstF\
rame</div>
<iframe id="firstFrame" width="200" src="http://sandbox.yoshimax.net/tmp/testcode/9-1.html\
"></iframe>
</body>
</html>
iframeのHTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>firstIframe</title>
<script type="text/javascript">
window.addEventListener("message", function(e) {
  console.log("firstframe message-----")
  console.log(e);

  // 受信するドメインを指定する
  if(e.origin == "http://dev.yoshimax.net"){
    // 受信したデータを表示する
    console.log("firstFrame:" + e.data);

    // 親ウインドウにメッセージを送信する
    e.source.postMessage("[from:firstFrame]" + e.data , e.origin);
  }

}, false);
</script>
</head>
<body>
firstIframe
</body>
</html>
実行結果
js_message_1.png

messageのイベントで送られてきたイベントの構造は
HTML5 Web Messaging の 2 Event definitionsで定義されており
このイベントのインターフェースは server-sent events, Web socketsでも
利用されているのでしっかりと把握しよう。

originを適切なドメインにしなかった場合
js_message_2.png

originのドメインの指定が適切ではない場合上記のようなエラーが発生する

参考サイト:
W3C HTML5 Web Messaging
MDN window.postMessage

[javascript ECMAScript5] Q. indexOf

  • 投稿日:
  • by
A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
indexOfはArrayの中身を検索し見つかったindexを返却します。
見つからなかった場合は -1 が戻り値として返ります。
indexOfは前からlastIndexOfは後ろから探してます。
var a = [1,2,3,4,1,2,3,4];
console.log(a.indexOf(3)); // 2
console.log(a.lastIndexOf(3)); // 6
console.log(a.lastIndexOf(5)); // -1
js_Array_indexof.png

参考サイト:
MDN Array indexOf

[javascript ECMAScript5] Q. reduce()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
reduceは配列の各要素にcallback関数を呼び出します。
第一引数にcallback関数、第二引数に初期値を渡すと
callback関数の第一引数に最初は初期値が2回目からreturnで返された値が
callback関数の第一引数に返されます。
var a = [1,5,10];
function test(previousValue, currentValue, index, array) {
  console.log("-----");
  console.log("index: " + index);
  console.log("previousValue: " + previousValue);
  console.log("currentValue: " + currentValue);
  return previousValue + currentValue;
}

b = a.reduce(test, 0);
console.log(b);
js_Array_reduce.png

参考サイト:
MDN Array reduce

[javascript ECMAScript5] Q. some()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
someは配列の各要素にcallback関数を呼び出します。
callback関数の第一引数は値、第二引数はインデックス、第三引数は元のArrayがそのまま入ってきます。
callback関数内の返り値が一つでも真の場合 trueを返します。
callback関数内の返り値が真の時点で処理が終了します。
var a = [1,5,10,15,20];
function test(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  return element > 10;
}

function test2(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  return element > 100;
}

b = a.some(test);
console.log(b);

c = a.some(test2);
console.log(c);
is_Array_some.png

参考サイト:
MDN Array some

[javascript ECMAScript5] Q. every()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
everyは配列の各要素にcallback関数を呼び出します。
callback関数の第一引数は値、第二引数はインデックス、第三引数は元のArrayがそのまま入ってきます。
callback関数内の返り値がすべて真の場合 trueを返します。
callback関数内の返り値が偽の時点で処理が終了します。
var a = [1,5,10,15,20];
function test(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  return element < 10;
}

function test2(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  return element < 100;
}

b = a.every(test);
console.log(b);

c = a.every(test2);
console.log(c);


js_Array_every.png

参考サイト:
MDN Array every

[javascript ECMAScript5] Q. filter()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
filterは配列の各要素にcallback関数を呼び出します。
callback関数の第一引数は値、第二引数はインデックス、第三引数は元のArrayがそのまま入ってきます。
callback関数の返り値として、真偽を返却し真の新たの配列を返します。
var a = [1,5,10,15,20];
function bai(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  return element > 10;
}
b = a.filter(bai);
console.log(a);
console.log(b);
js_Array_filter.png

参考サイト:
MDN Array filter

[javascript ECMAScript5] Q. map()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
mapは配列の各要素にcallback関数を呼び出します。
callback関数の第一引数には値が入っており
callback関数内で値を加工して returnで返すと新しい配列が作成されます。
var a = [1,2,3];
function bai(element) {
  return element * 2;
}
b = a.map(bai);
console.log(a);
console.log(b);

js_Array_map.png

参考サイト:
MDN Array map

[javascript ECMAScript5] Q. forEach()

A.
ECMAScript5では、ビルトインArrayオブジェクトにメソッドが追加されました。
forEach(), map(), filter(), every(), some(), reduce(), reduceRight(), indexOf(), lastIndexOf()
が新たに追加されています。
FirefoxではJavaScript1.6で実装されました。
MDN Array で確認できます。

forEachは配列の各要素にcallback関数を呼び出します。
callback関数の第一引数は値、第二引数はインデックス、第三引数は元のArrayがそのまま入ってきます。
Arrayはリファレンスなので、callback関数内で値を変更すると元の配列の内容も変更されます。

var a = [1,5,15,20,25];
function log(element, index, array) {
  console.log("-----");
  console.log(element);
  console.log(index);
  array[index] = element * 5;
}
a.forEach(log);
console.log(a);
js_Array_forEach.png

参考サイト:
MDN forEach

[javascript] Q.ECMAScript5 isArray()

A. JavaScriptの配列は typeof では object が返ってくるので
Arrayか判断するのはちょいと面倒だったのですが
ECMAScript5では Array.isArray() というクラスメソッドが追加されました。

var a = [1, 2, 3];
var b = {x:1, y:2, z:3};

// typeof は object になる
console.log( typeof a );
console.log( typeof b );

// ECMAScript5では isArray が使える
console.log( Array.isArray(a) );
console.log( Array.isArray(b) );

// コンストラクタをみてみる
console.log( a.constructor );
console.log( b.constructor );
console.log( a.constructor === Array);
console.log( b.constructor === Array);

// ObjectのtoStringを呼び出して判断
console.log( Object.prototype.toString.call(a) );
console.log( Object.prototype.toString.call(b) );
console.log( Object.prototype.toString.call(a) === "[object Array]");
console.log( Object.prototype.toString.call(b) === "[object Array]");


js_isArray.png

参考サイト:
Chapter 3. Literals and Constructors

[javascript] Q.ECMAScript5 bind()

A.
ECMAScript5では、bind() というメソッドで関数のthisの値を変更できます。
var x = 1;
var y = 2;

// 1. bind
// 第一引数はthisの値を設定します。
function test1(y) {
  console.log( this );
  console.log( arguments );
  console.log( this.x + y );
}
test1(10); // 11
var a1 = { x:5 };
var b1 = test1.bind(a1);
b1(10); // 15

// 2. bind
// 第二引数以降は引数が順番にアサインされます
var goukei = function(x, y, z) {
  console.log( this );
  console.log( arguments );
  console.log( x + y + z);
}
goukei(1, 2, 3); // 6
var b2 = goukei.bind(null, 1, 2);
b2(3); // 6
var b3 = goukei.bind(null, 1, 2, 10);
b3(); // 13

js_bind_01.png

参考サイト:
MDN bind

[javascript] Q.createでオブジェクトを作る

A.
ECMAScript5では、Createでオブジェクトが作成できます。
Createする際に原型となるプロトタイプを指定する事ができます。
enumerableは、for..inで表示するかを決める事ができます。
// 1
// リテラル
var obj1 = { test: 1 };
console.dir(obj1);

// 2
// ビルトインオブジェクト
var obj2 = new Object();
obj2['test'] = 1;
console.dir(obj2);

// 3
// create 第一引数にプロトタイプとなるオブジェクト指定
var obj3 = Object.create(Object.prototype, {
  "test": {
    value: 1,
    enumerable: true,
    writable: true,
    configurable: true
  }
});
console.dir(obj3);

// 4
// prototype継承を何も指定しない事も可能
var obj4 = Object.create(null, {
  "test": {
    value: 1,
    enumerable: true,
    writable: true,
    configurable: true
  }
});
console.dir(obj4);

js_object_01.png

プロトタイプ継承では、createにより記述の簡略化をする事が可能になります。
// 1 prototype
function Base(name) {
  this.name = name;
}
function Child(name) {
  // 継承元のコンストラクタを呼ぶ
  Base.apply(this,[name]);
}
// 継承元を指定
Child.prototype = new Base();
var obj5 = new Child('max');
console.dir(obj5);

// 2 create
var obj6 = Object.create(new Base(), {name:{value:'max'}});
console.dir(obj6);

js_object_02.png

参考サイト:
ECMAScript 5 Objects and Properties