はじめに
JavaScriptの関数に関する機能について学んだことを簡単に書き記していきます。
関数の引数について
fuction sum(a, b) {
return a + b;
}
alert( sum(1, 2, 3, 4, 5));//結果:3
関数が定義された際は、引数を2つとして定義しています。
ここで、定義された関数を呼び出された際は、5つの引数を指定していますが、必要以上の引数でもエラーになりません。
エラーにはなりませんが、最初の2つだけが使用されます。
任意の数の引数は3つのドットにより、関数定義できる
fuction sumAll(...args) {
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
alert( sumAll(1));//結果:1
alert( sumAll(1, 2)) //結果:3
alert( sumAll(1, 2, 3))//結果:6
...args
の意味するところはパラメータを配列にまとめることであり、args
は配列名として扱われます。
最初の引数を指定して残りの引数をまとめる
function showName(firstName, lastName, ...titles) {
alert( firstName + '' + lastName );
alert( titles[0] );
alert( titles[1] );
alert( titles[2] );
alert( titles.length );
}
showName("山田", "花子", "次郎", "三郎");
//結果:山田花子, 次郎, 三郎, 2
引数をfirstName, lastName, ...titles
とすることで、第1引数、第2引数を変数として扱い、残りの引数を配列として扱うことができる。
arguments
変数によりすべての引数を取得することができる
function showName () {
alert( arguments.length );
alert( arguments[0]);
alert( arguments[1]);
}
showName("太郎", "花子")
//結果:2, 太郎, 花子
showName("太郎")
//結果:1, 太郎, undefined
arguments
の注意点:
- 配列のように見えて厳密には配列ではない
- 反復可能である
- アロー関数は
arguments
を持たない
スプレッド演算子を使って反復可能オブジェクトを引数に展開する
スプレッド演算子(...
)はどのような場面で使われるか。
例えば、配列から最大値を返す組み込み関数などを使用するときに使われます。
alert( Math.max(3, 5, 1)); //5
let arr = [3, 5, 1];
alert( Math.max(arr));//NaN
上記コードで配列として保持している変数(arr
)を引数の中で展開したい場合に使用する。
let arr = [3, 5, 1];
alert( Math.max(...arr)); //5
let arr1 = [1, 2, 3 ,4];
let arr2 = [1, 2, 3, 5];
alert( Math.max(...arr1, ..arr2) );//5
配列をマージするためにスプレッド演算子を使用する
通常の値とスプレッド演算子を組み合わせることで配列をマージすることができます。
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15
レキシカル環境とは?
レキシカル環境とはなんであるか
JSではすべての実行中の関数やコードブロック、スクリプト全体はレキシカル環境と呼ばれる関連オブジェクトを持っている。
レキシカル環境オブジェクトは2つの部分から構成されている。(この辺が意味わからん)
* 環境レコード。プロパティとしてすべてのローカル変数をもつオブジェクトであること
* 外部のレキシカル環境への参照。通常、直近の外部のレキシカルなコードに関連付けられている。
これにより、変数は特別な内部オブジェクト、環境レコードのプロパティである。
変数を取得、変更するとは、そのオブジェクトのプロパティを取得または変更するということである。
→(自分の見解)変数や関数はオブジェクトとして扱われ、プロパティ(アトリビュート)を保持している。
レキシカル環境とは一体何であるかをつかむために、下記のコードにおいて、レキシカル環境が変わる様子を示す。
let phrase;
phrase = "Hello";
phrase = "Bye";
1.スクリプト開始時、レキシカル環境は空
2.let phrase
が定義された。初期値がないため、undefined
が格納される
3.phrase
が代入される
4.phrase
が新しい値を参照する
要約
変数は特別な内部オブジェクトのプロパティで、現在の実行ブロック/関数/スクリプトと関連付けられています。
変数を使った作業は、実際にはそのオブジェクトのプロパティを使って作業しています
関数呼び出しの間、2つのレキシカル環境がある。
内部と外部→ローカル変数とグローバル変数
自分のレキシカル環境に対するイメージ
レキシカル環境とは変数や関数が定義あるいは宣言され、格納されている場所そのもののことを指す。
変数にアクセスする時、最初に内部のレキシカル環境を探し、次に外部のレキシカル環境で検索する。
コードブロックとループについてのレキシカル環境
レキシカル環境はコードブロックにも存在します。
コードブロックが実行され、ブロック内のローカル変数を含む時それらが作成されます。
if文を例として
let pharse = "こんにちは"
if (true) {
let user = "太郎"
alert(`${pharse}, ${user}`)
}
alert(user);// Uncaught ReferenceError: user is not defined
実行がif
ブロックに来た時、if
だけのレキシカル環境が作成されます。
作成されたレキシカル環境はその外部への参照をもつphrase
を見つけることができます。
しかし、if
の中で宣言されたすべての変数と関数式はレキシカル環境の中にあり、外部からは見えません。
例えば、if
が終わった後にあるalert
はuser
が見えないためエラーになります。
let,const,var
let
,const
はレキシカル環境に関してまったく同じように振る舞います。
let
とconst
の違いといえば、一度代入された変数に再度代入できるかどうかかなと。
var
,let
の違いは、
-
var
はブロックスコープを持たない -
var
宣言はグローバルスクリプトの開始時(または関数の開始時)に処理される
var
はブロックスコープを持たない
if (true) {
var testvar = true;
let testlet
}
alert(testvar); //結果:true
alert(testlet); //結果:testlet is not defined
var
宣言はグローバルスクリプトの開始時(または関数の開始時)に処理される
var
による変数宣言は関数の開始時に処理されます。
しかし、変数は代入されるまではundefined
です。(宣言がブロック内で最初に行われることを巻き上げ処理という)
function sayHi() {
phrase = "Hello";
alert(phrase);
var phrase;
}
上記のコードは以下のコードと同じです。
function sayHi() {
var phrase;
phrase = "Hello";
alert(phrase);
}
スケジューリングについて
関数をすぐには実行させず、ある時点で実行するようにしたいことがあります。
そのために2つのメソッドがあります。
-
setTimeout
:指定時間経過後、一度だけ関数を実行します。 -
setInterval
:指定した間隔で、定期的に関数を実行します。
setTimeout
setTimeout
の構文
let timerId = setTimeout(func|code, delay[, arg1, arg2...])
引数について
-
func|code
:関数もしくはコードの文字列。通常は関数であり、コードの文字列は推奨されていない -
delay
:実行前の遅延時間であり、単位はミリ秒 -
arg1,arg2...
:関数の引数。
setInterval
setInterval
の構文はsetTimeout
と同じ。
let timerId = setInterval(func|code, delay[, arg1, arg2...])
呼び出しを停止する際はclearInterval()
を使用します。
//2秒のインターバルで繰り返し
let timerId = setInterval(() => alert('tick'), 2000);
//5秒後に停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
デコレータ
デコレータとは任意の関数に対して振る舞いを変更する関数のことを指します。
下記コードの説明です。仮に頻繁に呼び出される関数があったとします。頻繁に呼び出される関数に対して、再計算を行わず結果をキャッシュしておく機能を追加したデコレータの実装です。
fucntion slow(x) {
alert(`Call with ${x}`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x);
cache.set(x, result);
return result;
};
}
slow = cachingDecorator(slow);
alert(slow(1));
alert(slow(2));
このような実装を行ったデコレータにはいくつかのメリットが挙げられます。
- デコレータ(
cachingDecorator
)は再利用可能であること -
slow
自身の複雑性は増加しない - 複数のデコレータと組み合わせることができます。
上記コードのようにオブジェクトメソッドで動作するには適していません。
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`Call with ${x}`);
return x * this.someMethod();
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x);
cache.set(x, result);
return result;
};
}
alert(worker.slow(1));
worker.slow = cachingDecorator(worker.slow);
alert(worker.slow(2));//「Call with 2」 の後の結果がTypeError: this is undefined
func.call()について
上記のコードのfunction cachingDecorator(func)
のlet result = func(x);
の実装にて、func(x)
として呼び出されているため、this is undefined
となります。
これを修正する方法として組み込みの関数メソッドfunc.call(obj, arg1, arg2, ...)を使用する方法があります。
function sayName() {
alert(this.name);
}
let user = { name: "John"};
let admin = { name: "Admin"};
sayName.call(user);
sayName.call(admin);
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`Call with ${x}`);
return x * this.someMethod();
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x);
cache.set(x, result);
return result;
};
}
alert(worker.slow(1));
worker.slow = cachingDecorator(worker.slow);
alert(worker.slow(2));
関数バインディングについて
オブジェクトメソッドsetTimeout
使ったり、オブジェクトメソッドを渡す場合、this
がundefined
になる場合があります。
以下、例
let user = {
firstName: "Taro",
sayHi() {
alert(`Hello! ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000);//Hello! undefined!
ブラウザでは、setTimeout
メソッドが呼びだされる時、this
はwindow
を指します。(ここでいうwindow
とはブラウザオブジェクトのwindow
)
this.firstName
はwindow.firstName
を取得しようとするため、this
はundefined
になります。
主な解決手段は2つ。
- ラップされた関数を使う
- 組み込みメソッド
bind
を使用する
ラップされた関数を使用する。
先のコードをラップされた関数を使用してみます。
let user = {
firstName: "Taro",
sayHi() {
alert(`Hello! ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); //Hello! Taro!
}, 1000)
bind
を使用する
関数の組み込みメソッドとしてfunc.bind(this)
があります。func.bind(this)
の引数で指定されたオブジェクトを呼び出し元のfuncに渡すことができます。
let user = {
firstName: "Taro",
};
function func () {
alert(`${this.firstName}`)
}
setTimeout(func.bind(user), 1000);//Taro
参考文献
https://ja.javascript.info/
https://developer.mozilla.org/ja/docs/Web/JavaScript