書籍やWeb上でJavaScriptを学ぶ際に、小難しい名前の関数が多く登場します。JavaScriptをより理解するために、知っておいて損はないものを集めました。
本記事ではソースコード添付し、各関数を簡潔に説明しています。よりアドバンスな説明や議論を知りたい方は、各文末に掲載している別記事のリンクから飛んでください。
##目次
##関数の定義方法を復習
JavaScriptの関数の基本的な定義方法を復習します。
###function命令での定義(関数宣言)
function命令を使って関数に名前をつけて定義します。関数を呼び出す際は()
を付けて呼び出します。
/**
* @param {number} num1 - 数値引数1
* @param {number} num2 - 数値引数2
* @return {number} 引数の合計値を返却する
*/
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(1,10)); // 11
()
なしで記述すると定義した関数自体を参照することになります。
引数なしの関数を呼び出す際に、()
を付け忘れてしまうと実行時まで不具合に気が付きません。
function returnTen() {
return 10;
}
console.log(returnTen + 10);
// 関数定義に10を結合した文字列を表示してしまう
// function returnTen() {
// return 10;
// }10
###関数リテラル(関数式)
関数を変数に代入することで定義することもできます。
関数呼び出しは同じく()
を付けて呼び出します。
const sum = function(num1, num2) {
return num1 + num2;
}
console.log(sum(1,10)); // 11
他にもコンストラクタを利用した関数宣言の方法もありますが、使われません。
Qiita 【JavaScript】関数定義いろいろ
##アロー関数
let
やconst
と同じくES2015から追加されたわりと新記述方法です。
※もう新しくないかも。。。
以前の書き方より簡潔に関数を記述できます。
function
の代わりに=>
を使うので、より簡潔に書けます。
Qiita ES2015の新機能: 関数の新しい書き方「arrow 関数」
// ES2015以前
var numList = [1,2,3,4,5]
numList.forEach(function(item) {
console.log(item);
});
// ES2015
const numList = [1,2,3,4,5]
numList.forEach(item => {
console.log(item);
});
###アロー関数と通常の関数では実行結果の違い
アロー関数は関数を短く記述できるだけではなく、細かい動作の違いがあります。
最も注意するべき点は関数内のthis
を参照が異なる点です。
window.name = "ピカチュウ"; // グローバルオブジェクトにピカチュウを設定
const fusigidane = {
name: 'フシギダネ',
type: ['草', '毒'],
// 通常の無名関数を引数にした場合
showType: function() {
this.type.forEach(function(item, i) { // ここのthisはfusigidane
console.log(`${this.name}のタイプ${i+1}は${item}`); // ここのthisはグローバル!!
});
},
// アロー関数を引数にした場合
showTypeArrow: function() {
this.type.forEach((item,i) => { // ここのthisはfusigidane
console.log(`${this.name}のタイプ${i+1}は${item}`); // ここのthisもfusigidane
});
}
}
fusigidane.showType(); // 通常の関数使っている方を実行
// ピカチュウのタイプ1は草
// ピカチュウのタイプ2は毒
fusigidane.showTypeArrow(); // アロー関数を使っている方を実行
// フシギダネのタイプ1は草
// フシギダネのタイプ2は毒
他にもいろいろと違いがあります。
JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。
##無名関数、匿名関数
名前の無い関数を無名関数、または匿名関数と呼ばれています。
JavaScriptのコーディングに頻繁に登場するため、会話の中でわざわざ無名関数と言うことは少ないかもしれませんが、解説書などでは無名関数、匿名関数と出てくるので、どういう意味か知っておいた方が良いでしょう。
名前がある関数が何かってのを知れば、無名関数は容易に理解できます。特に難しくなくfunction命令で名前付けた関数が名前付きの関数です。
function namedFunc() {
return '名前あるよ';
}
変数に代入したり、引数にしたりと無名関数はいたるところで利用されています。
JavaScriptでは関数もデータ型の一つなので、変数に代入したり引数にしたりできます。
// 無名関数をnoName変数に代入している
const noName = function() {
return '無名関数';
}
// ArrayのforEachの第一引数に無名関数を渡している
const numList = [1,2,3,4,5]
numList.forEach(function(item) {
console.log(item); // 1,2,3,4,5と順に表示
});
##即時関数、即時実行関数
JavaScriptでは関数を定義してから、別の場所で()
付けて呼び出すことで、関数を実行できました。
関数の定義と実行を同時に行うテクニックが即時関数、または即時実行関数と呼ばれています。
初見は意味不明で何が起きているかわからないかと思います。
関数全体を()
で囲い、その後に関数実行の()
を付けることで評価後すぐに関数を実行しています。
(function () {
console.log(1);
})()
モジュールシステムが無く変数のスコープに欠点があった時代に、スコープを作るために使われていました。
ファイル全体を即時関数にして、ファイル内でスコープを作ったり、特定の処理でスコープを作ったりされていました。
このぱっと見意味不明な関数見つけたら、書いた人はスコープを作りたいんだなと思うようにしましょう。
##コールバック関数
関数の引数に渡される関数のことをコールバック関数と呼ばれます。
引数に渡す際は関数自体を渡すため、実行の()
を付けません。付けると関数の実行結果が引数に渡されます。
function excecuteFunc(callback) {
callback(); // コールバック関数を実行する
}
function hello() {
console.log('hello!');
}
excecuteFunc(hello); // hello!
// 無名関数で書いた場合
excecuteFunc(function() {
console.log('hello!'); // hello!
});
setTimeout
や配列操作などでよく使われています。
// hello関数はsetTimeoutの中で実行される。
setTimeout(function() {
console.log('hello');
}, 1000); // 約1秒後にhello!を表示する。
const arr = [1,2,3];
arr.forEach(function(e) {
console.log(e); // 1 2 3
});
###コールバック地獄
Promise
やasync/await
、jQuery.Defferd
など非同期処理を制御するものが無い時代には、コールバックを利用して、非同期処理を実装していたらしいです。
ネストが深くなってしまう可読性が著しく下がってしまい、トレースが難しい状態をコールバック地獄と呼ばれています。エラーハンドリング等が絡めば可読性を維持することは難しいでしょう。
function delayLog(val, timer, callback) {
setTimeout(function() {
console.log(val);
callback();
}, timer);
}
console.log('Hell Start');
delayLog('A', 5000, function() {
delayLog('B', 4000, function() {
delayLog('C', 3000, function() {
delayLog('D', 2000, function() {
delayLog('E', 1000, function() {
console.log('Hell End');
});
});
});
});
});
##純粋な関数
純粋な関数とは、参照透過性を持ち副作用を持たない関数のことです。
参照透過性を意識し、副作用を分離するように設計することでテストがしやすいプログラムとなります。
###参照透過性
同じ引数であれば、同じ値を返すという関数の性質が参照透過生です。
例えば、下の関数の引数に10と100を渡すと、関数の返却値はいつも110です。
function add(num1, num2) {
return num1 + num2;
}
下のgetYear関数はどうでしょうか?
実行する西暦によって返却値が変わります。今年成功していたテストは来年には壊れるでしょう。
function getYear() {
return new Date().getFullYear();
}
ストレージからデータを取ってきたり、端末情報を取得したりと、可変なデータを参照することで参照透過生が失われます。実行時の状態によって返却値が変わるのでテストが壊れる可能性がある関数となってしまいます。
###副作用
値を返却するという作用が関数の主な作用ですが、実際にはログを出力したり、ファイルに書き込んだりと様々な働きを関数で行なっています。
このような値の返却以外の機能のこと副作用と呼びます。
特に外部の状態を変更する副作用を持つ関数は注意が必要です。
const person = {
name: '半沢直樹',
company: '東京中央銀行',
};
function secondedCentral(man) {
man.company = 'セントラル証券';
return man;
}
secondedCentral(person);
console.log(person.company); // セントラル証券
Qiita 関数型JavaScriptへの入門#1(純粋関数とは)
Qiita 「参照透過である」とは、何から何への参照がどういう条件を満たすことを言うのか
Qiita 参照透過性と副作用についての提言
##高階関数
関数を引数にとる、または関数を返す関数のことお高階関数と呼びます。配列オブジェクトのforEachやmapなどが高階関数の例です。
const numbers = [1,2,3,4,5];
// Array.filterは関数を引数にとっている
const even = numbers.filter(function(e) {
return e % 2 === 0;
});
console.log(even); // [2,4]
高階関数のメリットは主要機能をそのままに、具体的な処理を差し替えることができます。
上のサンプルコードで言うと、filter
関数には配列の要素を抽出するという主要機能があります。そして、どのような条件で抽出するかは引数の関数で自由に決めることができます。
偶数ではなく奇数を抽出したい場合は、引数の関数内で具体的な処理を変更すればよいです。
##再帰関数
自分自身を呼び出す関数を再帰関数と呼びます。他には再帰的と再帰処理とか言われています。
自分を呼び出し続けるので、終了条件の設定と状態の変更を書かないと無限ループになってしまいます。
function divide(num1,num2) {
let count = 0;
function recursion() {
if (num1 >= num2) {
count++;
num1 -= num2;
recursion(); // 自分自身を呼び出す
}
}
recursion();
return count;
}
divide(10, 2); // 5