【Salesforce】認定JavaScriptデベロッパー試験対策

# はじめに
この度[JavaScriptデベロッパー試験](https://trailhead.salesforce.com/ja/help?article=Salesforce-Certified-JavaScript-Developer-I-Exam-Guide)に合格することが出来たので、情報共有として記事を投稿します。

受験した感想としては、LWCの実装経験と試験に合格できるかどうかは、必ずしもイコールではないな、というものでした。
試験で問われるのはJavaScriptの知識そのもので、LWCの知識は一切問われません。
また、LWCを実装する上では必須とは言えない部分からも出題されるため、LWCの実装経験が豊富だったとしても試験対策は必要だと感じました。

そこで本記事では、LWCを実装する上で見落としがちな、JavaScriptの基本的な仕様について解説したいと思います。
[試験範囲](https://trailhead.salesforce.com/ja/help?article=Salesforce-Certified-JavaScript-Developer-I-Exam-Guide)で言うと、以下単元に関わる部分のみ解説しています。
網羅的な解説は行なっていないためご了承下さい。

- 変数、データ型、コレクション
- オブジェクト、関数、クラス
- 非同期プログラミング

# 私の経歴

- プログラミング経験：1年半
- 実務経験：1年（Salesforce開発を担当）
- LWC実装経験：3機能ぐらい

LWC以外に実務でJavaScript開発を行なった経験はありません。
半年前まで`arguments`も知らなかったレベルです、、

# 学習した内容

受験を決めてから合格までは約2ヶ月です。
その期間に行なったことを順番に紹介します。

- Trailhead：[Lightning Web コンポーネントの作成](https://trailhead.salesforce.com/ja/content/learn/trails/build-lightning-web-components)
- Trailhead：[Study for the Salesforce JavaScript Developer I Exam](https://trailhead.salesforce.com/ja/content/learn/trails/study-for-the-salesforce-javascript-developer-i-exam)
- Udemy：[【JS】ガチで学びたい人のためのJavaScriptメカニズム](https://www.udemy.com/course/javascript-essence/)  
この講座のおかげで合格できたと言っても過言ではありません。  
体系的にしっかりJavaScriptを学びたい方にお薦めです。
- 書籍：[JavaScriptモダンプログラミング完全ガイド](https://www.amazon.co.jp/dp/B08QVCSY1D/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)  
カバーしている範囲は上記Udemyの動画とほぼ同じです。  
私は動画 → 書籍の順番だったので何とか読めましたが、初見だと若干難易度が高い、、？
- Superbadge：[Lightning Web Components Specialist](https://trailhead.salesforce.com/ja/content/learn/superbadges/superbadge_lwc_specialist)  
有志の方々による[日本語訳](https://shunkosa.gitbook.io/trailhead-superbadge-jp/lwc-specialist)が公開されています。  
資格を取得するためには、試験合格とTrailのバッジ獲得が必須条件なので、学習も兼ねて受験前に取り組むことをお勧めします。

# 解説

前置きが長くなりましたが、いよいよ本題に入ります。

## 変数、データ型、コレクション

### var / let / const

この辺りは基本中の基本かもしれませんが、現在は非推奨とされている`var`を使ったことが無い方向けです。

- var：ブロックスコープを形成しない / 巻き上げが行われる
- let / const：ブロックスコープが形成される / 巻き上げが行われない

```js
if(true) {
    var a = 'a';
    let b = 'b';
}

console.log(a); // a
console.log(b); // ReferenceError
```
```js
console.log(a); // undefined ※巻き上げされた時点では値が未設定
console.log(b); // ReferenceError

var a = 'a';
let b = 'b';
```


### 配列操作

ここはLWCの実装でも使用頻度が高い部分だとは思いますが、私は`flat()`の存在を試験で初めて知った（そして案の定間違えた）ので念の為、、

- 結合にはスプレッド構文か`concat()`を使用

```js
const arry1 = [1, 2, 3];
const arry2 = [4, 5, 6];
const result1 = [...arry1, ...arry2];
const result2 = arry1.concat(arry2);

console.log(result1); // [1, 2, 3, 4, 5, 6]
console.log(result2); // [1, 2, 3, 4, 5, 6]

```

- 連想配列には`flat()`を使用、全て展開する場合は引数に`Infinity`を渡す

```js
const arry1 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
console.log(arry1.flat());         // [1, 2, 3, 4, [5, 6, [7, 8, [9, 10]]]]
console.log(arry1.flat(2));         // [1, 2, 3, 4, 5, 6, [7, 8, [9, 10]]]
console.log(arry1.flat(Infinity));  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```


## オブジェクト、関数、クラス

### オブジェクトのコピー
- `Object.assign({}, obj)`で浅いコピー
- `JSON.parse(JSON.stringify(obj))`で深いコピー（メソッドの定義があれば消失）

```js
const obj1 = {
    a: 'a',
    b: 'b',
    c: {
        text: 'original'
    },
    d: {
        text: 'original'
    },
    fn: function() {
        console.log('hello');
    }
};

const copy1 = Object.assign({}, obj1);
console.log(copy1); // {a: 'a', b: 'b', c: {text: 'original'}, d: {text: 'original'}, fn: ƒ}
const copy2 = JSON.parse(JSON.stringify(obj1));
console.log(copy2) // {a: 'a', b: 'b', c: {text: 'original'}, d: {text: 'original'}}

// obj1とcopy1は同じオブジェクトを参照している
copy1.c.text = 'changed';
console.log(obj1.c.text); // changed
copy2.d.text = 'changed';
console.log(obj1.d.text); // original

console.log(copy1.c === obj1.c); // true
console.log(copy2.c === obj1.c); // false
```

### this

`this`を理解するために大切なポイントは2点です。

- アロー関数内では`this`コンテキストが生成されない  
→ そのため`this`を参照しようとした場合は外側のスコープを順に辿っていく
- メソッドではなく関数として呼ばれた場合は`this`がwindowオブジェクトを参照する  
→ `bind()` などを使用しない限りどこで呼んでも`this = window`

「アロー関数では`this`コンテキストが生成されない = `this`が参照できない」ではないため、アロー関数を使用している1つ上のスコープに`this`があれば参照できます。
逆にオブジェクトから呼ばれるメソッドではなく、関数として呼ばれた場合は基本的にグローバルスコープを参照するため、1つ上の階層に`this`が存在しても`this = window`になります。

私は「アロー関数内では`this`の使用不可」と思い込んでいたため、正しく`this`が参照できているような例文を見て激しく混乱していました、、
コンテキストやスコープについて理解が進めば、自然に`this`の挙動についても理解できるようになると思います。

```js
window.target = 'window';
const target = 'script';

const obj = {
    target: 'obj',
    hello1() {
        console.log(this.target); // obj → 基本的なthisの使用法
        const bye = () => {
            console.log(this.target); // obj → 1つ上のスコープにthisが存在するため'obj'を参照
            const traditionalFn = function() {
                console.log(this.target); // window → 無名関数のため強制的にwindowを参照
            }
            const allowFn = () => {
                console.log(this.target); // obj → 2つ上のスコープにthisが存在するため'obj'を参照
            }
            traditionalFn();
            allowFn();
        }
        bye();
    },
    hello2: () => {
        console.log(this.target); // window → アロー関数のためthisが生成されずwindowを参照
        const bye = () => {
            console.log(this.target); // window → 1つ上のスコープにもthisが存在しないためwindowを参照
        }
        bye();
    },
}

obj.hello1();
obj.hello2();

const copyHello1 = obj.hello1;
copyHello1(); // 出力は全てwindow
copyHello1.call(obj); // 出力は全てobj.hello1()と同様 → copyHello1()のthisを強制的に'obj'に束縛
```

### クラス関連

クラス構文だけでなく、コンストラクタ関数でのインスタンス生成も理解しておく必要があります。
特にコンストラクタ関数を用いた方法は見落としがちな部分だと思うので、しっかり理解した上で試験に臨みましょう。


```js
// クラス構文
class Animal {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log(`this is ${this.name}`);
    }
}

class Bird extends Animal {
    constructor(name, action) {
        super(name);
        this.action = action;
    }

    hello() {
        console.log(`${this.name} can ${this.action}`);
    }
}

const animal = new Animal('Animal');
animal.hello(); // this is Animal

const bird = new Bird('bird', 'fly');
bird.hello(); // bird can fly
```
```js
// コンストラクタ関数での書き換え
function Animal(name) {
    this.name = name;
}

// helloメソッドをAnimalクラスのprototypeへ登録
Animal.prototype.hello = function() {
    console.log(`this is ${this.name}`);
}

function Bird(name, action) {
    Animal.call(this, name); // Animalクラスのコンストラクタを、thisを束縛しつつ呼び出す
    this.action = action;
}

// BirdクラスのprototypeにAnimalクラスのプロトタイプを割り当て
Bird.prototype = Object.create(Animal.prototype);

const animal = new Animal('Animal');
animal.hello(); // this is Animal

const bird = new Bird('bird', 'fly');
bird.hello(); // this is bird

// Birdクラスのhelloメソッドを上書き
Bird.prototype.hello = function() {
    console.log(`${this.name} can ${this.action}`);
}

bird.hello(); // bird can fly
```

# 非同期プログラミング
### Promise
ここはLWCでも使用頻度が高い部分ですが、油断は禁物です。
以下のようなチェーンや、`setTimeOut()` / `setInterval()`と複合したコード内で何が起こっているのか理解を求められます。
以下は最低限把握しておく必要があると感じた点です。

- Promiseには3つの状態がある
    - 待機 (pending): 初期状態
    - 履行 (fulfilled): 処理が成功して完了
    - 拒否 (rejected): 処理失敗  
※ 決定 (settled) は、`fulfilled`と`rejected`であることを示す単なる呼び方で、`settled`という状態はない

- `then()` / `catch()` / `finally()`は全てプロミスを返す
- `then()` / `catch()` / `finally()`は全てコールバック関数を引数に取る
→ ただの引数を渡しても機能せず、引数を使用する際はコールバック関数の引数に指定する
※`finally()`は引数を受け付けないので注意する

```js
// Promiseチェーン
const sleep = time => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('called');
            resolve('done');
        }, time)
    })
}

sleep(1000)
.then(data => {
    console.log(data);
    return sleep(1000) 
}).then(data => {
    console.log(data);
    return sleep(1000)
}).then(data => {
    console.log(data);
    return sleep(1000)
}).finally(() => {
    console.log('finally');
})
```

### async / await

そんなに深堀りはされなかった印象です。

- `async`が付与された関数・メソッドはプロミスを返す
- async functionでのみ`await`が使用でき、プロミスの解決を待機する

```js
const sleep = time => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('called');
            resolve('done');
        }, time)
    })
}

async function callSleep() {
    const a = await sleep(1000);
    return a;
}

callSleep();

```

# おわりに
取り留めのない内容になってしまった感は否めませんが、本記事でJavaScriptへの理解が少しでも深まっていれば幸いです。
最後までご覧下さりありがとうございました！