はじめに
この度JavaScriptデベロッパー試験に合格することが出来たので、情報共有として記事を投稿します。
受験した感想としては、LWCの実装経験と試験に合格できるかどうかは、必ずしもイコールではないな、というものでした。
試験で問われるのはJavaScriptの知識そのもので、LWCの知識は一切問われません。
また、LWCを実装する上では必須とは言えない部分からも出題されるため、LWCの実装経験が豊富だったとしても試験対策は必要だと感じました。
そこで本記事では、LWCを実装する上で見落としがちな、JavaScriptの基本的な仕様について解説したいと思います。
試験範囲で言うと、以下単元に関わる部分のみ解説しています。
網羅的な解説は行なっていないためご了承下さい。
- 変数、データ型、コレクション
- オブジェクト、関数、クラス
- 非同期プログラミング
私の経歴
- プログラミング経験:1年半
- 実務経験:1年(Salesforce開発を担当)
- LWC実装経験:3機能ぐらい
LWC以外に実務でJavaScript開発を行なった経験はありません。
半年前までarguments
も知らなかったレベルです、、
学習した内容
受験を決めてから合格までは約2ヶ月です。
その期間に行なったことを順番に紹介します。
- Trailhead:Lightning Web コンポーネントの作成
- Trailhead:Study for the Salesforce JavaScript Developer I Exam
- Udemy:【JS】ガチで学びたい人のためのJavaScriptメカニズム
この講座のおかげで合格できたと言っても過言ではありません。
体系的にしっかりJavaScriptを学びたい方にお薦めです。 - 書籍:JavaScriptモダンプログラミング完全ガイド
カバーしている範囲は上記Udemyの動画とほぼ同じです。
私は動画 → 書籍の順番だったので何とか読めましたが、初見だと若干難易度が高い、、? - Superbadge:Lightning Web Components Specialist
有志の方々による日本語訳が公開されています。
資格を取得するためには、試験合格とTrailのバッジ獲得が必須条件なので、学習も兼ねて受験前に取り組むことをお勧めします。
解説
前置きが長くなりましたが、いよいよ本題に入ります。
変数、データ型、コレクション
var / let / const
この辺りは基本中の基本かもしれませんが、現在は非推奨とされているvar
を使ったことが無い方向けです。
- var:ブロックスコープを形成しない / 巻き上げが行われる
- let / const:ブロックスコープが形成される / 巻き上げが行われない
if(true) {
var a = 'a';
let b = 'b';
}
console.log(a); // a
console.log(b); // ReferenceError
console.log(a); // undefined ※巻き上げされた時点では値が未設定
console.log(b); // ReferenceError
var a = 'a';
let b = 'b';
配列操作
ここはLWCの実装でも使用頻度が高い部分だとは思いますが、私はflat()
の存在を試験で初めて知った(そして案の定間違えた)ので念の為、、
- 結合にはスプレッド構文か
concat()
を使用
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
を渡す
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))
で深いコピー(メソッドの定義があれば消失)
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
の挙動についても理解できるようになると思います。
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'に束縛
クラス関連
クラス構文だけでなく、コンストラクタ関数でのインスタンス生成も理解しておく必要があります。
特にコンストラクタ関数を用いた方法は見落としがちな部分だと思うので、しっかり理解した上で試験に臨みましょう。
// クラス構文
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
// コンストラクタ関数での書き換え
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()
は引数を受け付けないので注意する
// 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
が使用でき、プロミスの解決を待機する
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への理解が少しでも深まっていれば幸いです。
最後までご覧下さりありがとうございました!