りあくとで学んだ以下の項目を復習します。
それではれっつごー
この二つはどちらも配列の反復処理を行うメソッドで、他と違うところはメソッドの引数として渡す関数に二つの引数が存在するものだということ。
コードで見るとこんな感じ。
const arr = [1, 2, 3, 4, 5];
console.log(arr.reduce((n, m) => n + m)); //15
console.log(arr.sort((n, m) => n > m ? -1 : 1)); //[5, 4, 3, 2, 1]
const arr = [1, 2, 3, 4, 5];
console.log(arr.reduce((n, m) => n + m)); //15
なので、最初から追って見ていくとこんな感じになる。
色々やってるけど一言でまとめると配列の数値全てを足した値が出力されるってこと。
文字列を並べ替えたい場合、以下のようにsortするとアルファベット順に並べ替えられる。
let str = ['sa', 'mu', 'ra', 'i'];
str.sort();
console.log(str); // ["i", "mu", "ra", "sa"]
※ 数値を昇順に並べ替えたい場合、以下のようにsortしても昇順には並ばない。
なぜなら、数値の配列に対してsortメソッドを使用すると、数値が文字コードに変換され、文字コード順に並ぶから。
文字コードが何なのかはよくわからん。
とにかくこのやり方だと思い通りにならない。
lef num = [5, 3, 10, 6, 55];
num.sort();
console.log(str); // [10, 3, 5, 55, 6]
なので、数値を並べ替える場合は「比較関数」というものをつかう。
これは、2つの値を比較しながら、1つづつ順番を入れ替えていくという手法。
比較関数とは文字通り比較を行う関数のこと。
//比較関数のサンプル
function compareFunc(a, b) {
return a < b;
}
これをsortの引数に当てはめることで、数値の並べ替えを実現する。
数値を昇順(1, 2, 3...)に並べ替えたい場合は以下の通り。
function compareFunc(a, b) {
return a - b;
}
var num = [5, 3, 10, 6, 55];
num.sort(compareFunc);
console.log(num); //[3, 5, 6, 10, 55]
順を追って処理を見ていくと多分こんな感じ。
数値を降順(...10, 9, 8...)に並べ替えたい場合は以下の通り
function compareFunc(a, b) {
return b - a;
}
var num = [5, 3, 10, 6, 55];
num.sort(compareFunc);
console.log(num); //[55, 10, 6, 5, 3]
順を追って処理を見ていくとこんな感じ。
計算の部分は多分こんな感じて行われてると思われる。
注意点としては、新しい配列が返されるのではなく、対象の配列が書き換えられるてところ。
サンプルコードはわかりやすくするためにfunction関数で書いてあるけど、アロー関数でこのように省略して書くこともできる。
const arr = [1, 2, 3, 4, 5];
arr.sort((n, m) => n - m); //[1, 2, 3, 4, 5]
arr.sort((n, m) => m - n); //[5, 4, 3, 2, 1]
挙動から見るに、比較関数の返り値がtruthyな値だと後ろに並べ替えられて、falsyな値だと前に並べ替えられるっぽい。
ってことは配列の要素数とtrue/falseさえわかれば並べ替えられる。
だから最初に出した例のように書くこともできるってことか。なるほど。
console.log(arr.sort((n, m) => n > m ? -1 : 1)); //[5, 4, 3, 2, 1]
カリー化:「複数の引数を取る関数を、より少ない引数を取る関数に分割して入れ子にすること」
カリー化する前のコード
const multiply = (n, m) => n * m
console.log(multiply(2, 4));
multiplyは引数に受け取った値の積を返すだけの関数。
カリー化した後のコード
const withMulthple = (n) => {
return (m) => n * m
}
console.log(withMulthple(2)(4));
withMulthpleはnを引数に受け取った上で、「mを引数に受け取りnとの積を返す関数」を返す関数。
アロー関数でカリー化したコード
const withMulthple = (n) => (m) => n * m ;
console.log(withMulthple(2)(4));
このように複数の引数をとる関数を、より少ない引数を取る関数に分割して入れ子にすることをカリー化という。
const withMultiple = (n) => (m) => n * m;
console.log(withMultiple(3)(5));
const triple = withMultiple(3);
console.log(triple(5)); // 15
カリー化された関数の一つ目の引数に3を渡してできた関数にtripleという名前をつけている。
このようにすれば、どんな数を渡しても常に3倍される関数を作ることができる。
このようにカリー化された関数の一部の引数を固定して新しい関数を作ることを、「関数の部分適用」という。
Closure。日本語に訳すと「閉鎖」という意味。
プログラミングでは「関数閉包」つまり、関数を関数で閉じて包むことを意味する。
クロージャーのメリットを理解するためにまずは閉じていない状態を考える。
let count = 0;
const increment = () => {
return count += 1;
};
$ increment();
▶︎ 1
$ increment();
▶︎ 2
$ count
▶︎ 2
一つづつカウントアップしていく単純なカウンターだが、グローバル変数countはどこからでも参照ができて、任意に書き換えができてしまう。なので、incrementは参照透過的ではなく、挙動が予測不可能な関数だと言える。(参照透過性のない関数)
参照透過性とは、その式をその式の値に置き換えても、プログラムの観測可能な振る舞いが変わらないことを指します。 別の言い方をすると、参照透過性のある関数は、同じ入力に対して、同じ作用と同じ出力を持ちます
これを丸ごと関数の中に入れてみる。
const counter = () => {
let count = 0;
const increment = () => {
return count += 1;
};
};
安全にはなったが、カウンターの機能が完全に関数の中に閉じ込められていて使えない。
つまりcounterを実行しても毎回初期化されカウントアップされない。(参照透過性のある関数)
機能だけを外から使えるように出してみる。
const counter = () => {
let count = 0;
const increment = () => {
return (count += 1);
};
return increment;
};
安全性を保ったままカウントアップの機能も使えるようになった。
言葉で説明すると、
最初に例としてあげたこれ(グローバル変数に依存してるけどカウントアップされてたやつ)を、
let count = 0;
const increment = () => {
return count += 1;
};
$ increment();
▶︎ 1
$ increment();
▶︎ 2
$ count
▶︎ 2
動作した環境ごと関数の中に閉じ込めている。
という説明になる。
const counter = () => {
let count = 0;
const increment = () => {
return (count += 1);
};
return increment;
};
でも正直なんでこのcountの状態が保持されたままになるのかがわからない。
この例ではcountが毎回初期化されるけど、
const counter = () => {
let count = 0;
const increment = () => {
return count += 1;
};
increment();
};
こっちの例ではcountが初期化されない
const counter = () => {
let count = 0;
const increment = () => {
return (count += 1);
};
return increment;
};
前者はcounter内でincrement関数を実行してる。
後者はincrementの参照をcounter実行時に返り値として返してる。
この違いがcountが初期化されるかされないかの違いを生んでる。
りあくとでなぜこうなるのかの説明があったのでまとめてみる。
言語の種類に関わらず、一般的にメモリのライフサイクルというの以下のようになっている。
javascriptのような高水準言語は3を手動で行う必要はなく、不要になったメモリ領域を自動的に判別し解放する機能が装備されている。(ガベージコレクタ)例えると死神のようなもの。
この死神がいることによって、この世で死者が溢れかえって生者の居場所を圧迫してしまうということが起こらずに済んでいる。
現在確保されているメモリ領域が、この世にいるべきではない死者かどうかを判断しているのがこのガレージコレクタだが、その判断基準は「他の生者から必要とされているかどうか」ということ。つまり、そのメモリ領域への参照があるかどうかということ。
こちらの例(countが毎回初期化される方)は処理がこの関数内で完結しているので、処理が終われば確保されていたメモリ領域は死者であると判断され、ガベージコレクタによってメモリ解放(初期化)される。
だからカウントアップ機能が機能しない。
const counter = () => {
let count = 0;
const increment = () => {
return count += 1;
};
increment();
};
こちらの例(カウントアップが正常に行われる方)は、関数incrementへの参照が、counterの実行元に返されるので、つまり「他の生者から必要とされている関数である」と判断される。なので、関数の実行によって確保されたメモリ領域は処理が終了してもメモリ解放(初期化)されない。だからcountが状態を保ち続ける変数となり、正常にカウントアップを行うことができる。
const counter = () => {
let count = 0;
const increment = () => {
return (count += 1);
};
return increment;
};
なるほど!めちゃしっくりきた!
ちなみにクロージャーは必ずしも内側の関数を返す必要はないみたいです。
単に外のスコープの自由変数を参照する関数をさらに関数で包み込んだものをクロージャーと呼ぶ。
包み込んでいる外側の関数はエンクロージャーと呼ぶ
@ 酒井悠宇