Salesforce 認定 JavaScript デベロッパー試験の個人的な躓きポイント

こんにちは。この記事はSalesforce 開発者向けブログ投稿キャンペーンへのエントリー記事です。
テーマは `[2] Salesforce のコーディング開発` です。

> :pencil: [Salesforce 開発者向けブログ投稿キャンペーン 第4弾](http://sfdc.co/DEV-BLG-DEC)

# はじめに

日本語試験が公開されたばかりの [Salesforce 認定 JavaScript デベロッパー](https://trailhead.salesforce.com/ja/help?article=Salesforce-Certified-JavaScript-Developer-I-Exam-Guide) に挑戦中です。

> 日本語のガイド: https://tandc.salesforce.com/examguide_cert_javascript_developer.pdf

このエントリを書いている 2020年11月時点で 1回落ちていますので、次は2回目の挑戦になります。

1回目は2020年11月に受験（正答率：60%）。
ボーダラインは 65% ですので、あと一息です。次こそ合格するべく、苦手な単元を整理していきたいと思います。

# 苦手な単元

1回目の試験結果を見ると、ボーダラインに達していない（あるいはギリギリの）単元が 4 つありました。

1. 【57%】(8/14) Variables, Types, and Collections (変数、データ型、コレクション)
1. 【66%】(10/15) Objects, Functions, and Classes (オブジェクト、関数、クラス)
1. 【40%】(4/10) Browser and Events (ブラウザとイベント)
1. 【37%】(3/8) Asynchronous Programming (非同期プログラミング)

試験はほぼ全てコードリーディングです。普段書き慣れてない書き方が多く、結構戸惑いました。
受験ガイドの試験範囲を見ながらキーワードを深掘りしていきます。

---

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

## 出題内容

- シナリオに基づき、変数を作成して正しく初期化するコードを記述する。
- ビジネス要件に基づき、文字列、数値、日付を効果的に利用する。
- シナリオまたは例に基づき、型強制とその影響を認識していることを示す。
- 特定のシナリオに基づき、truthy または falsey な評価を区別する。
- データのリストに基づき、配列を使用したデータ操作を示す。
- JSON 応答に基づき、JSON オブジェクトの操作方法を示す。

## Number

> :bookmark: [Number](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number)

- JavaScript の数値は `Number` と `BigInt` のみ。
    - Float (32 bit) も Double (64 bit) も `Number`
- $1.8 \times 10^{308}$ より大きいと `Infinity` に解釈される。
- 渡された値が NaN かどうかを判定する： `Number.isNaN()`
- 渡された値が有限数かどうかを判定する： `Number.isFinite()`
- 渡された値が整数かどうかを判定する： `Number.isInteger()`

```js
// true は 1 と同じ
(true + 1 + '10') * 2    // 210 * 2 = 420

// 文字列を数字に変換する。
Number('12345')    // 12345
```

## NaN (Non-A-Number)

> :bookmark: [NaN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/NaN)

- `NaN` になるもの

```js
const value = NaN;
value !== value    // NaN

Number(undefined)    // NaN
Number('zero')       // NaN
Number(0/0)          // NaN
Number(1/'zero')     // NaN
Number('zero'/0)     // NaN
parseInt("zero")     // NaN
Math.sqrt(-1)        // NaN
0 * Infinity         // NaN

NaN === NaN;             // false
Number.isNaN(NaN);       // true
Object.is(value, NaN)    // true
```

- `NaN` にならないもの

```js
Number(1/0)    // Infinity
Number(1/'0')    // Infinity
```

## reduce()

> :bookmark: [Array.prototype.reduce()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)

- `accumulator` : callback の返り値を蓄積するアキュームレーター
- `currentValue` : 現在処理されている配列の要素
- `initialValue` : 返り値の初期値

```js
const array = [1, 2, 3, 4, 5];
const initialValue = 10;

const newArray1 = array.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
});
console.log(newArray1);    // 15

const newArray2 = array.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, initialValue);
console.log(newArray2);    // 25
```

## splice()

> :bookmark: [Array.prototype.splice()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)

既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりできる。

```js
const array = ['A0', 'B1', 'C2', 'D3', 'E4', 'F5', 'G6', 'H7'];
const start = 3;
const deleteCount = 2;
const item1 = 4

const newArray = array.splice(start, deleteCount, item1);
console.log(newArray);    // ["D3", "E4"]
console.log(array);       // ["A0", "B1", "C2", 4, "F5", "G6", "H7"]
```

## slice()

> :bookmark: [Array.prototype.slice()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)

start から end まで (end は含まれない) で選択された配列の一部を返す。

```js
const array = ['A0', 'B1', 'C2', 'D3', 'E4', 'F5', 'G6', 'H7'];
const start = 3;
const end = 6;

const newArray1 = array.slice(start);
const newArray2 = array.slice(start, end);
console.log(newArray1);    // ["D3", "E4", "F5", "G6", "H7"]
console.log(newArray2);    // ["D3", "E4", "F5"]
console.log(array);        // ["A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7"]
```

## for (...) {}

> :bookmark: [ループと反復処理](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Loops_and_iteration)

`in` なのか `of` なのかで結果が違うので注意する。

```js
const items = [{name: 'a'}, {name: 'b'}, {name: 'c'}];

for (let item in items) {
  console.log(item);                // 0, 1, 2
  console.log(items[item]);         // {name: 'a'}, {name: 'b'}, {name: 'c'}
  console.log(items[item].name);    // a, b, c
}

for (let item of items) {
  console.log(item);         // {name: 'a'}, {name: 'b'}, {name: 'c'}
  console.log(item.name);    // a, b, c
}
```

## Object.assign()

> :bookmark: [Object.assign()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)

値を上書きする。

```js
const target = { a: 1, b: 2 };
const source = { b: 0, c: 3 };

console.log(Object.assign(target, source));    // { a: 1, b: 0, c: 3 }
```

## Object.freeze()

> :bookmark: [Object.freeze()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)

値を更新も追加もできないようにロックする。

```js
const target = { a: 1, b: 2 };
Object.freeze(target);
target.a = 0;
target.c = 3;

console.log(target);    // { a: 1, b: 2 }
```

## Object.seal()

> :bookmark: [Object.seal()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/seal)

値を追加できないようにロックする。更新は可能。

```js
const target = { a: 1, b: 2 };
Object.seal(target);
target.a = 0;
target.c = 3;

console.log(target);    // { a: 0, b: 2 }
```

## Object.preventExtensions()

> :bookmark: [Object.preventExtensions()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)

オブジェクトのさらなる拡張を抑制する。

```js
const obj = {};
obj.name = 'A';
Object.preventExtensions(obj);
obj.title = 'B';
console.log(obj);    // { name: "A" }
```

---

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

## 出題内容

- ビジネス要件に基づき、最良の関数実装を見つける。
- ビジネス要件に基づき、オブジェクト実装の基本を適用し、ビジネス要件を解決する。
- ビジネス要件に基づき、クラス実装の基本を適用し、ビジネス要件を解決する。
- 与えられた JavaScript モジュールで、モジュールの使用方法の例を示す。
- 与えられた JavaScript デコレータで、デコレータの使用方法の例を示す。
- 与えられたコードブロックで、変数のスコープと実行フローを分析する。

## コンストラクタの定義

- クラスで定義する時には `constructor` を使う。
- 関数で定義する時には、関数名と同じにする。

```js
class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
}

const taro = new Person('Taro');
taro.sayHello();    // "Hello, Taro"
```

```js
function Person(name) {
  this.name = name;
  this.sayHello = function() {
    console.log(`Hello, ${this.name}`);
  }
}

const taro = new Person('Taro');
taro.sayHello();    // "Hello, Taro"
```

---

# 3. ブラウザとイベント

## 出題内容

- ビジネス要件に基づき、イベント、イベントハンドラー、イベント伝達を利用する。
- ビジネス要件に基づき、DOM を評価および操作する。
- シナリオに基づき、ブラウザ開発ツールを利用してコードの動作を調査する。
- シナリオと要件に基づき、ブラウザ固有の API を利用する。

## style.display

非表示にしたい場合は hidden ではなく `none` を代入する。

```html
<div id="sample">Sample</div>
```

```js
// 非表示
document.getElementById("sample").style.display = "none";
// 表示
document.getElementById("sample").style.display = "block";
```

## addEventListener()

> :bookmark: [addEventListener()](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
> :bookmark: [Window: load イベント](https://developer.mozilla.org/ja/docs/Web/API/Window/load_event)

特定のイベントごとに呼び出される関数を定義する。

```html
<a class="clickOnce" href="https://www.mozilla.org" target="_blank">Click Once</a>
```

```js
const clickOnce  = document.querySelector('.clickOnce');

// ページの DOM が読み込まれたら実行したい場合は "DOMContentLoaded" を定義する
window.addEventListener('DOMContentLoaded', (event) => {
  console.log('DOM fully loaded and parsed');
});

// 画像など全てのリソースが読み込まれてから実行したい場合は "load" を定義する
window.addEventListener('load', (event) => {
  console.log('ページが完全に読み込まれました');
});

// "onload" でも同じ
window.onload = (event) => {
  console.log('ページが完全に読み込まれました');
};

// 1回切りにしたい場合は、オプションの "once" を指定する
clickOnce.addEventListener('click', function(event) {
  console.log('clicked!');
}, { once : true });

// イベントが伝播するのをブロックしたい場合は、"stopPropagation" を呼ぶ
clickOnce.addEventListener('click', function(event) {
  event.stopPropagation();
  console.log('clicked!');
}, { once : true });

// 規定の動作をブロックしたい場合は、"preventDefault" を呼ぶ
clickOnce.addEventListener('click', function(event) {
  event.preventDefault();
  alert('You cannot click me!');
});
```

## CustomEvent

> :bookmark: [CustomEvent()](https://developer.mozilla.org/ja/docs/Web/API/CustomEvent/CustomEvent)
> :bookmark: [イベントの作成と起動](https://developer.mozilla.org/ja/docs/Web/Guide/Events/Creating_and_triggering_events)

`detail` プロパティを使用してカスタムデータを渡すことができる。

```js
const event = new CustomEvent("sample", {
  detail: {
    recordId: '12345'
  }
});
```

## Node.js のデバッグ起動

> :bookmark: [インスペクタを有効にする](https://nodejs.org/ja/docs/guides/debugging-getting-started/)

```sh
node --inspect index.js
```

## console.table()

> :bookmark: [console.table()](https://developer.mozilla.org/ja/docs/Web/API/console/table)
> この機能は標準ではなく、標準化の予定もありません

- 表形式のデータを表で表示する。
- 列のソートも可能。

```js
console.table(["apples", "oranges", "bananas"]);
```

---

# 4. 非同期プログラミング

## 出題内容

- シナリオに基づき、非同期プログラミングの概念を適用する。
- シナリオに基づき、イベントループとイベント監視を使用する、またはループの結果を判断する。

## setTimeout

遅延時間に `0` を設定しても、実際には最低 4 ミリ秒の遅延がある。

```js
console.log('1');
setTimeout(() => {
  console.log('3');
}, 0);
console.log('2');    // 1, 2, 3
```

## setInterval

`setTimeout` と共存する場合は、遅延時間に注目する。

```js
console.log('1');
setTimeout(() => {
  console.log('3');
}, 1100);
console.log('2');
const timerId = setInterval(() => {
  console.log('A');
}, 1000);
setTimeout(() => {
  clearInterval(timerId);
  console.log('stop');
}, 3000);    // 1, 2, A, 3, A, A, stop
```

## Promise

> :bookmark: [Promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)
> :bookmark: [States and Fates](https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md)

- Promise の状態（States）は 3 つのみ。
    - 待機 `pending`：初期状態。成功も失敗もしていない。
    - 満足 `fulfilled`：処理が成功して完了。
    - 拒絶 `rejected`：処理が失敗。
    - `fulfilled` か `rejected` かのどちらかになった状態（`pending` ではない状態）を `settled` と呼ぶ。 
- Promise の運命（Fates）は 2 つのみ。
    - 解決 `resolved`：`fulfilled` か `rejected` かのどちらかになることが決まっている。
    - 未解決 `unresolved`：`fulfilled` か `rejected` かのどちらかになることが決まっていない（`pending`）。
- `then(...).catch(...).finally(...)` でつなぐ。
 
```js
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

const promise3 = Promise.resolve(3);
const promise4 = 4;

// 先に解決した方が勝ち： "race"
Promise.race([promise1, promise2])
  .then((value) => {
    console.log(value);
  });    // two

Promise.race([promise1, promise2, promise3, promise4])
  .then((value) => {
    console.log(value);
  });    // 3

// 全部解決するまで待つ： "all"
Promise.all([promise1, promise2, promise3, promise4])
  .then((value) => {
    console.log(value);
  });    // [one, two, 3, 4]
```
```js
const onFulfilled = (result) => {
  console.log(result);
};

const onRejected = (result) => {
  console.error(result);
};

// 引数で与えられた理由で解決された Promise オブジェクトを返す
Promise.resolve('Success')
  .then(onFulfilled, onRejected);    // Success

// 引数で与えられた理由で拒絶された Promise オブジェクトを返す
Promise.reject(new Error('Fail'))
  .then(onFulfilled, onRejected);    // Error: Fail

Promise.reject(new Error('Fail'))
  .catch((result) => {
    console.error('result - ' + result)
  });    // result - Error: Fail

```

## debounce()

> :bookmark: [debounce()](http://www.elder-flower.com/blog/2015/04/debounce.html)

- 最後に受け付けた第1引数の関数のみを発動する。
- 第2引数の時間の間は関数を受け付けない。
- 第3引数に true が設定された時は即時発動する。

```js
_.debounce = function(func, wait, immediate) {...}
```

---

# 参考文献

そのほか、試験勉強で利用したサイトです。

- [Prepare for your Salesforce JavaScript Developer I Credential](https://trailhead.salesforce.com/ja/users/strailhead/trailmixes/prepare-for-your-salesforce-javascript-developer-i-credential)
- [Study for the Salesforce JavaScript Developer I Exam](https://trailhead.salesforce.com/ja/content/learn/trails/study-for-the-salesforce-javascript-developer-i-exam)

# さいごに

多肢選択式試験に関しては、Salesforce の知識は全然必要ありませんでした。
みなさんもぜひ挑戦してください！

それではまたお会いしましょう。

# おまけ

試験の結果は...
2020年12月に合格しました:sunny: