関数
JavaScriptの関数の特徴は
- 第一級オブジェクトである
- スコープを提供する
第一級オブジェクト(Wikipediaより)
たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。
JavaScriptの場合、関数はオブジェクトとなので、オブジェクトとして扱う事ができるというのが最大の特徴ですね。
関数はスコープ提供する
Javaなどでは波括弧{ }
で囲むとその中はローカルとなるので一時的に変数を作成したりすることができたのですが、JavaScriptではそうはならないようです。
あくまでローカルを提供する波括弧は関数でだけであり、if
やfor
やwhile
で使う波括弧は ただのブロックであり、その中での変数宣言は グローバル変数となってしまうので認識をしっかり持ちましょう。(かくいう私も知らなかった汗)
その検証結果が以下のコードです。
//if文でブロック内で変数を宣言してみる
if(true){
var a = "a";
console.log("ifスコープ内のa:" + a); //ifスコープ内のa:a
}
console.log("ifスコープ外のa:" + a); //ifスコープ内のa:a
//関数宣言
function func(){
var c = "c";
console.log("funcスコープ内のc:" + c); //funcスコープ内のc:c
}
//関数の実行
func();
console.log("funcスコープ外のc:" + c); //ReferenceError: Can't find variable: c
用語の理解
結果を変数に代入する場合は、 名前付き関数式と 関数式もしくは無名関数を使って宣言します。
結果を入れず、関数のみを宣言する場合は 関数宣言のみ行います。
関数式は、式であるため、最後にセミコロンを付けます。(無くても暗黙的に付与されますが、理解する上でつけるとよいでしょう)
名前付き関数式
名前付き関数式は、宣言した関数を代入した変数のnameプロパティに関数名が設定されます。
var sum = function sum(a, b) {
return a + b;
};
console.log(sum.name); //sum
関数式、無名関数
無名関数は、宣言した関数を代入した変数のnameプロパティには何も設定されません。IEの場合はundefinedとなるようです。
var sum = function (a, b) {
return a + b;
};
console.log(sum.name); // <--空欄
関数宣言
いわゆるthe関数の宣言、という構文ですね。
function sum(a, b) {
return a + b;
}
関数の巻き上げ
変数の際同様に関数についても巻き上げがあります。宣言されたものに付いては巻き上げられますが、実装自身は巻き上げられません。
function func(){
console.log(typeof a); //function <-- 関数宣言は巻き上げられている
console.log(typeof b); //undefined <-- 関数式は巻き上げ起こらない
a();//function a <-- 関数宣言は巻き上げられている
b();//TypeError: b is not a function.
function a(){
console.log("function a");
}
var b = function(){
console.log("function b");
}
}
func();
コールバックパターン
関数はオブジェクトなので、引数に関数を渡せばその中から関数を呼び出す事ができます。この時、呼び出される関数をコールバック関数と言います。
//100を引数valで割り切れた時に引数callbackを呼び出す関数
function search(callback, val) {
//渡された引数が関数でなければメッセージを出して関数終了
if (typeof callback !== "function") {
console.log("callbackが関数ではありません。");
return;
}
//100個の数を当たる
for (var i = 1, max = 100; i < max; i++) {
//割り切れた場合は関数呼び出し
if (i % val === 0) {
callback(i);
}
}
}
//発見した時に呼び出されるコールバック関数
function find(value) {
console.log(value);
}
search(find, 7); //7で割り切れた数が出力
search(null, 3); //コールバックが無い旨のメッセージが出る
以上のコードの結果は以下となりました。
思った通りの結果となりました。
コールバックパターンを使って何か作ってみた
学習を進めないといけない…と思いながら思いついたものを作っていました。
クリックした要素のインデックスの倍数に色がつく、という実用にはあまり意味を見いだせないものですが、思いついたらOUTPUTしておいたほうが「なんかやってやったなぁ」って思えるのでそれも大事、という事で!
ソース
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<style>
#container {
margin: 0;
padding: 0;
}
#container ul {
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
#container ul li {
width: 25%;
margin: 0;
padding: 100px 0;
list-style: none;
text-align: center;
}
</style>
<body>
<div id="container">
<ul class="userlist">
<li class="list">box</li>
<li class="list">box</li>
<!-- 入れたいだけのリスト要素を入れる -->
<li class="list">box</li>
</ul>
</div>
</body>
<script type="text/javascript" src="js-playground2.js">
</script>
</html>
// 対象のli要素を取得
var targets = document.querySelectorAll("ul.userlist li.list");
// ターゲットの要素の数を取得
var targetCount = targets.length;
// クリックイベントをイベントリスナへ登録
if (document.addEventListener) { //Webkit,Mozilla
for (var i = 0, max = targets.length; i < max; i++) {
targets[i].addEventListener('click', clicked, false);
}
} else if (document.attachEvent) { //IE
for (var i = 0, max = targets.length; i < max; i++) {
targets[i].attachEvent('onclick', clicked);
}
} else { //その他
for (var i = 0, max = targets.length; i < max; i++) {
targets[i].onclick = clicked;
}
}
// クリックされた時の処理
function clicked(e) {
var src,
clickedIndex = 0;
// イベント全てのブラウザ差異を吸収
e = e || window.event;
// イベントを起こしたアイテムのブラウザ差異を吸収
src = e.target || e.srcElement;
// クリックされたアイテムを取得
clickedIndex = getClickedItemCount(src);
// コンソールにクリックされたものを出力
console.log("clickedIndex:" + clickedIndex + "(左上から数えて" + (clickedIndex + 1) + "個目)");
// コールバックの仕組みを使って関数呼び出し
search(find, clickedIndex);
}
// 対象要素の色を全て白にする
function clearColor() {
for (var i = 0, max = targets.length; i < max; i++) {
targets[i].style.backgroundColor = "#ffffff";
}
}
// クリックした要素から何番目の物か判定する関数
function getClickedItemCount(node) {
var index = 0,
prevNode = node; //現在のノードオブジェクトを格納
// 兄弟要素があるかぎり続ける
while (prevNode) {
// 対象の要素の兄要素を取得
prevNode = prevNode.previousSibling;
// 兄要素がnullでなくnodeTypeが要素ノードであればまだ兄が居る…っ!
if (prevNode !== null && prevNode.nodeType == 1) {
index++; //カウントアップ
} else if (prevNode === null) { //要素がなくなればループを抜ける
break;
}
}
return index + 1; //インデックスなので1足す
}
function search(callback, val) {
//渡された引数が関数でなければメッセージを出して関数終了
if (typeof callback !== "function") {
console.log("callbackが関数ではありません。");
return;
}
// 色の付いた所を白に戻す
clearColor();
//ターゲットの要素数を総当たり
for (var i = 0; i < targetCount; i++) {
//対象要素とvalとで割り切れた場合は関数呼び出し
if ((i + 1) % val === 0) {
callback(i);
}
}
}
//発見した時に呼び出される関数
function find(value) {
//ターゲット要素を抽出
var t = targets[value];
t.style.backgroundColor = "#00dd33";
console.log("fillColorIndex:" + value + "(左上から数えて" + (value + 1) + "個目)");
}
解説
HTML
HTMLはリスト要素を入れたいだけいれるようなシンプルなものです。
CSS
CSSはFlexを使って、横幅の計算をしなくて済む作りにしています。昔はFloatというものがあってだな、いやいやTABLEってものがあってだな…とそんな事をしなくてもいいのはとても楽ですね。
JavaScript
そしてJavaScriptの解説です。
流れとしては、 要素をクリックする→クリックされた要素のインデックスを調べる→調べたインデックスを基にその数で割って余りの無い値のインデックスを持つ要素の色を変える。というもの。
割り算の前に一旦全ての要素の背景の色を白に戻すという処理が入っていたりもします。
こういうアクションを起こして何か反応があるものが作れると面白いですね。
対象DOMを一番はじめに1回だけ変数に入れる
まず一番はじめに対象のDOM要素を変数に格納してしまいます。以後対象DOMを使う時はすべてこの変数を使って処理します。何度もDOMを呼ぶと内部ではそれだけ何度もDOMの要素を総当りして精査する事となりますので、それを避けるためです。
今回は、要素の色を変更していますが、常に一方通行で値を変化させ続けるので、このような作りで大丈夫ですが、書き換えた要素の値を再利用するような時はこのような作りでは値がおかしくなってしまうので、考えねばなりません。
状態は裏で常に作っておいて変化があれば描画するだけ、のような仕組みのReactが流行るのも理解できる気がしますね。
イベント処理
今回、まだ学習していない「イベント」を利用しています。イベントでは「クリックした時の処理」を書けるようにしています。
addEventListener
はAndroid開発でも何かイベントを追加する時は書き続けていた所です。JavaScriptも似ていますね。
JavaScriptではブラウザ毎にイベントに追加する関数が違うので、それを吸収する書き方をしています。そんなことをせずに書けるJQueryは本当に偉大だと思います。何故捨てようとするのか、何故か自分も捨てに走っていますが、何事も仕組みを理解してから有用に使おう、という事で極力JQueryは使わない方針でございます。
クリックした要素が何番目か
jQueryを使っている時はクリックした要素は何番目か、のような処理はindex()
を使用する事でものすごくカンタンにできました。
しかしJavaScriptだけでやろうとすると少し面倒なようで、兄要素はあるか、そして兄は要素ノードかどうかを判定して数を数えるといったやり方を行っています。あぁやっぱりjQueryは偉…。
コールバックで関数を呼び出す
もうすっかり忘れてしまったコールバック関数ですが、今回は引数にインデックスの与えています。持って行った先でその値を使って処理を行っています。
今回無理矢理コールバックをねじ込んだ形となっていますが、パターンを覚えるのが今回の前提なので、clicked()
関数からsearch()
関数を呼び出す際に引数でfind
を指定している、という所が一番重要な部分です。
感想
勉強をしていると、何か違う事が気になってしてしまう、というようなことがよく有りますね。掃除しちゃったりね。今回はそれに近い形で欲が出て楽しんでしまいました。
特にこの関数編、なかなかの内容なのでまずは学習を第一に考えて続けていかねばと思っています。
リンク
次回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「関数・クロージャ・即時関数編」 〜JavaScriptパターン 学習6日目【逃げメモ】〜
前回はこちら
オブジェクト指向初心者の私がJavaScriptにも再入門 「配列・JSON編」 〜JavaScriptパターン 学習4日目【逃げメモ】〜