LoginSignup
14

More than 1 year has passed since last update.

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

Posted at

はじめに

この度JavaScriptデベロッパー試験に合格することが出来たので、情報共有として記事を投稿します。

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

そこで本記事では、LWCを実装する上で見落としがちな、JavaScriptの基本的な仕様について解説したいと思います。
試験範囲で言うと、以下単元に関わる部分のみ解説しています。
網羅的な解説は行なっていないためご了承下さい。

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

私の経歴

  • プログラミング経験:1年半
  • 実務経験:1年(Salesforce開発を担当)
  • LWC実装経験:3機能ぐらい

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

学習した内容

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

解説

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

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

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) は、fulfilledrejectedであることを示す単なる呼び方で、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への理解が少しでも深まっていれば幸いです。
最後までご覧下さりありがとうございました!

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
14