チェ・ゲバムラの日記

脱犬の道を目指す男のブログ

【JS】繰り返し利用するコードを一箇所にまとめる(関数定義、スコープ、クロージャ、高階関数)〜JavaScript本格入門 Chapter4〜

4-1.関数とは

関数定義の3つの方法がある

functionで定義

function triangle(base, height) {
return base * height / 2;
}

document.writeln('三角形の面積:' + triangle(5,2)); //5

名前は動詞+名詞の形式が一般的。
showMessageなど。

Functionコンストラクタ経由で定義

→基本的に非推奨。

関数リテラル表現で定義

var triangle = function(base, height){
return base * height /2;
};
document.writeln('三角形の面積:' + triangle(5,2)); //5

function命令と関数リテラルの違い(似ている)

・function命令
→関数triangleを直接定義している

・関数リテラル
名前のない関数(function)を定義した上で変数triangleに代入している。
名前のない関数を匿名関数と呼ぶ。


4-2 関数定義の4つ注意点

return命令は途中改行しない

Javascriptではセミコロンを打たなくても改行で勝手に解釈してしまうため、おかしな実行となる。

関数はデータ型の一種

変数に関数を代入して、その後普通の数字などを代入してもエラーとはならない。
※他言語とは違う点。

function命令は静的な構造である

関数を宣言する前にdocument.writelnなどで関数を呼び出しても、エラーとはならない。
↓下記は正常に動作する
document.writeln('三角形の面積:' + triangle(5,2));
function triangle(base, height){
return base * height /2;
};

関数リテラル/Functionコンストラクタは実行時に評価される

↓下記はエラーとなる
document.writeln('三角形の面積:' + triangle(5,2));
var triangle = function(base, height){
return base * height /2;
};


4-3 変数はどの場所から参照できるかースコープー

グローバルスコープとロールカルスコープ2種類ある

グローバルスコープ
スクリプト全体から参照可能

ローカルスコープ
→定義した関数内でのみ参照可能

変数宣言にvarは必須

scope = 'global';

function getVallue(){
scope = 'local';
return scope;
}
こうするとscopeはlocalとなり上書きされる。
つまりローカル変数を定義するときは必ずvarを使うこと。

ローカル変数の有効範囲

scope = 'global';

function getVallue(){
document.writeln(scope); // エラー(undefined)
var scope = 'local';
return scope;
}
この場合はローカル変数が確保されてはいるが、var命令は実行されていないため未定義エラーとなる。
つまり、ローカル変数は関数の先頭で宣言すること。

仮引数のスコープ

仮引数とは「呼び出し元から関数に対して渡されたパラメータを受け取る変数」のこと。
基本的にはローカル変数扱い。
function triangle(base, height){..........}
→この場合base,heightが仮引数

動作確認(基本型)
var value = 10; //global変数
function decrementValue(value) {
value--;
return value;
}
document.writeln(decrementValue(100)); //99 ローカル変数のためグローバル変数には影響なし
document.writeln(value); //10 影響ないためそのままグローバル変数が表示


動作確認(参照型)
var value = [1,2,4,8,16];
function deleteElement(value) {
value.pop(); //末尾の要素を削除
return value;
}
document.writeln(deleteElement(value)); // 1,2,4,8 グローバル変数valueの値を仮引数valueに渡されている
document.writeln(value); // 1,2,4,8

参照型とは、値そのものではなく、値を格納したメモリ上のアドレスを格納している型。
つまり、グローバル変数で定義されたvalueと関数で定義された仮引数valueは変数としては別物だが、
deleteElementでグローバル変数valueが仮引数valueに渡された時点で結果的に参照しているメモリ上の場所が等しくなる。

ブロックレベルのスコープは存在しない

Javascriptではブロックスコープはない。
下記はJavaとかならエラーとなるが、Javascriptでは正常動作する。
if(true){
int i = 5;
}
System.out.println(i); //5

関数リテラル/ Functionコンスタラクタにおけるスコープの違い

var scope = 'global';

function checkScope(){
var scope = 'local';

var f_lit = function(){ return scope;}; //関数リテラル
document.writeln(f_lit()); // local

var f_con = new Function() { 'return scope;' }; //functionコンストラクタ
document.writeln(f_con()); // global
}
checkScope();

4-4 引数情報を管理する argumentsオブジェクト

関数内で利用可能な特別なオブジェクト

Javascriptは引数の数をチェックしない

ある関数に対して、関数の仮引数は1つに対して、呼び出し元は2つの引数があった場合、
最初に渡された引数しかチェックしない。
2つ目は無視されたように感じるが、実際はargumentsオブジェクト内に保存されて利用可能。

function showMessage(value) {
if(arguments.length != 1){
throw new Error('引数の数が間違っています:' + arguments.length);
}
document.writeln(value);
}

try {
showMessage('山田','鈴木');
} catch(e) {
window.alert(e.message);
}

結果
アラートが上がり、引数の数が間違っています:2と表示される。
※これ以外にも、データ型や値の有効範囲などもチェックできる。

補足
Javascriptでは引数の省略が可能であるが、省略したらおおよそ動かない事が多いため、
未定義の場合にデフォルト値を設定するようにすることが望ましい。

可変長引数の関数定義

呼び出し元から渡された引数の数にが何個でも処理する場合。合計値など。

function sum(){
var result = 0;

  for(var i =0; i < arguments.length; i++) {
    var tmp = arguments[i];
    if(isNaN(tmp)){
      throw new Error('指定値が数値ではありません:' + tmp);
    }
    result += tmp;
  }
  return result;
}

try{
  document.writeln(sum(1,3,7,9));
} catch(e) {
  window.alert(e.message);
}

明示的に宣言された引数と、可変長引数を混在させる

両者の混在も可能。

再帰呼び出し定義

現在実行中の関数自身を参照する為のcalleeプロパティがある。
例 与えられた自然数nの階乗を求める。
function factorial(n) {
if(n != 0) { return n * arguments.callee(n -1); }
return 1;
}
document.writeln(factorial(5)); //120

結果
→5*arguments.callee(4)
→5*4*arguments.callee(3)



のように再帰呼び出しされる。
因数分解すると→5*4*3*2*1=120ということになる。

4-5 高度な関数のテーマ

ここは基本より上ステップとのこと。
一応メモ程度に記載しておく。

名前付き引数でコードの可読性アップ

triangle( { base:5, height:4 } );

メリット
・引数が多くなっても意味が分かりやすい
・省略可能な引数をスマートに表現
・引数の順番を自由に変更可能
特に下記のような場合により有効。
・引数の数が多い
・省略可能な引数が多く、省略パターンにも組み合わせが多数ある

関数の引数も関数 高階関数

//高階関数
function arrayWalk(data, f){
  for(var key in data){
    f(key,data[key]);
  }
}

//配列処理用ユーザ定義関数
function showElement(key,value){
  document.writeln(key + ':' + value);
}

var ary = [1,2,4,8,16];
arrayWalk(ary, showElement);

結果
0:1
1:2
2:4
3:8
4:16

メリット
arrayWalk関数は一切書き換えず、大枠の機能だけを定義しておいて、
詳細な機能は関数利用者が自由決定することが可能。

使い捨て関数は匿名関数(関数リテラル)で

その場限りしか利用されない場合はいちいち名前つけずに関数定義したほうがすっきりする。

クロージャ

ローカル変数を参照している関数内関数のこと
一種の記憶域を提供するしくみといえる。

function closure(init) {
 var counter = init;
 
 return function() {
    return ++counter;
 }
}

var myClousure = closure(1);
document.writeln( myClosure() ); //2
document.writeln( myClosure() ); //3
document.writeln( myClosure() ); //4

通常、関数内で使われたローカル変数counterは関数処理終了後に破棄されるが、
closure関数から返された匿名関数がローカル関数counterを参照し続けている為、
closure関数終了後も変数は保持され続ける。(一種の記憶域を提供していることになる)

例2 つまり下記のように書くと、あたかも違う変数を参照しているかのように動く。

function closure(init) {
  var counter = init;
 
  return function() {
    return ++counter;
  }
}

var myClousure1 = closure(1);
var myClousure2 = closure(100);
document.writeln( myClosure1() ); //2
document.writeln( myClosure2() ); //101
document.writeln( myClosure1() ); //3
document.writeln( myClosure2() ); //102