2021/08/06

りあくと復習

@ 酒井悠宇

JavaScript

りあくとp89。「式と演算子で短く書く」と「JavaScriptの鬼門、thisを理解する」という項目を復習します。

式と演算子で短く書く

ショートサーキット評価

又の名を「短絡評価」と呼ぶ。&&(アンパサンド)や||(パイプライン)、!といった論理演算子が左から右に評価される性質を利用して、右辺の評価を左辺の評価に委ねる記法のこと。


OR演算子「||」

OR演算子||は左辺がfalsyな値だと評価が右辺に渡される。変数の初期化を動的に行いたい場合等に使用する。

サンプルコード

const test = undifined || null || 0 || NaN || '' || 'test' ;

この場合はfalsyな値が続いているので、最終的にもっとも右の文字列'test'が代入される。

AND演算子「&&」

AND演算子&&は左辺がtruthyな値だと評価が右辺に渡される。falsyな値があればそれ以降の右辺は無視される

サンプルコード

const test2 = ' ' && 100 && [] && {} && 'Chao!';


この記法は左辺がfalsyだと右辺が評価・実行されないため、if文の代わりに使用できる。

サンプルコード

true && console.log('hello') # hello
false && console.log('hello') # (no output)


Nullish Coalescing とOptional Chaining

ES2020で導入された記法。「何かを実行して返ってきた値が失敗を示すものだったら、代わりに別のものを入れたい」ケースには、OR演算子||によるショートサーキット評価よりもこちらのNullish Coalescing とOptional Chainingを用いる方法が良いとされる


Optional Chaining「?.」

通常のオブジェクトに対するプロパティアクセス演算子は . と [] の 2つ。これらによって指定したキーのプロパティが存在してなかった場合、それが 1 階層めであれば undefined が返るだけだが、その参照が2階層以上に渡って外れているとタイプエラーになる。


ところが代わりに ?. 演算子を使うと、チェーン内の各参照が正しいかどうかを明示的に確認せずにアクセスしていくことができる。途中のプロパティが存在していなかったら、そこで式が短絡されて undefined を返してくれる。

サンプルコード

Nullish Coalescing「??」

「coalesce」というのは「癒着する、合体する」という意味の単語である。OR演算子 || に似ているが、ちがうのは左辺が null または undefined のときだけ右辺が評価されるということである。よって 0 や ' ' の空文字といった falsy な値はそのまま評価される。

サンプルコード

OR演算子だと、0や空文字もスルーされてしまう為、より明示的なnullish coalescingの方が望ましい。


ここまでのまとめ

・OR演算子 || は、左がfalsyな値の場合に右を返す。変数に代入する値を同的に変化させたい場合に有効。
・AND演算子 && は、左がtruthyな値の場合に右を返す。左がfalsyな値であれば右が実行されない為、if文の代わりに使用できる。
・Optional Chaining ( .? 演算子 ) は、タイプエラーを出すことなくオブジェクトの存在しないプロパティにアクセスしていくことができる。
・Nullish Coalescing ( ?? 演算子 ) は、左がnull、又はundefinedの場合のみに右を返す。よって0などのfalsyな値はそのまま評価される。
・OR演算子と似ているが、より明示的なNullish Coalescingを使用する方が良いとされる。

JavaScriptの鬼門、thisを理解する

りあくとと合わせていろんな教材見ながらthisを勉強していきます。
https://www.youtube.com/watch?v=sIGrsliVj4U

thisって結局なんなん?

thisとは、関数を呼び出すオブジェクトへのリンクである。


例えばこんなオブジェクトが定義されていたとします。

//オブジェクトを定義
const test = {
  name: 'taro',
  //オブジェクトのキーに関数がある
  getName: function(){
    //this.nameを出力
    return this.name
  }
}

thisとは、「関数を呼び出すオブジェクトへのリンク」なので、
オブジェクトtestの、getNameのバリューにある関数の返り値にあるthis.nameのthisは、オブジェクトtestを表します。
なので、test.getName();とした場合、返り値として帰ってくるのは、「taro」になります。

色々なケースでのthis

次は、グローバルな関数を定義して、
1, グローバルに呼ぶ場合
2, 関数をオブジェクトのプロパティにつけた場合
この二つを比較していきたいと思います。

まずはthisを出力するグローバルな関数を定義します。

//ログにthisを出力する関数を定義
const sayThis = function () {console.log(this);};


この関数をオブジェクトのプロパティにつけて呼び出すのも試したいので、オブジェクトを作ります。

//オブジェクトを定義
const obj = {name: 'taro'};


オブジェクトのプロパティに先ほど作った関数をつけてみます。

//objのsayThisというプロパティに関数sayThisを定義
obj.sayThis = sayThis;


グローバルにthisを呼ぶ場合

まずは関数をグローバルに呼んだ場合のthisの挙動を見ていきます。

sayThis(); // ▶︎ window

windowオブジェクトが出力されました。

関数をオブジェクトのプロパティにつけてthisを呼ぶ場合

それではこの関数をオブジェクトのプロパティにつけて呼んでみたいと思います。

obj.sayThis(); // ▶︎ {name: "taro", sayThis: ƒ}

objオブジェクトが出力されました。

thisが、関数を呼び出すオブジェクトを参照していることがわかります。

コンストラクタ関数でのthis

コンストラクタとは、日本語に訳すと「構築子」となり、JavaScriptではインスタンス(実態)を作成する関数のことをコンストラクタ又はコンストラクタ関数と呼びます。


ES6より以前はクラス構文がなかった為、コンストラクタを使用することで擬似的にクラスを再現していました。
コンストラクタからインスタンス(実態)を作成するためには、new演算子を使用します。
普通の関数の違いは、new演算子によって実態を作る関数であるというところです。
それではコンストラクタでのthisの挙動を見ていきましょう。

まずはコンストラクタ関数を定義します。

//コンストラクタ関数を定義
const Person = function (name) {
  //引数に受け取ったnameをnameプロパティに設定
  this.name = name;
};


new演算子を使用してコンストラクタからインスタンス(実態)を作っていきます。

//インスタンス生成
const taro = new Person("taro");


ログを出力してみると、コンストラクタ関数Personから、このようなインスタンス(実態)が生成されていました。

console.log(taro); // ▶︎ Person {name: "taro"}

コンストラクタ関数でのthisは、new演算子によって作成されたオブジェクトがthisに参照されているようです。

コンストラクタ関数は普通の関数としても使用することができるので、new演算子を使用せずに、引数に値を渡す形でも呼び出してみます。

const test = Person("test");


test.nameで生成された値を呼び出してみましたがエラーになりました。

console.log(test.name); // ▶︎TypeError 


これは、関数Personをコンストラクタ関数としてではなく、グローバル関数として呼び出したためです。
なので先程引数に渡したnameはwindowオブジェクトに格納されています。

console.log(window.name); ▶︎ "test"


・new演算子によって呼び出した時は、コンストラクタ関数内のthisはインスタンスオブジェクトを参照
・グローバルオブジェクトとして呼び出した時は、コンストラクタ関数内のthisはwindowオブジェクトを参照

コンストラクタでも「thisは関数を呼び出すオブジェクトを参照」していることがわかりました。

入れ子関数でのthis

入れ子関数とは、関数の中で関数を定義して呼んでいるもの。


実際にみた方がわかりやすいと思うので、入れ子関数を定義してみます。

//オブジェクトを定義
const test = {
  //メソッドを定義 (入れ子関数になっている)
  sayHello: function () {
    //thisを出力
    console.log(this);
    //関数を定義
    const func = function () {
      //thisを出力
      console.log(this);
    };
    //関数を呼び出し
    func();
  },
};
メソッドとは、オブジェクトのバリューに入っている関数のことです。


メソッド内のthisと、メソッドの中で定義した関数内のthis。どのように違うのでしょうか。
実際に見ていきましょう

test.sayHello();


const test = {
  sayHello: function () {
    console.log(this); //▶︎ {sayHello: ƒ} //ƒはfunctionの略
    const func = function () {
      console.log(this); //▶︎ window
    };
    func();
  },
};


1.sayHelloメソッド内のthisにはtestオブジェクト
2.sayHelloメソッド内に定義したfunc関数内のthisにはwindowオブジェクト
がそれぞれ格納されてあることがわかりました。

1のような挙動になったのは、sayHelloメソッドをオブジェクトのプロパティとして呼び出したからです。
thisは「関数を呼び出すオブジェクトへのリンク」なので、sayHelloメソッド内のthisには、sayHelloメソッドを呼び出しているオブジェクト、つまりtestオブジェクトが格納されています。

2のような挙動になったのは、func関数をsayHelloメソッド内で、オブジェクトのプロパティとしてではなく、グローバルオブジェクトとして呼び出したからです。thisは「関数を呼び出すオブジェクトへのリンク」なので、sayHelloメソッド内で、グローバルオブジェクトとして呼び出されたfunc関数内のthisには、func関数を呼び出すオブジェクト、つまりwindowオブジェクトが格納されています。

入れ子関数でも、「thisは関数を呼び出すオブジェクトを参照」していることがわかりました。

コールバック関数内でのthis

コールバック関数とは、ある関数を呼び出す時に、引数に指定する別の関数のこと。


//オブジェクトを定義
const test = {
  //メソッドを定義 (入れ子関数になっている)
  sayHello: function () {
    //thisを出力
    console.log(this); // {sayHello: ƒ}
    //1秒後に第一引数に渡した関数を実行するsetTimeout関数を定義
    //コールバック関数
    setTimeout(function () {
      //thisを出力
      console.log(this); //▶︎ window
    }, 1000);
  },
};


コールバック関数では、functionの呼ばれ方によりthisが変化します。
なぜなら、functionを呼んでいるオブジェクトがthisになるからです。
上の例でいうとsetTimeout関数はグローバルで呼ばれるので、コールバック関数内のthisはwindowになります。

functionとarrow関数でのthisの違い

ここでいうfunctionとは、function(){}で定義する関数のことで、arrow関数とは、() => {}で定義する関数のこと。
functionとarrow関数ではthisの挙動に違いがあるので注意しましょう。

functionを使った場合のthis

//オブジェクトを定義
const test = {
  //メソッドを定義 (入れ子関数になっている)
  sayHello: function () {
    //thisを出力
    console.log(this); // {sayHello: ƒ}
    //1秒後に第一引数に渡した関数を実行するsetTimeout関数を定義
    setTimeout(function () {
      //thisを出力
      console.log(this); //▶︎ window
    }, 1000);
  },
};

先程のコードと全く同じなので説明は省きます。

arrow関数を使った場合のthis

//オブジェクトを定義
const test = {
  //メソッドを定義 (入れ子関数になっている)
  sayHello: function () {
    //thisを出力
    console.log(this); // {sayHello: ƒ}
    //1秒後に第一引数に渡した関数を実行するsetTimeout関数を定義
    setTimeout(() => {
      //thisを出力
      console.log(this); //▶︎ {sayHello: ƒ}
    }, 1000);
  },
};


お気づきでしょうか、sayHelloメソッド内で出力されるthisがfunction関数ではwindowだったのに対してarrow関数ではtestオブジェクトになっています。
arrow関数で関数を定義すると、定義時点でどこから呼ばれたかでthisが決まるようになります。超わかりやすいですね。

そうです!arrow関数を使えば脳死でthisを使うことができます!今までの説明はなんだったんだ!おい!

thisのまとめ

「thisとは、関数を呼び出すオブジェクトへのリンクである。」

thisの呼び方:thisの中身 という感じでまとめていきます!

・グローバルに呼ぶ場合:windowオブジェクト
・関数をオブジェクトのプロパティにつけた場合:呼び出し元のオブジェクト
・new演算子によって呼び出す場合:インスタンスオブジェクト
・コールバック関数によって呼び出す場合:関数を呼んでいるオブジェクト
・arrow関数で呼び出すのthis:関数が定義されてるオブジェクト

こんな感じでしょうか!
thisが大体どんなものなのかはわかった気がします。
最後までごらんいただきありがとうございました!

© 2021 powerd by UnReact