LoginSignup
2
2

More than 3 years have passed since last update.

【JavaScript】関数について知ったことを書く

Last updated at Posted at 2020-01-26

はじめに

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が終わった後にあるalertuserが見えないためエラーになります。

let,const,var

let,constはレキシカル環境に関してまったく同じように振る舞います。
letconstの違いといえば、一度代入された変数に再度代入できるかどうかかなと。

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使ったり、オブジェクトメソッドを渡す場合、thisundefinedになる場合があります。

以下、例

let user = {
    firstName: "Taro",
    sayHi() {
        alert(`Hello! ${this.firstName}!`);
    }
};

setTimeout(user.sayHi, 1000);//Hello! undefined!

ブラウザでは、setTimeoutメソッドが呼びだされる時、thiswindowを指します。(ここでいうwindowとはブラウザオブジェクトのwindow)
this.firstNamewindow.firstNameを取得しようとするため、thisundefinedになります。

主な解決手段は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

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2