Object Oriented JavaScriptの入門
謝罪
Object Oriented JavaScriptの入門 - Yuta.Kikuchiの日記
2013/08/27に公開したObject Oriented JavaScriptの記事ですが、prototype周りの説明がいい加減だったため内容を一旦削除することにしました。多くの方にBookmarkして頂いたお陰でホットエントリーにも掲載されたのですが、正直それに見合う内容ではありませんでした。
あやふやな知識を書いてしまったのは僕の勉強不足が原因です。ネットで収集した情報だけで自分の知識を固めて行くのは危険だという事を身にしみて感じました。同時に今回とても悔しく情けない思いもしたので、必ずや正しいJavaScriptのオブジェクト指向知識を身につけて再投稿することをここに宣言します。 "I shall return!"
(function() { console.log( "I shall return!" ); }());> 後で読むタグをつけた方、もう記事はありません(笑)。修正版の再投稿を期待していてください。
> ブックマークをしていただいた方、申し訳ありませんがブックマークの削除をお願いします。
> id:yuisekiさん はい、仰せの通りCoffeeScriptで書くというのも一つの手だと感じています。
> id:anemoさん もしまた変な知識を書いていたら今度は容赦なく突っ込みをお願いします。
JSONを見やすく表示するにはPythonの-mjson.toolを使うと良いよ
- 作者: Alex Martelli,Anna Martelli Ravenscroft,David Ascher,鴨澤眞夫,當山仁健,吉田聡,吉宗貞紀
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/06/26
- メディア: 大型本
- 購入: 11人 クリック: 423回
- この商品を含むブログ (85件) を見る
JSONを見やすく表示する
WebツールやExtension
{"Compile":["C","C++","Objective-C"],"Script":["JavaScript","PHP","Perl","Python"]}JSONはJavaScriptのObjectを文字列化したもので、1行にまとまってしまうため非常に見づらいです。「JSON 整形」というキーワードでぐぐると上のようにWebサービスからChrome/FirefoxのExtensionなどが出てきます。今回はサーバサイドでJSONをechoする処理を書いていて、ツールを用いる事無く簡単に見やすくする方法を調べていたのですが、Pythonの-mjson.toolを使うとコマンドライン上で整形できることがわかりました。
Python pretty-print-json
Stack Overflowに How to pretty-print JSON script?というコラムがあり、そこでPythonの-mjson.toolを指定する方法を知りました。例えば以下のようなPerlコードがあって、実行処理に対してパイプ(|)でpython -mjson.toolを指定するだけで整形してくれます。下では生のJSON文字列と整形した内容を比較していますが、見やすさは歴然だと思います。特にHashを表す{}と配列を表す[]がそれぞれ分かりやすいかと。
#!/usr/bin/perl use strict; use warnings; use JSON; my %array = (); push( @{$array{Script}}, ( 'JavaScript', 'PHP', 'Perl', 'Python' ) ); push( @{$array{Compile}}, ( 'C', 'C++', 'Objective-C' ) ); print encode_json( \%array );$ perl echo_json.pl {"Compile":["C","C++","Objective-C"],"Script":["JavaScript","PHP","Perl","Python"]}$ perl echo_json.pl | python -mjson.tool { "Compile": [ "C", "C++", "Objective-C" ], "Script": [ "JavaScript", "PHP", "Perl", "Python" ] }
「魔法少女まどか☆マギカ」の台詞をJavaScriptでMapReduceしてGoogle Chart APIでグラフ出力したよ!
- 作者: Tom White,玉川竜司,兼田聖士
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/07/23
- メディア: 大型本
- 購入: 9人 クリック: 182回
- この商品を含むブログ (24件) を見る
概要
- 「魔法少女まどか☆マギカ」の台詞をNLTK(Natural Language Toolkit)で解析する - Yuta.Kikuchiの日記
- SpiderMonkeyでのコマンドラインJavascript - Yuta.Kikuchiの日記
- CentOSでHadoopを使ってみる - Yuta.Kikuchiの日記
以前に「魔法少女まどか☆マギカ」の台詞をNLTK(Natural Language Toolkit)で解析することに挑戦しましたが、解析結果の集計グラフが奇麗に表示されませんでした。今回はそれを改善すべく手法を変えて挑戦します。グラフ化はNLTKではなくGoogle Chart APIを利用します。Google Chart Tools - Google Code またHadoopのMapReduceにJavaScriptを用い、Google Chart APIとのデータ連携をしやすいようにします。以下に大まかな処理の流れを記述します。
- SpiderMonkeyをCentOSに設定
- 魔法少女まどか☆マギカの台詞をPythonでスクレイピング
- NLTKによる分かち書き
- JavaScriptによるMapReduce
- HadoopでMapReduce
- Google Chart APIでMapReduce結果をグラフ化
SpiderMonkeyをCentOSに設定
参考
HadoopのMapReduceをJavaScriptで行うためにCentOSにSpiderMonkeyを設定します。本家サイトのinstall手順を参考にしました。https://developer.mozilla.org/en/Building_only_SpiderMonkey
ソースコード取得/解凍
$ wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.8.0-rc1.tar.gz $ tar -xzf js-1.8.0-rc1.tar.gzbuild/install
$ cd js/src/ $ make BUILD_OPT=1 -f Makefile.ref $ sudo make BUILD_OPT=1 JS_DIST=/usr/local -f Makefile.ref export
「魔法少女まどか☆マギカ」の台詞をPythonでスクレイピング
スクレイピングPythonコード
pythonコードで魔法少女まどか☆マギカ台詞をスクレイピングします。以前より台詞掲載ページが増えていたのでURLを追加しました。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys,re,urllib,urllib2 urls = ( 'http://www22.atwiki.jp/madoka-magica/pages/170.html', 'http://www22.atwiki.jp/madoka-magica/pages/175.html', 'http://www22.atwiki.jp/madoka-magica/pages/179.html', 'http://www22.atwiki.jp/madoka-magica/pages/180.html', 'http://www22.atwiki.jp/madoka-magica/pages/200.html', 'http://www22.atwiki.jp/madoka-magica/pages/247.html', 'http://www22.atwiki.jp/madoka-magica/pages/244.html', 'http://www22.atwiki.jp/madoka-magica/pages/249.html', 'http://www22.atwiki.jp/madoka-magica/pages/250.html', 'http://www22.atwiki.jp/madoka-magica/pages/252.html', 'http://www22.atwiki.jp/madoka-magica/pages/241.html', 'http://www22.atwiki.jp/madoka-magica/pages/254.html' ) f = open( './madmagi.txt', 'w' ) opener = urllib2.build_opener() ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/ 534.51.22' referer = 'http://www22.atwiki.jp/madoka-magica/' opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )] for url in urls: content = opener.open( url ).read() if re.compile( r'<div class="contents".*?>((.|\n)*?)</div>', re.M ).search( content ) is not None: data = re.compile( r'<div class="contents".*?>((.|\n)*?)</div>', re.M ).search( content ).group() if re.compile( r'「(.*?)」', re.M ).search( data ) is not None: lines = re.compile( r'「(.*?)」', re.M ).findall( data ) for line in lines: f.write( line + "\n" ) f.close()抽出データ
以下は抽出したデータの一部です。全部で1259行あります。
んっん…あっ…! あっ…! ひどい… 仕方ないよ。彼女一人では荷が重すぎた でも、彼女も覚悟の上だろう そんな…あんまりだよ、こんなのってないよ 諦めたらそれまでだ でも、君なら運命を変えられる 避けようのない滅びも、嘆きも、全て君が覆せばいい そのための力が、君には備わっているんだから 本当なの? 私なんかでも、本当に何かできるの?こんな結末を変えられるの? もちろんさ。だから僕と契約して、魔法少女になってよ! 私は巴マミ あなたたちと同じ、見滝原中の3年生 そして キュゥべえと契約した、魔法少女よ
NLTKによる分かち書き
NLTKによる分かち書きを行います。分かち書きとは語の区切りに空白を入れて分かりやすくする表記です。正規表現による単語ベースでの分かち書きとMeCabを用いた形態素ベースでの分かち書きの両方をやります。
単語区切り
台詞の単語をスペースで区切ります。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys reload(sys) sys.setdefaultencoding('utf-8') import nltk from nltk.corpus.reader import * from nltk.corpus.reader.util import * from nltk.text import Text jp_sent_tokenizer = nltk.RegexpTokenizer(u'[^ 「」!?。]*[!?。]') jp_chartype_tokenizer = nltk.RegexpTokenizer(u'([ぁ-んー]+|[ァ-ンー]+|[\u4e00-\u9FFF]+|[^ぁ-んァ-ンー\u4e00-\u9FFF]+)') data = PlaintextCorpusReader( './', r'madmagi.txt', encoding='utf-8', para_block_reader=read_line_block, sent_tokenizer=jp_sent_tokenizer, word_tokenizer=jp_chartype_tokenizer ) #ファイル保存 f = open( './word.txt', 'w' ) for i in data.words(): f.write( i + " " ) f.close抽出された状態は以下のようになります。最初にスクレイプした状態からスペース区切りになっている事が分かると思います。これをword.txtとして保存します。
んっん … あっ …! あっ …! ひどい … 仕方 ないよ 。 彼女一人 では 荷 が 重 すぎた でも 、 彼女 も 覚悟 の 上 だろう そんな … あんまりだよ 、 こんなのってないよ 諦 めたらそれまでだ でも 、 君 なら 運命 を 変 えられる 避 けようのない 滅 びも 、 嘆 きも 、 全 て 君 が 覆 せばいい そのための 力 が 、 君 には 備 わっているんだから 本当 なの ? 私 なんかでも 、 本当 に 何 かできるの ? こんな 結末 を 変 えられるの ? もちろんさ 。 だから 僕 と 契約 して 、 魔法少女 になってよ ! 私 は 巴 マミ あなたたちと 同 じ 、 見滝原中 の 3 年生 そして キュゥ べえと 契約 した 、 魔法少女 よMeCabによる形態素解析
MeCabを利用して形態素解析を行い、形態素毎にスペース区切りでデータを保存するようにします。抽出するサンプルコードは次のようになります。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys reload(sys) sys.setdefaultencoding('utf-8') import MeCab mecab = MeCab.Tagger('-Ochasen') data = open( './madmagi.txt' ).read() f = open( './ma.txt', 'w' ) node = mecab.parseToNode( data ) phrases = node.next while phrases: try: k = node.surface f.write( k + " " ) node = node.next except AttributeError: break f.close()抽出した結果は以下のようになります。これをma.txtとして保存します。
ん っ ん … あっ … ! あっ … ! ひどい … 仕方 ない よ 。 彼女 一 人 で は 荷 が 重 すぎ た でも 、 彼女 も 覚悟 の 上 だろ う そんな … あんまり だ よ 、 こんな の って ない よ 諦め たら それ まで だ で も 、 君 なら 運命 を 変え られる 避け よう の ない 滅び も 、 嘆き も 、 全て 君 が 覆せ ば いい その ため の 力 が 、 君 に は 備わっ て いる ん だ から 本当 な の ? 私 なんか でも 、 本当に 何 か できる の ? こんな 結末 を 変え られる の ? もちろん さ 。 だから 僕 と 契約 し て 、 魔法 少女 に なっ て よ ! 私 は 巴 マミ あなた たち と 同じ 、 見滝 原中 の 3 年生 そして キュゥ べ えと 契約 し た 、 魔法 少女 よ
JavaScriptによるMapReduce
MapRuduceクラスの定義
NLTKによって分かち書きされた単語をJavaScriptでMapReduceします。
簡単なMapReduceクラスを以下のように定義します。mapメソッドは単語を整形、reduceは畳み込みを行っています。var MapReduce = function() {}; MapReduce.prototype = { map : function( data ) { var lines = []; re = new RegExp( "\n" ); if( re.test( data ) ) { lines = data.split( "\n" ); } else { lines[0] = data; } for( var j=0; j<lines.length; j++ ) { var words = lines[j].split( " " ); for( var i=0; i<words.length; i++ ) { print( words[i] + " ," + 1 ); } } }, reduce : function( lines ) { var nodes = {}; for( i=0; i<lines.length; i++ ) { var words = lines[i].split( " ," ); var k = words[0]; if( !nodes[k] ) { nodes[k] = 1; } nodes[k] = nodes[k] + 1; } for( var k in nodes ) { print( k + ' : ' + nodes[k] ); } }, };mapper.js
上のMapReduceクラスを呼び出すmapper.jsを次のように定義します。load関数を使って上で定義した外部ファイル(MapReduce.js)を読み込みます。またSpiderMonkeyのreadlineを使って標準入力を読み取ります。
#!/usr/local/bin/js load( "MapReduce.js" ); var MR = new MapReduce(); while( ( line = readline() ) !== null ) { MR.map( line ); }NLTKで抽出したword.txtに対してmapperをかけると以下のような結果を得る事ができます。
$ cat data/word.txt | js mapper.js んっん ,1 … ,1 あっ ,1 …! ,1 ,1 あっ ,1 …! ,1 ,1 ひどい ,1 … ,1 ,1 仕方 ,1 ないよ ,1 。 ,1 彼女一人 ,1 では ,1 荷 ,1 が ,1 重 ,1 すぎた ,1reducer.js
上のMapReduceクラスを呼び出すreducer.jsを次のように定義します。
#!/usr/local/bin/js load( "MapReduce.js" ); var MR = new MapReduce(); lines = []; i = 0; while( ( line = readline() ) !== null ) { lines[i] = line; i++; } MR.reduce( lines );mapperで得た結果に対してreducerをかけると以下のようになります。
$ cat data/word.txt | js mapper.js | js reducer.js んっん : 2 … : 237 あっ : 19 …! : 10 : 2080 ひどい : 4 仕方 : 11 ないよ : 8 。 : 457 彼女一人 : 3 では : 9 荷 : 3 が : 127 重 : 3 すぎた : 3 でも : 23 、 : 1009 彼女 : 18 も : 61 覚悟 : 3
HadoopでMapReduce
HDFSへの登録
折角mapper/reducerを書いたのでHadoop上で実行します。NLTKで解析した単語、形態素の二つのテキストデータをHDFSに登録します。HDFSのディレクトリをmadmagiとして新たに作成します。またHDFSコマンドは長いのでaliasを張ります。ここではalias hdfs='hadoop dfs'としました。
$ alias hdfs='hadoop dfs' $ hdfs -mkdir madmagi $ hdfs -put data/ma.txt madmagi/ $ hdfs -put data/word.txt madmagi/ $ hdfs -ls madmagi/ Found 2 items -rw-r--r-- 1 yuta supergroup 0 2012-03-21 08:04 /user/yuta/madmagi/ma.txt -rw-r--r-- 1 yuta supergroup 0 2012-03-21 08:04 /user/yuta/madmagi/word.txtHadoop実行
HadoopStreamingを使ってMapReduceします。基本的には上で実行したmapper.js/reducer.jsをHadoopで実行しているだけです。まずは単語の結果に対して(word.txt)に対して実行します。
$ hadoop jar /usr/lib/hadoop-0.20/contrib/streaming/hadoop-streaming-0.20.2-cdh3u3.jar -input madmagi_in/word.txt -output madmagi_out_word -mapper /home/yuta/work/dev/hadoop/map_reduce/mapper.js -reducer /home/yuta/work/dev/hadoop/map_reduce/reducer.js -file /home/yuta/work/dev/hadoop/map_reduce/mapper.js -file /home/yuta/work/dev/hadoop/map_reduce/reducer.js /yuta/work/dev/hadoop/map_reduce/map.py, /home/yuta/work/dev/hadoop/map_reduce/reduce.py, /var/lib/hadoop-0.20/cache/yuta/hadoop-unjar2165458985493486154/] [] /tmp/streamjob8394427362728426211.jar tmpDir=null 12/03/26 02:09:13 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 12/03/26 02:09:13 WARN snappy.LoadSnappy: Snappy native library not loaded 12/03/26 02:09:13 INFO mapred.FileInputFormat: Total input paths to process : 1 12/03/26 02:09:14 INFO streaming.StreamJob: getLocalDirs(): [/var/lib/hadoop-0.20/cache/yuta/mapred/local] 12/03/26 02:09:14 INFO streaming.StreamJob: Running job: job_201203260111_0017 12/03/26 02:09:14 INFO streaming.StreamJob: To kill this job, run: 12/03/26 02:09:14 INFO streaming.StreamJob: /usr/lib/hadoop-0.20/bin/hadoop job -Dmapred.job.tracker=localhost:8021 -kill job_201203260111_0017 12/03/26 02:09:14 INFO streaming.StreamJob: Tracking URL: http://localhost.localdomain:50030/jobdetails.jsp?jobid=job_201203260111_0017 12/03/26 02:09:15 INFO streaming.StreamJob: map 0% reduce 0% 12/03/26 02:09:41 INFO streaming.StreamJob: map 50% reduce 0% 12/03/26 02:09:42 INFO streaming.StreamJob: map 100% reduce 0% 12/03/26 02:10:08 INFO streaming.StreamJob: map 100% reduce 100% 12/03/26 02:10:16 INFO streaming.StreamJob: Job complete: job_201203260111_0017 12/03/26 02:10:16 INFO streaming.StreamJob: Output: madmagi_out_word同様に形態素解析の結果(ma.txt)もHadoopでMapReduceします。
$ hadoop jar /usr/lib/hadoop-0.20/contrib/streaming/hadoop-streaming-0.20.2-cdh3u3.jar -input madmagi_in/ma.txt -output madmagi_out_ma -mapper /home/yuta/work/dev/hadoop/map_reduce/mapper.js -reducer /home/yuta/work/dev/hadoop/map_reduce/reducer.js -file /home/yuta/work/dev/hadoop/map_reduce/mapper.js -file /home/yuta/work/dev/hadoop/map_reduce/reducer.js 59202/] [] /tmp/streamjob2162462095464428994.jar tmpDir=null 12/03/26 02:10:52 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 12/03/26 02:10:52 WARN snappy.LoadSnappy: Snappy native library not loaded 12/03/26 02:10:52 INFO mapred.FileInputFormat: Total input paths to process : 1 12/03/26 02:10:53 INFO streaming.StreamJob: getLocalDirs(): [/var/lib/hadoop-0.20/cache/yuta/mapred/local] 12/03/26 02:10:53 INFO streaming.StreamJob: Running job: job_201203260111_0018 12/03/26 02:10:53 INFO streaming.StreamJob: To kill this job, run: 12/03/26 02:10:53 INFO streaming.StreamJob: /usr/lib/hadoop-0.20/bin/hadoop job -Dmapred.job.tracker=localhost:8021 -kill job_201203260111_0018 12/03/26 02:10:53 INFO streaming.StreamJob: Tracking URL: http://localhost.localdomain:50030/jobdetails.jsp?jobid=job_201203260111_0018 12/03/26 02:10:54 INFO streaming.StreamJob: map 0% reduce 0% 12/03/26 02:11:17 INFO streaming.StreamJob: map 50% reduce 0% 12/03/26 02:11:21 INFO streaming.StreamJob: map 100% reduce 0% 12/03/26 02:11:39 INFO streaming.StreamJob: map 100% reduce 100% 12/03/26 02:11:46 INFO streaming.StreamJob: Job complete: job_201203260111_0018 12/03/26 02:11:46 INFO streaming.StreamJob: Output: madmagi_out_maHDFSからローカルへコピー
HDFS上の結果をローカルにコピーします。成功結果のpart-00000ファイルが出来ている事を確認します。
$ hdfs -get madmagi_out_word madmagi_out_word $ hdfs -get madmagi_out_ma madmagi_out_ma $ ls madmagi_out_word drwxr-xr-x 3 yuta yuta 4096 3月 26 02:48 . drwxr-xr-x 6 yuta yuta 4096 3月 26 02:46 .. -rw-r--r-- 1 yuta yuta 0 3月 26 02:46 _SUCCESS drwxr-xr-x 3 yuta yuta 4096 3月 26 02:46 _logs -rw-r--r-- 1 yuta yuta 59377 3月 26 02:46 part-00000 $ ls madmagi_out_ma drwxr-xr-x 3 yuta yuta 4096 3月 26 02:46 . drwxr-xr-x 6 yuta yuta 4096 3月 26 02:46 .. -rw-r--r-- 1 yuta yuta 0 3月 26 02:46 _SUCCESS drwxr-xr-x 3 yuta yuta 4096 3月 26 02:46 _logs -rw-r--r-- 1 yuta yuta 24294 3月 26 02:46 part-00000part-0000の中身は以下のようになっています。ここではword.txtに対して実行した結果のpart-00000を載せます。
あ ,15 あぁ ,1 あぁっ ,2 ああ ,5 ああそうだったの ,1 ああでもしなきゃ ,1 あいつは ,3 あけみさんは ,1 あげるよ ,1 あたし ,3 あたしが ,1 あたしがこんな ,1 (略)抽出で目立った単語としては以下のようになりました。
Google Chart APIでMapReduce結果をグラフ化
データをJSON形式に変換
part-00000といった結果をJSON形式に変換します。JSONに変換するPythonコードは以下の通りです。結果をtext.jsとして保存します。
#!/usr/bin/env python import json,codecs map = {} for line in codecs.open( 'word.txt', 'r', 'utf-8' ): data = line.split( ':' ) key = data[0] value = data[1] map[ key ] = value str = json.dumps( map ) f = open( 'text.js', 'w' ) f.write( str ) f.close()JSON形式をGoogleChartAPIで出力
GoogleChartAPIに対してimgタグで読み込むスクリプトを次のように定義します。上で保存したtext.jsにて下で定義するdrawImage関数にjsonデータを渡すように修正が必要です。データ量が多いとURL長の制限を超えるので15個データを表示するようにしました。
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> </head> <body> <img src='' id='chart' /> </body> <script> var drawImage = function( json ) { var base = 'http://chart.apis.google.com/chart?cht=bhg&chs=500x500'; var count = []; var title = []; var j = 0; for( var i in json ) { title[j] = 't' + encodeURI ( i ) + ',333333,0,' + j + ',13,1'; count[j] = json[i]; j++; if( j == 15 ) { break; } } var titles = title.join( '|' ); var counts = count.join( ',' ); url = base + '&chd=t:' + counts + '&chdlp=tv|r&chxt=y,x&chm=' + titles; var img = document.getElementById( 'chart' ); img.setAttribute( 'src', url ); } </script> <script src='./text.js'></script> </html>
超絶簡単!JavaScriptの性質を10分で理解するための重要なポイントのまとめ
JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
- 作者: Douglas Crockford,水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/12/22
- メディア: 大型本
- 購入: 94人 クリック: 1,643回
- この商品を含むブログ (187件) を見る
JavaScriptのニーズ
NodeJSやTitaniumMobileの普及によりサーバサイド/スマフォアプリの作成をJavaScriptで書こうとする動きが盛んです。それだけ注目を集めているせいかブログの記事でもJavaScriptのネタを書くとはてぶ登録されやすい傾向が現れます。一応今までJavaScript系の記事をいくつか書いてきたのでリンクを紹介します。
- Javascriptによる正規表現まとめ - Yuta.Kikuchiの日記
- WebSocket対応状況のまとめ - Yuta.Kikuchiの日記
- Node.jsでWebSocketを試してみる - Yuta.Kikuchiの日記
- jQuery Proven Performance Tips And Tricks (翻訳) - Yuta.Kikuchiの日記
- SpiderMonkeyでのコマンドラインJavascript - Yuta.Kikuchiの日記
- jQueryの参考にすべきSiteのまとめ - Yuta.Kikuchiの日記
- javascriptのクラスまとめ - Yuta.Kikuchiの日記
JavaScriptは記述の制限が緩い言語で記述者が自由に定義できてしまいます。自由度が高いという言葉は良いように聞こえますが、各人それぞれの志向に左右されることが多く、特に他人のコードを読む時に苦労します。今日はJavaScriptの性質を理解するためにdebugを仕込みながら勉強を進めたいと思います。
JavaScriptを理解する上で大切な事
個人的にJavaScriptの性質を理解する上で重要な事はObjectと関数を理解することだと認識しています。JavaScriptのほとんどのデータ定義がObjectで表現されます(関数、文字列、数値以外)。JavaScriptにはClassといった概念が無く、関数定義を他言語でのClassのConstructorのように扱い、Classメソッドのようなものをprototypeといった暗黙参照のObjectで定義します。左の言葉に少し違和感を感じられるかもしれません。後にコード例を示すのでそちらも参照してください。
配列/連想配列の定義
配列と連想配列は当然ながら別物です。配列はList型のデータに対して連想配列はKeyからValueを得るHashになります。JavaScriptの表記としても別々で扱いますが、両方ともObjectとしての性質を持ちます。
配列の定義
配列の宣言はArrayもしくは[]を利用します。私はArrayで宣言か添字で入れる書き方をします。newを使っても使わなくても同じということであれば使用しない事にしています。また添字を文字列で入れるまたhashで一度宣言という方法は使った事が無いです。
// Arrayで宣言 var a = Array( 'a', 'b' ); // Arrayをnew var a = new Array( 'a', 'b' ); // []に入れちゃう var a = [ 'a', 'b' ]; // 添字で入れる var a = Array(); a[ 0 ] = 'a'; a[ 1 ] = 'b'; // 添字を文字列で入れる var a = []; a[ '0' ] = 'a'; a[ '1' ] = 'b'; // hashで入れてArrayを継承させる var a = { 0 : 'a', 1 : 'b', length : 2 }; a.__proto__ = Array.prototype;連想配列の定義
連想配列の定義はObjectもしくは{}を利用します。。当然ながらKeyとValueが必要でKey:ValueやKeyを添字にしてValueを格納といった表現をします。配列のパターンと同じでnewをするしないで結果が同じであるのであればnewをしないようにしています。
// Objectで宣言 var o = Object(); o = { 0 : 'a', 1 : 'b' }; // Objectをnew var o = new Object(); o = { 0 : 'a', 1 : 'b' }; // {}に入れちゃう var o = { 0 : 'a', 1 : 'b' }; // keyを指定して入れる var o = {}; o[ '0' ] = 'a'; // property参照させる var o = {}; o.0 = 'a'; //これはエラーになる propertyはkeyが文字列でないと駄目な様子 o.a = 'a'; //これはOKtypeofで表示してみると
ArrayもObjectも両方typeofで表示するとObjectになります。int、string、functionも調べた結果を以下に載せます。
var a = Array(); alert( typeof a ); //object var o = Object(); alert( typeof o ); //object var i = 1; alert( typeof i ); //number var s = 'string'; alert( typeof s ); //string var f = function(){}; alert( typeof f ); //functionObjectの拡張
上で示したObject(連想配列)を拡張した定義を行います。ObjectはKey:Valueの表現を利用して変数だけでなく関数の定義も行えます。拡張定義したObjectの関数をproperty参照を利用する事でメソッドのように利用できます。以下に例を記します。
var newObject = { property : 'Object', //連想配列の定義なのでカンマが必要です。 echo : function() { // 関数だって定義できます。 alert( this.property ); } }; newObject.echo(); //Objectこれを覚える上で忘れてはいけないのが連想配列だと言う事です。連想配列はKey:Valueのペアをカンマ区切りで格納します。関数も連想配列のルールに従えば定義できるという事です。私は上のような表記をしますがpropertyを使って以下のように記述する事も出来ます。
var newObject = Object(); newObject.property = 'Object'; newObject.echo = function() { alert( this.property ); } newObject.echo();
関数
通常の定義
関数はfunctionで定義します。varで宣言するか関数をそのまま宣言するかの方法がありますが、JavaScriptでより多く使われるのは前者の書き方だと思っています。(他言語だと後者の書き方しかできないと思いますが。)
// varで宣言する var f = function(){}; // 関数としてfを宣言する function f(){}無名関数
無名関数とはその名の通り名前の無い関数を定義できる仕組みです。まずは単純な例を記述しますがfunctionとだけ宣言して();にて実行します。Bookmarkletを作る時も良くこの記述をします。
(function(){ alert( 'OK' ); })();この無名関数を使うとクロージャが定義できます。クロージャの簡単な説明としてはグローバル空間に影響を与えないようにする仕組みです。よくある使い方としては無名関数で名前空間を一つ定義して、名前空間の下にObjectやクラスを定義するとincludeするJSファイル間で名前のバッティングを回避できます。以下は名前空間の下にObjectを定義して実行する例です。
var NameSpace = (function(){ return { property : 'NameSpace Object', echo : function() { alert( this.property ); }, }; } )(); NameSpace.echo(); // NameSpace ObjectClass定義
JavaScriptにはClassという概念がありません。正確に言うとfunctionを基にClassっぽいものを表現しているだけです。functionでの宣言がClassのConstructorのような働きをします。
var Class = function() { //Constructor this.property = 'Class'; //property this.echo = function() { //method alert( this.property ); } }; new Class().echo(); //Class実は上の書き方は良い例とは言えません。なぜならばConstructorの中にmethodを書いてしまっているからnewされる度にメソッドまでもが再定義されてしまいます。これを防ぐためにConstructorとmethodの定義を分離します。更にJavaScript特有のprototype属性(Object)を定義する事で暗黙的な参照を利用します。上の例を書き換えると以下のようになります。
var Class = function() {}; //Constructorだけ定義 Class.prototype = { //prototypeはObject property : 'Class', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; new Class().echo(); // Class基本的にprototypeはConstructorの外に書きます。以下はprototypeが参照がうまく利用できない例と非推奨の書き方です。errorが出てしまうのはメソッドを呼び出すタイミングでundefinedとなります。Constructor内部でprototypeを定義すると新たにprototypeというオブジェクトを生成してしまう事になります。これだと暗黙参照ができません。それを回避する方法としてprototypeをConstructor内部でreturnすると思うように動作しますが、prototypeの暗黙参照の役割を考えるとConstructor内部にprototypeを定義すべきではありません。
// errorが発生。Constructor内部でprototypeを定義すると暗黙参照とは別物になってしまう。 var Class = function() { this.prototype = { property : 'Class', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; }; new Class().echo(); // undefined new Class().prototype.echo(); // これは動作する。新しいprototypeというpropertyを作ってしまっている // 非推奨の書き方 Constructor内部にprototypeを記述している。 var Class = function() { this.prototype = { property : 'Class', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; return this.prototype; }; new Class().echo(); // ClassClassの継承
親クラスの継承は子クラスのprototypeにnewして定義するだけです。
//親クラス var ParentClass = function() {}; //Constructorだけ定義 ParentClass.prototype = { //prototypeはObject property : 'Parent', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; //子クラス var ChildClass = function() {}; //継承 ChildClass.prototype = new ParentClass(); ChildClass.prototype.property = 'Child'; new ChildClass().echo(); //Child上の例だと子Class側でChild.prototype.〜という定義を繰り返し記述しなければなりません。私はそれが面倒だと思うので無名関数とapplyを使ってChildClass.prototypeに追加します。
//親クラス var ParentClass = function() {}; //Constructorだけ定義 ParentClass.prototype = { //prototypeはObject property : 'Parent', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; //子クラス var ChildClass = function() {}; //継承 ChildClass.prototype = new ParentClass(); (function(){ this.property = 'Child'; this.call = function() { alert( 'Call ' + this.property ); }; }).apply( ChildClass.prototype ); new ChildClass().call();本当は上の無名関数とapplyを使った例を以下のように書きたいのですがerrorが出ます。
//親クラス var ParentClass = function() {}; //Constructorだけ定義 ParentClass.prototype = { //prototypeはObject property : 'Parent', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; //子クラス var ChildClass = function() {}; //継承 ChildClass.prototype = new ParentClass(); (function(){ return { property : 'Child', call : function() { alert( 'Call ' + this.property ); }, }; }).apply( ChildClass.prototype ); new ChildClass().call(); //callが定義されていないというerrorが出ます。Singletonで記述
一つのinstance生成で実行できるClassはnewにより余計なinstanceを生成させないようにします。Singletonはnewされても同一のObjectを返すようにClass側で制御するデザインパターンです。JavaScriptでも簡単に記述できます。arguments.calleeを利用するだけです。2回instanceを生成しようとしても同じものがClassから返されるので比較してみると同一のものを示します。
// SingletonClassの定義 var SingletonClass = function() { if( arguments.callee.singletonInstance ) { return arguments.callee.singletonInstance; } arguments.callee.singletonInstance = this; }; // prototype SingletonClass.prototype = { property : 'Singleton Class', echo : function() { alert( this.property ); }, }; var i = new SingletonClass(); var i2 = new SingletonClass(); alert( i === i2 ); //trueObject.createによる生成
newの要/不要が色々なブロブで議論されていますが、crockfordさんが言うようにnewを出来る限り避けるという方針を考えます。ECMAScript 5th EditionからObject.createというメソッドが利用できるようになっています。これを基にObjectを生成します。Object.createのサポートが各種ブラウザで異なるので、定義されていない場合は自前で準備した関数を利用するように切り替えます。Object.createメソッドにClass.prototypeを入れます。
if (!Object.create) { Object.create = function (o) { if (arguments.length > 1) { throw new Error('Object.create implementation only accepts the first parameter.'); } function F() {} F.prototype = o; return new F(); }; } var Class = function(){}; Class.prototype = { //prototypeはObject property : 'Class', //propertyを定義 echo : function() { //methodを定義 alert( this.property ); }, }; Object.create( Class.prototype ).echo();気をつけて欲しいのが上で紹介したSingletonパターンに対してObject.createを挟んでしまうと別Instanceとなってしまいます。上の例では生成instanceの比較が同じでしたがObject.createを入れるとfalseとして認識されます。原因はObject.create自体がSingleton化していないだけだと思います。
// SingletonClassの定義 var SingletonClass = function() { if( arguments.callee.singletonInstance ) { return arguments.callee.singletonInstance; } arguments.callee.singletonInstance = this; }; // prototype SingletonClass.prototype = { property : 'Singleton Class', echo : function() { alert( this.property ); }, }; var i = Object.create( SingletonClass.prototype ); i.echo(); var i2 = Object.create( SingletonClass.prototype ); i2.echo(); alert( i === i2 ); //falseprototypeによる機能拡張
prototypeを利用すると既存Objectの機能を拡張する事が可能です。例えば配列に対して合計値を計算するようなメソッド(sum)を追加します。
Array.prototype.sum = function() { var sum = 0; for(var i = 0; i < this.length; i++ ) { sum += this[i]; } return sum; }; var a = [0,1,2,3,4]; alert( a.sum() ); // 10thisについて
thisを指し示すものが非常にややこしいのがJavaScriptの特徴でもあります。結論を簡単に言うとthisを読み込む箇所によって全く性質が異なるものを示します。thisが参照するパターンは大きく分けて2つで、イベント発生源のObjectかObject自身のインスタンスです。何も定義されていない、もしくは関数の内部でthisを呼び出すとDOMWindowを示します。ClassやObject内部でthisを呼び出すと自身のインスタンスを示します。
alert( this ); //object DOMWindow (function(){ alert( this ); //object DOMWindow })(); var Class = function(){}; Class.prototype = { //prototypeはObject property : 'Class', //propertyを定義 echo : function() { //methodを定義 alert( this ); }, }; Object.create( Class.prototype ).echo(); //object object var SampleObject = { property : 'Object!', echo : function() { alert( this ); }, }; Object.create( SampleObject ).echo(); //object object
html5のcanvasを使ってブラウザ上でのお絵描きやニコニコ動画風テロップを実装する
概要
html5のcanvasで遊んでみます。canvasの2dは学生の頃から使ってGoogleMap上にお絵描きできるシステムを作りました。またcanvasを巧く使えばニコニコ動画風のテロップも作れると思って今回実装してみました。次回は3dに挑戦したいです。
ブラウザ上でのお絵描き
仕組み
作り方は非常に簡単でhtmlにcanvasタグを埋め込み、それをJavascriptでdocument.getElementById( 'canvas' ).getContext( '2d' );とするだけでcanvasの2dオブジェクトが操作できます。mousedown,mousemove,mouseupのイベントを追加して、downしたら描画開始/moveしたら軌跡を残す/upしたら描画終了という処理の流れになります。当然の事ながらマウスのポイントを取得する必要がありevent.clientX/event.clientYで取得します。canvas上に軌跡を残すにはマウスポイントの一つ前の座標と新しい移動座標をからmoveTo、lineTo、strokeといったcanvasの2dオブジェクトが持っているメソッドを利用するだけです。jsも全部で50行ぐらいです。必要なことを箇条書きでも記します。
項目 簡易コード canvas2dの取得 document.getElementById( 'canvas' ).getContext( '2d' ); マウスイベント追加 addEventListener( 'mousedown', Canvas.mousedown, false);
addEventListener( 'mousemove', Canvas.mousemove, false);
addEventListener( 'mouseup' , Canvas.mouseup , false);マウスポイント取得 { x:e.clientX, y:e.clientY }; canvasに軌跡を書く var can = document.getElementById( 'canvas' ).getContext( '2d' );
can.beginPath();
can.moveTo(last.x, last.y);
can.lineTo(new.x, new.y);
can.closePath();
can.stroke();ソースコード
html,javascriptともにgithubに上げました。
(function(){ Canvas.init = function(){ addEventListener( 'mousedown', Canvas.mousedown, false); addEventListener( 'mousemove', Canvas.mousemove, false); addEventListener( 'mouseup' , Canvas.mouseup , false); }; Canvas.getObject = function() { return document.getElementById( 'canvas' ).getContext( '2d' ); } Canvas.getPosition = function(e) { return { x:e.clientX, y:e.clientY }; }; Canvas.mousedown = function(e) { Canvas.attr.drawing = true; Canvas.attr.lastposition = Canvas.getPosition(e); }; Canvas.mouseup = function(e) { Canvas.attr.drawing = false; }; Canvas.mousemove = function(e) { if( Canvas.attr.drawing === false ) { return false; } Canvas.attr.position = Canvas.getPosition(e); var can = Canvas.attr.Object; can.beginPath(); can.moveTo(Canvas.attr.lastposition.x, Canvas.attr.lastposition.y); can.lineTo(Canvas.attr.position.x, Canvas.attr.position.y); can.closePath(); can.stroke(); Canvas.attr.lastposition = Canvas.attr.position; }; Canvas.attr = {}; Canvas.attr.drawing = false; Canvas.attr.lastposition = { x:0, y:0 }; Canvas.attr.position = { x:0, y:0 }; Canvas.attr.Object = Canvas.getObject(); Canvas.attr.Object.strokeStyle = 'black'; Canvas.attr.Object.lineWidth = 10; })();
ニコニコ動画風テロップ
仕組み
手書きの件よりも少し複雑で文字列の描画とその消去を繰り返す実装を行いました。描画と消去の呼び出しをsetIntervalで定期的に関数を呼び出すのですが、setInterval関数の第二引数の間隔が重要になります。短くした方が描画がスムーズで今回は0.025秒間隔で文字列再描画のx座標を減らしていきます。文字列をcanvas上に描画するためにfillStyleプロパティとfillTextメソッドを利用します。また消去するためにはclearRectメソッドにてcanvas上のデータ全部を削除します。箇条書きで項目を記載します。
項目 簡易コード canvas2dの取得 document.getElementById( 'canvas' ).getContext( '2d' ); 文字列描画 var can = document.getElementById( 'canvas' ).getContext( '2d' );
can.fillStyle = 'black';
can.fillText( text, text.start.x - text.offset , text.start.y );文字列消去 var can = document.getElementById( 'canvas' ).getContext( '2d' );
can.fillStyle = 'white';
can.clearRect( 0, 0, 800, 800 );ソースコード
html,javascriptともにgithubに上げました。
(function(){ Nico.init = function() { if( Nico.attr.timer ) { clearInterval( Nico.attr.timer ); } Nico.attr.timer = setInterval( 'Nico.play()', 25 ); }; Nico.getObject = function() { return document.getElementById( 'canvas' ).getContext( '2d' ); }; Nico.play = function() { Nico.remove(); for( var i=0; i<=Nico.attr.text.length-1; i++ ) { var data = Nico.attr.text[i]; Nico.attr.Object.fillStyle = 'black'; Nico.attr.Object.fillText( data.node, data.start.x - data.offset , data.start.y ); Nico.attr.text[i].offset += 7; } }; Nico.remove = function() { Nico.attr.Object.fillStyle = 'white'; Nico.attr.Object.clearRect( 0, 0, 800, 800 ); }; Nico.attr = {}; Nico.attr.Object = Nico.getObject(); Nico.attr.Object.font = '40pt Arial'; Nico.attr.text = Array(); for( var i = 0; i<=10; i++ ) { Nico.attr.text[i] = {}; var prefix = ''; for( var j = 0; j<=i; j++ ) { prefix += ' '; } Nico.attr.text[i].node = prefix + "(´;ω;`)"; Nico.attr.text[i].start = { x:800, y:80*i }; Nico.attr.text[i].offset = 0; } })();
JavaScript Ajax : XmlHttpRequestのLevel2でSameOriginPolicyを回避する
クロスドメイン制限の回避について
- 今まではXHR(XmlHttpRequest)の仕様によりJSを読み込んでいるHTMLファイルがあるサーバから異なるドメインサーバへのAjaxリクエストが制限されていました。Same Origin Policyと呼ばれているものです。Same Origin Policyの役割としては悪意のあるscriptが個人情報等を他のサイトに転送する事を防ぐためです。このセキリティ制限を回避するために多くの人が代表的なJSONP(JSON with Padding)を利用してサーバサイドでクライアントのコールバック関数をechoしてクライアント側で実行されることにより、クロスドメイン間のAjax通信をそれっぽく動くように対応していたと思います。
- JSONPについては以前記事を書いたので宜しければ参照してください。20秒で理解するJSONP - Yuta.Kikuchiの日記
- JSONPを利用するのは少し面倒でコールバック関数をリクエストされたサーバサイドから返却しないといけなく、またセキュリティ面でも不安で十分に気をつける必要があります。JSONP以外にもリバースProxyを用意する、Flashを経由するとクロスドメインリクエスト可能となりますがこの面倒な事に頭を悩ませるJavascripterも多かったと思うのですが、色々と調べてみるとXHRのLevel2の仕様によりJSONPを使わずとも異なるドメイン間でのAjaxリクエストが可能となりそうです。
制限を回避するために
Access-Control-Allow-Origin
- HTTP access control - MDN にあるようにAccess-Control-Allow-OriginというHttpResponseHeaderを仕込むとクロスドメインを回避する事ができるようです。このHttpResponseHeaderをどこに仕込むのか?ということが疑問になりそうですが、Ajaxリクエスト先のAPIサーバに設置します。(リクエスト元に設定する方式だとXSRFなどが好き勝手出来てしまうのでそれは無いですね)
- Access-Control-Allow-Originにリクエストを受け付けるURLを指定、もしくは*(ワイルドカード)を指定すると全てのURLを受け付ける設定になります。
関連Header一覧
- 色々なブラウザ仕様があるのでAccess-Control-Allow-Originだけでなく、念のため以下のResponseHeaderも設定しておくと動作が確認できるようです。
Demo
今回のAPI/ClientともにサンプルコードをGitHubに挙げました。https://github.com/yutakikuchi/JS/tree/master/crossdomain
API Server
- アクセスするAPIサーバをGoogle App Engineに置きます。上で説明したHeaderを仕込み、とりえあずは簡単な文字列(CrossDoaminRequest)だけをprintします。
#!/usr/bin/env python # -*- coding: utf-8 -*- from google.appengine.ext import webapp from google.appengine.ext.webapp import template, util class CrossDomain(webapp.RequestHandler): def get(self): self.response.headers[ 'Content-Type' ] = 'text/plain' self.response.headers[ 'Access-Control-Allow-Origin' ] = '*' self.response.headers[ 'Access-Control-Allow-Methods' ] = 'GET' self.response.headers[ 'Access-Control-Allow-Headers' ] = '*' self.response.headers[ 'Access-Control-Allow-Age' ] = '86400' self.response.out.write( 'CrossDoaminRequest' ) def main(): application = webapp.WSGIApplication([('/CrossDomain', CrossDomain)]) util.run_wsgi_app(application) if __name__ == '__main__': main()API出力Header
- 設定したAPI(http://mobiles-proxy.appspot.com/CrossDomain)にRequestすると次のようなResponseHeaderが返却されます。Access-Cotroll-Allow-*というHeaderが返ってきていることが分かります。
http://mobiles-proxy.appspot.com/CrossDomain GET /CrossDomain HTTP/1.1 Host: mobiles-proxy.appspot.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:8.0.1) Gecko/20100101 Firefox/8.0.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7 Connection: keep-alive HTTP/1.1 200 OK Cache-Control: no-cache Content-Type: text/plain Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: * Access-Control-Allow-Age: 86400 Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Encoding: gzip Vary: Accept-Encoding Date: Sat, 25 Feb 2012 08:29:03 GMT Server: Google Frontend Content-Length: 38Request Client
- IEでも動くようにXDomainRequestとXMLHttpRequestをそれぞれ使い分けるようなコードを書きます。色々と試してみたところクライアント側でAPI ServerのResponseを取得した時にAccess-Control-Allow-Originの値にて許可されたサイトであることを判定しているようです。
<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8"> <title>CrossDomainAjax</title> <script> var Request = function() { var url = 'http://mobiles-proxy.appspot.com/CrossDomain'; var XHR; // IE対策 if( window.XDomainRequest ) { XHR = new XDomainRequest(); XHR.onload = function(){ echo( XHR.responseText ) }; XHR.open( 'GET', url ); XHR.send(); // IE以外 } else { XHR = new XMLHttpRequest(); XHR.onreadystatechange = function(){ if( XHR.readyState === 4 && XHR.status == 200 ) echo( XHR.responseText ); }; XHR.open( 'GET', url, true ); XHR.send(); } } var echo = function( text ) { alert( text ); } </script> </head> <body> <input type='button' name='ajax' value='CrossDomainRequest' onClick='javascript:Request();' /> </body> </html>実行検証
- いくつかのBrowserで動作確認をします。環境がMacなのでIEは試していません。
- 結果SafariはAccess-Control-Allow-Originが無くても実行が出来てしまいました。またOperaは動作しませんでした。
Browser Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Allow-Headers Access-Control-Allow-Age Safari ver 5.1.2 不要 不要 不要 不要 Chrome ver 17.0.963.56 要 不要 不要 不要 FireFox ver 10.0.2 要 不要 不要 不要 Opera ver 11.61 - - - -
まとめ
- JSONPを用いなくてもXHR Level2を利用するとCrossDomainRequestが可能です。
- XHR Level2を利用するにはAccess-Control-Allow-OriginというResponseHeaderをアクセス先のサーバが返す必要があります。
- IEで動作させるにはXDomainRequestオブジェクトを生成します。
- 各種ブラウザによって挙動が異なり、SafariはAccess-Control-Allow-Originさえ無くても動き、Operaは未対応です。
- 今回の検証サンプルコードを以下に設置しました。https://github.com/yutakikuchi/JS/tree/master/crossdomain
HerokuでNodeJSを動かしてみる
前提
- Herokuとは?
- rubyやnodejsのPaas。ファイルのDeployでアプリケーションを作成することができる。
- Googleが提供しているGAEやWindowsのAzureみたいなもの。
- 少し前にSalesforce.comに買収されている。
- Herokuのアカウント登録は次のURLから https://api.heroku.com/signup
- GitHubでソースを開発/管理して、動作させるのはHerokuサーバ。これが素晴らしい。
- アプリケーションサーバを1プロセス(dyno)を利用できる。dynoとはHeroku独自の単位。
- PostgreSQLを5MBまで利用可能。
- この記事の紹介はHerokuのアカウントを保持していること、自分のPCにgem(Rubyのパッケージインストールコマンド),node,npmが入っている事を前提とする。(node,npmあたりは以前記事を書いた Node.jsでWebSocketを試してみる - Yuta.Kikuchiの日記 を参照して欲しい)
- GitHubに対する設定と知識も必要。以前Macでの設定を書いた。MacユーザのためのGitHub登録手順 - Yuta.Kikuchiの日記
- 私が試した環境はMacOSX。バージョン10.6.8
Herokuパッケージ設定手順
Herokuのinstall
$ sudo gem update --system $ sudo install herokuアプリ一覧を表示
まだ何も登録していないのでYou have no apps.と表示される。
$ heroku list Enter your Heroku credentials Email: hoge@gmail.com Password: Found existing public key: /Users/YutaKikuchi/.ssh/id_rsa.pub Uploading ssh public key /Users/YutaKikuchi/.ssh/id_rsa.pub You have no apps.
必要なファイル
ファイル構成
.git .gitignore Procfile socketchat.js node_modules package.jsonProcfile
- 新しいHerokuの環境となったCedarから取り入れれたファイル。以下のように記述
web: node socketchat.jspackage.json(Nodeを使う場合に必要)
- アプリの説明やNPMの依存を記述する。
{ "name": "socketchat", "version": "0.0.1", "dependencies": { "express": "2.2.0" } }実行するNodejsファイル
- socketchat.jsというファイルにて作成。
- とりあえず動く事を確認したいのでHerokuにあるサンプルプログラムをそのまま記述する。
var express = require('express'); var app = express.createServer(express.logger()); app.get('/', function(request, response) { response.send('Hello World!'); }); var port = process.env.PORT || 3000; app.listen(port, function() { console.log("Listening on " + port); });
GitHubへの登録
localhostで動作確認
- expressを使うのでnpmでinstallしておく
$ sudo npm install express mime@1.2.4 ./node_modules/express/node_modules/mime qs@0.4.0 ./node_modules/express/node_modules/qs formidable@1.0.8 ./node_modules/express/node_modules/connect/node_modules/formidable connect@1.8.2 ./node_modules/express/node_modules/connect express@2.2.0 ./node_modules/express
- 起動。http://localhost:3000/にHello World!が出力される。
$ node socketchat.js Listening on 3000GitHubにコミット
- GitHubに登録。
$ git init $ git add . $ git commit -m "init" $ git remote add origin git@github.com:yutakikuchi/Node.git $ git push -u origin master
- GitHubにUPしたソースは次のもの https://github.com/yutakikuchi/Node
HerokuへDeploy
Herokuにアプリケーションを作成
- socketchatという名前で登録する。
- --stack cedarとすると
.heroku.com というドメインではなく、.herokuapp.com という名前で作成される。$ heroku create socketchat --stack cedar Creating socketchat... done, stack is cedar http://socketchat.herokuapp.com/ | git@heroku.com:socketchat.git Git remote heroku addedHerokuにアプリケーションをDeploy
$ git push heroku master Counting objects: 10, done. Delta compression using up to 2 threads. Compressing objects: 100% (7/7), done. Writing objects: 100% (10/10), 1022 bytes, done. Total 10 (delta 1), reused 0 (delta 0) -----> Heroku receiving push -----> Node.js app detected -----> Fetching Node.js binaries -----> Vendoring node 0.4.7 -----> Installing dependencies with npm 1.0.94 express@2.2.0 ./node_modules/express ├── qs@0.4.0 ├── mime@1.2.4 └── connect@1.8.2 Dependencies installed -----> Discovering process types Procfile declares types -> web -----> Compiled slug size is 6.9MB -----> Launching... done, v4 http://socketchat.herokuapp.com deployed to Heroku To git@heroku.com:socketchat.git * [new branch] master -> master
次回
Nodeを書いてもう少しちゃんと動くものを提供する予定。