みこむらめもむらむらむら

なんかHTML5とかJS勉強とかやりながらめもを綴るブログ

【JavaScript】高度な関数のテーマ①

さてさて関数もここまでやってきました最後の章

関数を利用したより高度なトピックの紹介とのことで
基本を!っていう方は後でもいいよ!ってなってたんだけれど
なんか周りでクロージャクロージャって
よく聞くからやってみることにするよ

補足:名前付き引数でコードを読みやすくする

名前付き引数とは、
呼び出し時に名前を明示的に指定できる引数のこと

triangle ({ base:5; height:4; })
  • 名前付き引数を用いることのメリット
    • 引数が多くなっても、コードの意味が分かりやすい
    • 省略可能な引数をスマートに表現できる
    • 引数の順番を自由に変更できる

名前付き引数を利用すれば先ほどのtriangle関数は
以下のように呼び出すことも可能

triangle ({ height:4; })  //前方の引数だけ省略
triangle ({ height:4; base:5; })  //引数の順番の変更

もちろん呼び出しに際して
明示的に名前を指定しなければならないので
コードが冗長になるというデメリットもあるが

  • そもそも引数の数が多い
  • 省略可能な引数が多く、省略パターンにもさまざまな組み合わせがある

ようなケースには有効な方法である
その時々の文脈に応じて使い分けるようにすること

名前付き引数の具体的実装方法

function triangle(args) {
  if (args.base == undefined) { args.base = 1; }
  if (args.height == undefined) { args.height = 1; }
  return args.base * args.height / 2;
}

document.writeln(triangle({base:5, height:4}));  //10

名前付き引数と云っても何ら難しいものではなく
引数を匿名オブジェクト(ここでは仮引数args)で
受け取っているだけである
メソッドの中でもオブジェクトのプロパティとして
それぞれの引数にアクセスしている点に注意
呼び出しのタイミングでも引数を「{..}」と記述しているのは
匿名オブジェクトを表しているためである

関数の引数も関数(高階関数)

JavaScriptの関数はデータ型の一種」そうね何度もやったね
つまり、関数そのものもまた、他の数値型や文字列型などと同様
関数の引数として渡したり
戻り値として返したりすることができるということ
べんりー(・∀・)
そして、そのように
「関数を引数、戻り値として扱う関数」のことを
高層関数と呼ぶ、うむ

//高階関数arrayWalkを定義
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関数は、引数に与えられた配列dataの内容を
指定されたユーザ定義関数fの規則に従って
順番に処理するための高階関数

さてここでちょっとfor..in命令の復習はさんでいいですかw

配列内の要素を順に処理する(for..in命令)

for(仮変数 in 配列/オブジェクト) {
  ループ内で実行する命令(群)
}
  • 指定された配列/連想配列やオブジェクト配下の要素/メンバに対して先頭から順番に繰り返し処理を行う
  • 仮変数には、配列/連想配列やオブジェクトから取り出された要素のインデックス番号やキー名、メンバ名が格納され、for..inブロックの中で要素値を参照する際に使用することができる
  • 仮変数に格納されるのが要素値そのものではないことに注意
  • for..inループを利用するのは連想配列、オブジェクトのキーを走査する場合に留め通常配列を走査する場合は原則としてforループを利用するようにする
    • for..inループは配列のインデックス番号を取り出すだけなのでコードがあまりシンプルにならない(値そのものではないので、却って誤解を招く)
    • 通常配列の内容を取り出す処理はコードによっては正しく動作しない場合がある

おうおう思い出した順番に取り出してくるやつね
よしわかったところで続き

高階関数arrayWalkにおける引数の意味は

  • data
    • 処理対象の配列
  • f
    • 配列の各要素を処理するためのユーザ定義関数

となっている

なお、ユーザ定義関数fは引数として
配列のキー名(仮引数key)、値(仮引数value)を受け取り
与えられた配列要素に対して任意の処理を行うものとする
(これは高階関数というよりもarrayWalk関数としての決まりである)

えーっと‥?(・∀・;)

ここでは、ユーザ定義関数showElementは与えられた引数に基づいて
「<キー>:<値>」のような形式で出力する
そのため、arrayWalk関数はそれ全体として
配列内のキー名と値をリスト形式で出力することになる

えーっと‥?えーっと‥?(;∀;)

もちろんユーザ定義関数は自由に差し替えることができる

‥‥(・∀・)

ちょっとここまで理解できてないから
一旦次に行かずストップして考える(・∀・)

//高階関数arrayWalkを定義
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);
  • arrayWalk関数を実行する
  • 引数ary、showElementを渡す
  • ってことは「function arrayWalk(ary, showElement) {」みたいな
  • ってことは「f(key, data[key]);」⇒「showElement(key, data[key]);」みたいな
  • for..in命令で配列内の要素を順に処理する
  • そのたびにshowElementも実行される
  • 「document.writeln(key + ':' + value);」される
    • ここの引数keyは「f(key, data[key]);」のkeyが代入されたものか?

あーあーわかった
引数がkeyばっかりだから
なにがなんだかわからないんだ

書きなおしてみた

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

//配列を処理するためのユーザ定義関数
function showElement(key2, value) {
  document.writeln(key2 + ':' + value);
}

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

はあく!

じゃあ続き!

もちろんユーザ定義関数は自由に差し替えることができる

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

//配列を処理するためのユーザ定義関数
var result = 0;
function sumElement(key, value) {
  result += value;  //与えられた配列要素でresultを加算
}

var ary = [1, 2, 4, 8, 16];
arrayWalk(ary, sumElement);
document.writeln('合計値:' + result);

合計値:31

ユーザ定義関数sumElementは与えられた値valueを
グローバル変数resultに足しこんでいる
(ここでは引数keyは使用しない)
そのため、arrayWalk関数はそれ自体として
配列要素の合計値を求めることになる

ここでおおもとのarrayWalk関数は
一切書き換えていない点に注目
このように高階関数を利用することで
ベースとなる機能はそのまま
(ここでは配列の内容を順番に取り出すこと)に
具体的な処理内容だけを自由に差し替えることができる
ふむーなるほどー

さっきの二つを私なりに書きなおしてみると
こんな感じかな?

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

//配列を処理するためのユーザ定義関数①
function showElement(key2, value) {
  document.writeln(key2 + ':' + value);
}
//配列を処理するためのユーザ定義関数②
var result = 0;
function sumElement(key2, value) {
  result += value;  //与えられた配列要素でresultを加算
}

var ary = [1, 2, 4, 8, 16];
arrayWalk(ary, showElement);  //ユーザ定義関数①呼び出し
arrayWalk(ary, sumElement);  //ユーザ定義関数②呼び出し
document.writeln('合計値:' + result);

0:1
1:2
2:4
3:8
4:16
合計値:31

二つ並べても問題ない、ってことですな、べんり!
keyのあたり分かりにくいから
わざと引数名変えていますです(・∀・)
私がばかなだけなんだけれども

「使い捨ての関数」は匿名関数で

匿名関数と高階関数は密接な関係を持っている、とな
というのも、高階関数においては引数として与えられる関数が
「その場限り」でしか利用されないことがよくあるため
このような「使い捨ての関数」は
あえて名前付きの関数として定義するよりも
匿名関数(関数リテラル)として記述した方がコードがシンプルになる

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

var ary = [1, 2, 4, 8, 16];
arrayWalk(
  ary, 
  function(key2, value) {
    document.writeln(key2 + ':' + value);
  }
);

さっきのshowElement関数を匿名関数で書き換えたもの

匿名関数(関数リテラル)を利用することで
関数呼び出しコードに関数を直接指定することができる
このような記法は下記のようなメリットがある

  • コードが短くなる
  • 関連する処理が一つの文で記述できるため、呼び出し元のコードと実際の処理を規定している関数との関係がわかりやすくなる
  • 一度限りしか利用しない関数に名前(しかもグローバルスコープの名前)を付けずにすむため、「意図せぬ名前の重複を回避できる」

今後より高度なスクリプト(特にAjaxのコールバック関数)を
記述する上で重宝する
また、多くのJavaScriptプログラマが好んで利用しているので
外部ライブラリなどを読み解く際にも有効、ふむー






まだもう少し高度な~続きそうなので記事分けよう