【もりけん塾】JavaScript課題3 まとめ
webコーダーのsaco @sacocco_sacoya です。
所属しているもりけん塾にてマークアップエンジニアの方がフロントエンドエンジニアになる為の課題3に挑戦しました。
備忘録&勉強になったことをまとめていきます。
課題内容
このDOM
<ul>
<li><a href="a1.html"><img src="/img/bookmark.png">a1</a></li>
<li><a href="a2.html"><img src="/img/message.png">a2</a></li>
</ul>
をJavaScriptでつくり、html内のulの中に差し込んでください
作成手順
ざっくり手順。
for文で<li>とその中身を作成し、最後に<ul>へまとめて挿入。
↓細かい手順。
- <ul>を取得
- for文の中で作成する<li>要素を保持しておくためのフラグメント(後述)を用意。
- 配列の中に、href / src / テキスト情報を用意
- for文を作成。
配列の要素数だけループさせるため、条件の最大値に配列名.lengthとして指定 - for文内で、<img><a><li>をそれぞれ作成
- for文内に、インデックス[ i ]に応じて配列を取り出す処理を記述
- for文内で、配列に用意していたhref / src / テキスト情報を、作成したHTML要素にそれぞれ付与
- for文内で、<li>に<a>を、<a>に<img>を挿入し、insertAdjacentHTMLでテキストを追加
- for文内で、createDocumentFragment()メソッドに作成した<li>を挿入(保管)
- for文の外で、createDocumentFragment()メソッドで保持していた<li>を<ul>に挿入
提出コード
const ul = document.getElementById('js-lists');
const fragment = document.createDocumentFragment();
const contents = [
{ href: 'a1.html', src: '/img/bookmark.png', text: 'a1' },
{ href: 'a2.html', src: '/img/bookmark.png', text: 'a2' },
];
for (var i = 0; i < contents.length; ++i) {
const image = document.createElement('img');
const anchor = document.createElement('a');
const li = document.createElement('li');
const content = contents[i];
image.src = content.src;
anchor.href = content.href;
li.appendChild(anchor).appendChild(image);
anchor.insertAdjacentHTML('beforeend', content.text);
fragment.appendChild(li);
}
ul.appendChild(fragment);
プルリクエスト
プルリクエストの内容はこちら↓↓↓
https://github.com/sunacodesu/frontend-handson/pull/4
レビューの結果・勉強になったこと
- 課題の画像パスが違っていた。正しくはbookmarkとmessage。
どこで勘違いしてしまったのかorz - for文の変数名に”var”を使用してしまっている。
varは現在は非推奨。 - for文の増減値「++i」を前置インクリメントにしてしまっていた。
今回のようなfor文では「i++」として、後置インクリメントにするのが通常。
(処理結果自体は変わらない。) - 挿入内容がHTMLを含まない場合は、innerAdjacentHTMLではなくtextContentを使うとHTMLを含んでいないということが直感的に伝わるコードになる
- 指定箇所に追加したい要素がテキストのみの場合は、innerAdjacentTextを利用すれば、HTMLを含んでいないことが直感的に伝わる。
わからなかった点・気付いた点
for文の条件の最大値を配列の数に合わせる
例文を見て、最初にfor文を記述する際に「要素が2つだから、i < 2;」という風に記述するところでしたが、もりけん塾先輩方の記録を確認したところ、配列の要素数に応じて最大値を変動できるよう、i <配列.lengthと指定されていました。
この指定であれば、配列の要素数が変動したときにも動的に対応できるのかなと思いました。
一般的によく非常に利用される手法とのことです。
また、ループの回数に数値を指定するのはマジックナンバー(意図不明な数値)と呼ばれ、良い書き方ではないと知りました。
for文の[ i ]を配列のインデックスとして使用
for文の[ i ]を配列のインデックスとして使用することで、for文の1度のループ処理ごとに、配列のインデックス番号に応じた内容を取得してfor文内で利用することができる。
この手法はfor文の中で配列の要素を順次利用する際に一般的によく使われる手法。
const contents = [
{ href: 'a1.html', src: '/img/bookmark.png', text: 'a1' },
{ href: 'a2.html', src: '/img/bookmark.png', text: 'a2' },
];
for (var i = 0; i < contents.length; ++i) {
//略
//配列名[i]と指定することで、iの数に応じた配列を取得することができる。
//取得した1つの配列を、定数に格納することで要素を取り出して利用できる。
const content = contents[i];
//略
image.src = content.src;
anchor.href = content.href;
//略
anchor.insertAdjacentHTML('beforeend', content.text);
//略
}
備忘録
以下、課題に取り組むにあたって気になったこと、調べたことの備忘録です。
constと配列の関係
「constは再割り当ても、再宣言もできない」が、配列に利用できるのはなぜか?
塾生のはるさん@fuwafuwahappyのブログを拝見していて気がつくことができた点です。
constで宣言されたオブジェクトや配列は、宣言後に再代入はできないが、その中身のプロパティや値は変更や追加を行うことができる。
const obj = { a: 1, b: 2 };
// オブジェクトのプロパティを変更
obj.a = 3;
// オブジェクトに新しいプロパティを追加
obj.c = 4;
const obj = {1: a, 2: b, 3: c}
//再度同じ定数名で定義すると、エラーとなる
forとforEachの違い
ループ処理を行うにあたって、forとforEachの違いが気になったので調べました。
for文
for(let i = 0; i < array.length; i++){
//処理
}
- JavaScriptの組み込み構文。
- 繰り返し処理内で、インデックス番号を使用できる
- 配列以外にも様々な繰り返し処理に柔軟に利用できるため汎用性が高い
(指定の数値範囲の分繰り返す、文字列の文字数だけ繰り返すなど) - ループを途中で終了するbreakや、特定の条件でスキップさせるcontinueが利用可能。
forEachメソッド
array.forEach(function(item){
//処理
});
コールバック関数に引数を指定することで、arrayの中の要素が1つずつ取得できることになります。
- 配列オブジェクトに対して定義されているメソッドで、他のデータ構造には利用できない(配列専用のメソッド)
- コールバック関数を利用するため、コードがシンプルで読みやすくなる
- for文と違い、途中でループを終了できない
- HTMLCollection(配列風オブジェクト)には利用できない。
DocumentFragmentとは(createDocumentFragment())
ノードの一種で、複数のノードをまとめて扱うために用意する仮想コンテナのようなもの。フラグメント=断片。
document.createDocumentFragment()メソッドで、空のDocumentFragmentを作成できる。
ノードの一種のため、appendChildなどで子ノードを追加したり、cloneNodeでDocumentFragment自身をコピーしたりもできる。
今回の課題では以下のように使用した。
//空のDocumetnFragmentを作成しておく
const fragment = document.createDocumentFragment();
for (var i = 0; i < contents.length; ++i) {
//略(<li>の作成処理)
//作成した<li>をfor文の中で順次保管していく
fragment.appendChild(li);
}
//for文の中でfragmentに保管していた<li>を挿入
//複数の<li>の挿入が1度の処理で済む!
ul.appendChild(fragment);
先に空のDocumentFragmentを作成しておき、for文の中で、作成した<li>をfragmentに順次保管していく。
その後、複数の<li>を保管したfragmentごと<ul>へ要素を追加することで、ブラウザ側のレンダリング処理が1度で済む。
例えばfor文の中でul.appendChild(li)として要素を追加するような記述では、<li>の追加の処理が走るたびにDOMの再描画や再レイアウトが行われ、これが頻繁に行われるとページの表示速度の遅延に繋がるなどの問題が発生してくる。
DocumentFragemntは仮想的なDOMツリーであり、実際のDOMツリーに追加されるまで、ブラウザは再描画や再レイアウトを行わない。
このような理由から、複数の要素を効率的に追加したいとき、DocumentFragmentを利用すると便利。
感想
for文で要素を追加するという課題内容ですが、とても難しく頭を悩ませました。
手も何度も止まりましたが、先輩方のコードを頼りに、わからない箇所を調べていく形でなんとか全体像が理解できたように思います。
ただ、わりとすぐ「ワカラン、、」となり、ギブアップして先輩のコードを参考にしたのがちょっと悔やまれました。
次の課題は自分でじっくり考える時間を設けたいと反省しました。
課題内容については、DocumentFragmentという初めて知るノードがあったり、for文の書き方の復習ができたりで苦しくも楽しく学ぶことができました。
主にレビューをしてくださったはせがわさん @starsurferz、記事を参考にさせていただいたはるさん @fuwafuwahappy、さえ @sae_progさん、課題を作成してくださったもりけんさん @terrace_tech。ありがとうございました。
もりけん塾とは?
フロントエンドエンジニアを目指す方の無料コミュニティ「もりけん塾」。
「無料でJavaScript教え合うフロントエンドコミニュティ」です。
詳しくはこちら↓↓
https://kenjimorita.jp/morikenjuku
もりけん先生のTwitter↓↓
https://twitter.com/terrace_tech
参考記事・動画
【もりけん塾】JavaScript課題3で学んだことまとめ | <>haru log
https://happy-making.com/javascript-lesson03/
【もりけん塾】#JS課題3 | フロントエンドエンジニアの積み上げブログ
https://itosae.com/archives/600
【JS】constで定義したオブジェクトや配列は、中身を変更できてしまう
https://zenn.dev/yuji6523/articles/js-object-mutable
JavaScriptのfor文の違いを解説【for, for in, for of, forEach, Object-keys】
https://lorem-co-ltd.com/for-loop-pattern/
【JavaScript】ループ処理をマスターしよう
https://zenn.dev/akkie1030/articles/js-loop-summary
DocumentFragmentオブジェクトでノードを追加 #JavaScript – Qiita
https://qiita.com/yuta-10112022/items/90882d89f90cb9062b7c
DocumentFragment – Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/DocumentFragment