月別アーカイブ: 2014年5月

初めてのGitとGitHub

なんとなくGitを始めようと思ってGitHubに登録したり色々と設定したのでその経緯を忘れないうちに書いておく。

Gitとはバージョン管理システムで、リポジトリと呼ばれる保管庫に更新したプロジェクトのファイルをどんどん追加(コミット)していく。更新履歴を残すことによって後から巻き戻したり管理するのが容易になるというわけである。リポジトリにはローカルリポジトリとリモートリポジトリがあり、ローカルリポジトリはそのままローカルでプロジェクトを管理していく。リモートリポジトリはどこか別のネットワーク上でプロジェクトを管理していく。GitHubはリモートリポジトリとして使えるサービスの一つ。GitHubでプロジェクトを管理することによってどのPCからでもプロジェクトを管理できるので便利。始めはファイルサーバーのようなものと考えればわかりやすい。他にもGitHubには自分だけでなく誰かが自分のプロジェクトをコピーして変更したり、自分が誰かのプロジェクトに加わることもできる。というかそれが目玉のサービスにようだが、今は一旦置いておく。

Gitをインストール

まず、GitHubを使うにはGitHubに登録して、Gitをインストールする必要がある。今回使ったOSはLinux(Ubuntu14.04)とWindows。WindowsはGitHub for Windowsを使えばGUIで色々操作できたり、Gitが入ったターミナルまで用意してくれたり、GitHubに接続するためのsshのkeyまで作成してくれて至れり尽くせりなのでこれを使う。これを使わないならLinuxを使ったほうが楽だと思う。Ubuntuはターミナルに次のコマンドを入力してインストール。

sudo apt-get install git

Gitの初期設定

Gitをインストールしたら、まずユーザー名とメールアドレスを設定する。ユーザー名はコミットした時などに使われる。メールアドレスはGitHubで登録したものと同じものを設定する。次のコマンドを実行する。

git config --global user.name "ユーザー名"
git config --global user.email "メールアドレス(GitHubで登録したものと同じ)"

ついでに次のようなコマンドも実行するとGitのターミナルの出力に色づけするようになるので設定しておくといいらしい。

git config --global color.ui true

設定した内容はgit config -lで見ることができる。

ローカルリポジトリの作成

とりあえずローカルでGitを使ってみる。まず始めに、Gitを使う時の流れをおさえておく。

  1. 作業ディレクトリでファイルを作ったり修正したりする。
  2. そのファイルをステージングエリアに加える。
  3. ステージングエリアに加えたファイルをコミットする。

コミットすることでファイルがデータベースに追加されて、後々修正を加えてもまた元に戻ったりすることができるというわけである。

まず、プロジェクトの作業ディレクトリを作成する。ここではホームディレクトリにHelloという名前で作ってみる。

mkdir ~/Hello
cd ~/Hello

Helloディレクトリをリポジトリとして使うには次のコマンドを実行する。

git init

これでHelloディレクトリはGitディレクトリになり、以後ローカルリポジトリとして使えるようになる。

次に、「Hello!」という内容のHello.txtファイルを作ったと仮定して、このHello.txtファイルを次のコマンドでステージングエリアに加える。コミットするにはでステージングエリアを経由する必要がある。

git add Hello.txt

これでHello.txtはコミットできるようになる。コミットするには次のコマンドを実行する。

git commit -m "初めてのコミット"

こうすることによって、Hello.txtファイルを後で修正し再びコミットしても、今コミットした「Hello!」という内容の状態に戻ることができる。ちなみにこの「-m」というのはメッセージを追加するオプションで「”初めてのコミット”」というのがメッセージである。後で見た時になぜコミットしたのか、どこを変更したのかなどわかるようなメッセージにする。

ローカルでGitを使ってみたが、GitHubでも同じような感じで管理する。次はGitHubのリモートリポジトリを使ってみる。その前にGitHubとはSSHで接続する必要があるため、まずSSH鍵を作る必要がある。

SSH鍵の作成と設定

まずSSH鍵を作る前に公開鍵暗号方式についてさらっと説明。公開鍵暗号方式には公開鍵と秘密鍵があり、公開鍵で暗号化、秘密鍵で復号化する。秘密鍵は自分だけの秘密にしておき、公開鍵は通信相手に渡す。通信相手は渡された公開鍵を使ってデータを暗号化し、そのデータを自分だけ持っている秘密鍵で復号化することによって通信のセキュリティを高めるというわけである。ここでは、GitHubに公開鍵を渡す。

SSH鍵は次のコマンドで作成する。

ssh-keygen -t rsa -C "任意のコメント(メールアドレスなど)"

「-t」というオプションでrsaという鍵のタイプを指定する。「-C」というのはコメントをつけるオプションで任意のコメント(メールアドレスなど)をつける。。コマンドを実行すると

Enter file in which to save the key (/home/ユーザー名/.ssh/id_rsa):

と表示されるのでEnterを押す。次にパスフレーズ(パスワードのようなもの)を聞かれるので適当なパスフレーズを入力する。確認でもう1回同じパスフレーズを入力する。こうして鍵が作成される。作成された鍵はホームディレクトリの.sshディレクトリ内にあるので.sshに移動する。lsコマンドで中身を見るとid_rsaid_rsa.pubというファイルがある。id_rsaというのが秘密鍵でid_rsa.pubが公開鍵である。つまり、id_rsa.pubの中身をGitHubに渡す。

id_rsa.pubの中身は次のように表示してクリップボードにコピーする。

cat ~/.ssh/id_rsa.pub
# 表示された内容をctrl+shift+cか右クリックでクリップボードにコピー

GitHubのSSHkeysに行き「Add SSH key」をクリックし、適当なタイトルをつけて「key」にコピーした内容を貼り付け「Add key」をクリックしてGitHubのパスワードを入力する。これでGitHubとSSHで通信できるようになる。正しく通信できているかは次のコマンドを打つことによって確かめられる。

ssh -T git@github.com

SSHで通信していいか確認が入るので「yes」と入力し、次にパスフレーズが聞かれるのでパスフレーズを入力する。以下のメッセージが表示されたらGitHubと正常にSSH通信できている。

Hi username! You've successfully authenticated, but GitHub does not
# provide shell access.

これでようやくGitHubと通信できるようになった。もし通信に失敗しているようであれば、もう一回鍵を作りなおすところから始めるかGenerating SSH Keysを参照。次はGitHubのリモートリポジトリにpushしてみる。

GitHubのリポジトリにpushする。

始めにGitHubにリポジトリを作成する。トップページにある「+ New repository」ボタンをクリックして適当なプロジェクト名と適当な説明を入力して「Create repository」ボタンをクリックする。ここでは先ほどローカルに作成したHelloプロジェクトを作成したと仮定する。ちなみにGitHubの無料プランはプロジェクトを非公開にすることはできない。

試しに先ほどローカルで作ったHelloプロジェクトをGitHubのリポジトリにpushしてみる。まずGitHubのリモートリポジトリにSSHで接続するための設定を追加する。

git remote add origin git@github.com:"GitHubのユーザー名"/Hello.git

originというのはリモートリポジトリのあるサーバー名に接続方式を加えたリモート名(ここではgit@github:"GitHubのユーザー名"/Hello.git)である。ドメインを省略したようなものと捉えるとわかりやすい。別にoriginではなく別の名前を使ってもいいが、デフォルトはoriginなのでここではoriginという名前にしておく。ちなみにSSL(https)で接続するための設定も追加できる。

git remote add origin2 https://github.com/"GitHubのユーザー名"/Hello.git

SSLだとpushするたびにユーザー名とパスワードを聞かれて面倒くさい。キャッシュを使う方法もあるが、基本的にSSLで接続する。ちなみにここではリモート名にorigin2という名称を与えている。

pushするには次のコマンドを実行する。

git push origin master

originは先ほど設定したリモート名なのでSSL接続でGitHubのHelloプロジェクトにpushしたということになる。masterというのはブランチ名で、ブランチというのはプロジェクトの中で履歴の分岐を記録するための機能。別のサイトに簡単な説明と公式サイトに詳しい説明が書いてある。GitHubの自分のページに行ってみるとHello.txtがpushされたのを確認できる。感動。大体こんな感じでリモートリポジトリでプロジェクトを管理していける。

リポジトリを削除する。

一応GitHubのリモートリポジトリを削除する方法も書いておく。まず、削除したいリポジトリのページに行き右側にある「settings」の項をクリックして、下にある「Danger Zone」にある「Delete this repository」ボタンをクリックする。削除したいリポジトリ名を聞かれるので入力して削除する。

以上、長々とまとまりなく書いてしまったが、大体の流れを掴めれば後はgitのコマンドを調べたり公式サイトを見ながらやっていけると思う。ちなみにGit公式にあるProGitという本が自分のような初心者でも非常にわかりやすく書いてあるので、これからProGitを見ながら色々と弄っていきたい。

【JavaScript】script要素<script>を動的に実行する

innerHTMLでscript要素を追加したり、既に実行されたscript要素をcloneNodeでコピーしてそれを追加しても処理は実行されない。script要素を実行するには、createElementで新たにscript要素を作り、appendChildで追加する必要がある。

例えば、Ajaxなどでhtmlを書き換える時、同時にscript要素にある処理も実行したい場合、新たにscript要素を作ってそこに実行したい処理をコピーしなければいけない。そういう時に使えるスクリプトを書いた。

function cloneScript(element) {
	var script = document.createElement('script');
	var attrs = element.attributes;
	for(var i = 0, len = attrs.length; i < len; i++) {
		var attr = attrs[i];
		script.setAttribute(attr.name, attr.value);
	}
	script.innerHTML = element.innerHTML;
	return script;
}

引数に指定されたscript要素の属性と処理内容をコピーした新しく作られたscript要素を返すので、src属性で外部jsファイルを指定しているscript要素や、script要素内に直接処理の書かれているscript要素どちらでも問題ないはず。

例えば以下のようなscript要素を再度実行する場合

<script class="origin">alert('hello');<script>
var origin = document.querySelector('.origin');
document.body.appendChild(cloneScript(origin)); //'hello'とalertが鳴る;

こんな感じで使える。

【JavaScript】addEventListenerの無名関数をremoveEventListenerで消す方法

今回は、JavaScriptのaddEventListener/removeEventListenerを使う時に気をつける点を順を追って書いていく。特に無名関数をremoveEventListenerで消す方法を中心に書く。

addEventListenerは1つのターゲットにイベントリスナーを複数登録できる。例えば以下のような場合

window.addEventListener('click', function() { console.log(1); }, false);
window.addEventListener('click', function() { console.log(2); }, false);

windowをクリックすればコンソールに1と2が表示される。この第二引数をイベントリスナーと言い、関数を指定する(以下、リスナーと書く)。今はaddEventListenerの中に無名関数をそのまま定義したが、関数は以下のように分けて書いてもいい。

function f() { console.log(1); }
window.addEventListener('click', f, false);

しかし、この方法ではリスナーに引数を渡せない。リスナーに引数を渡したい場合は以下のいずれかの方法を使って書いたりする。

//1.処理したい関数を分けてリスナーの中で関数を呼び出す方法;
function f(x) { console.log(x); }
window.addEventListener('click', function(){
	f(1);
}, false);

//2.bindを使う方法(上の方法とほとんど同じ);
function f(x) { console.log(x); }
window.addEventListener('click', f.bind(null, 1), false);

//3.即時関数で処理したい関数を返す方法(たぶんメジャー);
window.addEventListener('click', (function(x) {
	return function() {
		console.log(x)
	}
})(1), false);

特にループ内で一気にイベントを定義するときに自分は3の方法をよく使う。ここで気をつけないといけないのはいずれもremoveEventListenerで削除できないという点である。また、通常であればaddEventListenerは全く同じ内容のイベントが重複すれば自動的に削除されるが、上のような方法で定義したリスナーはどこからも参照できないため自動的に消されることもない。これは、前のイベントを消して同じターゲットに違うイベントを再び定義したい場合や引数を変えて定義し直したい時に困る。強引に再び定義するとイベントがどんどん追加されてメモリリークを起こしてしまう可能性があるしイベント時の処理が増えるのでが無駄に重くなる。そこで、リスナーに引数を渡すこととremoveEventListenerで定義したイベントを消せることを両立する方法を書いていく。

イベントを消す1番目の方法は、リスナーの中でremoveEventListenerを使うという方法である。その際、リスナーには名前をつける。例えば上の3番目の方法をこう書き換えられる。

//3.即時関数で処理したい関数を返す方法の場合;
window.addEventListener('click', (function(x) {
	return function f() {
		console.log(x);
		window.removeEventListener('click', f, false);
	}
})(1), false);

ちなみにリスナーに名前を付けられない場合は、arguments.calleeを使う(arguments.calleeは非推奨)。この方法は少し使いづらい。この例だと一回イベントが発火すると同時に消える。条件分岐も使えるが、自分でイベントを消したいと思った時に必ずしも消せない。

そこで、2番目の方法。良い方法がStackOverflowにあったので少し書き換えて紹介。

var handler = (function(){
	var events = {},
		key = 0;

	return {
		addListener: function(target, type, listener, capture) {
			target.addEventListener(type, listener, capture);
			events[key] = {
				target: target,
				type: type,
				listener: listener,
				capture: capture
			};
			return key++;
		},
		removeListener: function(key) {
			if(key in events) {
				var e = events[key];
				e.target.removeEventListener(e.type, e.listener, e.capture);
			}
		}
	};
}());

まず、addEventListenerを関数で囲む。ここでは、addListenerというメソッドを定義している。addListenerの引数にイベントターゲット、イベントタイプ、リスナー、キャプチャーを指定すればそれがそのままaddEventListenerでイベントとして定義される。更にaddListenerメソッドは渡した引数を全てをオブジェクトとして一つ上のスコープのeventsオブジェクトに格納する。そして今保存したイベントのkeyを返す。次のイベントのためにkeyはインクリメントする。こうすることによって定義したイベントを参照できる。そしてこのkeyをremoveListenerメソッドに渡すとそのkeyに格納されているイベントを削除できる。ここでは、handlerという変数にaddListenerメソッドとremoveListenerメソッドが入ったオブジェクトを返している。以下のように使う。

//イベントを定義すると同時に返ってきたkeyを取得;
var key = handler.addListener(window, 'click', (function(x) {
	return function() { console.log(x); }
})(1), false);

//イベントを消す;
handler.removeListener(key);

なんだかsetTimeoutのタイマーIDと似ている。このコードを見てわかるように引数も渡せて消すこともできる。ただ、自動的にイベントが消えるわけではないのでkeyを覚えておく必要がある。同じイベントを再び定義する時に自動的に消したいなら以下のようなコードを書ける。

var f = (function() {
	var key;
	return function() {
		//同じイベントが定義されていたら消す;
		handler.removeListener(key);
		//新しいkeyを取得;
		key = handler.addListener(window, 'click', (function(x) {
			return function() {
				console.log(x);
			}
		})(1), false);
		return key;
	}
})();

f();を何回実行しても自動的に前のイベントは消されるので重複することはない。少し読みにくいが、一応書いておく。なぜこんなことを書いているかというとループ内でaddEventListenerを使ってイベントを定義するときにどうするのがいいのか考えていたから。

上のようなコードをループ内でイベントを定義するときに適用すると以下のようなコードになる。

<p>あいうえお</p>
<p>あいうえお</p>
<p>あいうえお</p>
var f = (function() {
	var keys = [];
	return function() {
		var p = document.querySelectorAll('p');
		for(var i = 0; i < p.length; i++) {
			handler.removeListener(keys[i]);
			keys[i] = handler.addListener(p[i], 'click', (function(i) {
				console.log(i);
			})(i), false);
		}
		return keys;
	}
})();

ただ単にkeyを配列に変えただけ。ネストが深くなっているのがちと引っかかるが一例として。

最後にhandlerオブジェクトをIEのattachEventやdetachEventにも対応させたコードを書いて終わりとする。

var handler = (function(){
	var events = {},
	key = 0;

	return {
		addListener: function(target, type, listener, capture) {
			if(window.addEventListener) {
				target.addEventListener(type, listener, capture);
			}else if(window.attachEvent){
				target.attachEvent('on' + type, listener);
			}
			events[key] = {
				target: target,
				type: type,
				listener: listener,
				capture: capture
			};
			return key++;
		},
		removeListener: function(key) {
			if(key in events) {
				var e = events[key];
				if(window.removeEventListener) {
					e.target.removeEventListener(e.type, e.listener, e.capture);
				}else if(window.detachEvent){
					e.target.detachEvent('on' + e.type, e.listener);
				}
			}
		}
	};
})();