今日は、もりけん塾のJavaScript課題3を終えて学んだことをアウトプットしていきます。
こんにちは。Webコーダーのはるです。
現在、所属している「もりけん塾」でハンズオン課題 に取り組み、レビューをいただいています。
今日は、課題3 についてアウトプットをしていきます!
前回までのアウトプットは、こちらです。
【もりけん塾】JavaScript課題 3で学んだこと まとめ
今回、取り組んだ課題はこちらです。
(もりけんさんのJavaScriptハンズオン課題より)
この課題から学んだこと
・AddEventListener
・配列
・DocumentFragment
・いただいたレビュー
一つずつまとめていきます。
用語:ブロックスコープ、配列リテラル、オブジェクトリテラル
今回も知らない言葉がいくつか出てきました。自分の言葉で説明ができるようにまとめてみます。
ブロックスコープ
- MDNには、let・constともに「ブロックスコープを持つ」と書かれています。
- スコープとは、変数の名前や関数などの参照できる範囲を決めるものです。関数を定義すると、同時にスコープも定義される。(JavaScript Primerより)
- ブロックスコープとは、ブロック内で宣言された変数はブロック内でしか有効でないことを意味します。
例を出します。
const text = 'haru';
{
const text = 'coding';
}
console.log(text); //haruと表示される
- ブロック内とは、{ }で囲まれた部分のことです。上記の例のように、ブロック内で宣言された定数textの値 ‘coding’ は、そのブロック内でしか有効ではありません。よって、ブロック外でconsole.logをすると、ブロック外で宣言されていた定数 ‘text’ が有効になります。
- これは、letでも同じことが言えます。詳しくはMDN「厳格モード時の let、const、関数宣言のブロックスコープの規則」へ
// ちなみに..ES2015以前は
ES2015以前は、まだlet , constが登場しておらず、varのみが存在していました。
var はブロックスコープを持ちません。先程の例をもう一度使うと、
var text = 'haru';
{
var text = 'coding';
}
console.log(text); //codingと表示される
ブロック内で宣言されている変数textが、ブロック外でも有効になっています。
JavaScript本格入門本によると、これでは変数に意図せぬ競合が起きる場合があり、 それを避けるために即時関数が使用されることがありました。
(function() {
var text = 'coding';
console.log(text); //codingと表示される
}).call(this);
console.log(text); //エラー
このように、call(this)を使用して定義した関数を即時で呼び出すテクニックのことを即時関数と呼びます。
これで、ブロック外で変数textを呼び出してもエラーが出ます。
昔書かれたコードだともしかして見ることがあるのかな?と思い、ふむふむ本を眺めてました。
配列リテラル
- 配列リテラルは、[ ]で値を囲み、Arrayオブジェクト(配列)を作成します。配列は、値に順序をつけて格納できるオブジェクトのことです。(JavaScript Primerより)
const array = [1, 2, 3];
- 要素を取り出したい時は、変数名[インデックス]で取り出せます。配列にはインデックスと呼ばれる番号がそれぞれの要素に割り当てられていて先頭から0,1,2…と続いています。
console.log(array[0]); // => "1"
オブジェクトリテラル
上記の配列リテラルと並んで、オブジェクトリテラルという書き方があります→今回はこれを使用
- オブジェクトリテラルは、{ }を使用してオブジェクトを作成することができます。
const object = { "key": "value" };
このとき、このオブジェクトはkeyというプロパティとして、値valueを持っています。
- プロパティを参照するときは下記のように呼び出します。
console.log(object.key);
または
console.log(object["key"]);
ドットでつなげる方法を、ドット記法。[ ]で括る方法をブラケット記法と呼びます。
MDNとJavaScript Primerで学習しました。
AddEventListener
さて、課題に戻ります。課題をもう一度貼ります。
JavaScriptで<li>~を差し込む機能を作っていきます。
今回、はじめて使用したものにAddEventListenerがあります。
これは、JavaScript本格入門本を読んで知りました。(p298)
- document.getElementByIdを使用するときは、ページの読み込みが終わってからメソッドを実行すべきです。なぜなら、読み込み中にgetElementByIdが実行されてしまうと、IDの取得に失敗する恐れがあるからです。
- それを避けるためにAddEventListenerメソッドの「DOMContentLoaded」があります。
document.addEventListener('DOMContentLoaded', function() {
//イベント処理内容
};
'DOMContentLoaded'
というのは、ページがロードされたタイミングで処理を実行するものです。AddEventListenerメソッドには、'DOMContentLoaded'
のように、いくつものイベントリスナーと呼ばれる引数があります。
以下のように、onloadメソッドを使用することもできます。
window.onload = function() {
//イベント処理内容
};
onloadとaddEventListener
は、どちらもページがロードされたタイミングで処理を実行するものですが、実行のタイミングが少し異なります。
addEventListener('DOMContentLoaded')
→コンテンツがロードされてから、画像のロードを待たずに実行このように、画像のロードを待つ必要がないときはaddEventListener('DOMContentLoaded')
を使用した方がパフォーマンスが向上します。(JavaScript本格入門本より)
配列
問題の、href属性の値と<li>のテキストがそれぞれ違うので、配列にまとめてみようと思います。
先ほどまとめたオブジェクトリテラルを使用して以下のように書きました。
const attributes = [
{ to: "a1.html", text: "a1" },
{ to: "a2.html", text: "a2" }
];
toというプロパティと、textというプロパティを持つ配列を、定数attributesにまとめました。
これで属性はここから取り出して使うことができます。
DocumentFragment
属性の配列を作ったあと、for文を回して<li>を作成していきました。
初めは、このようなコードを書きました。
"use strict";
document.addEventListener("DOMContentLoaded", function () {
const attributes = [
{ to: "a1.html", text: "a1" },
{ to: "a2.html", text: "a2" }
];
const ul = document.getElementById("js-lists");
const arrayLength = attributes.length;
const fragment = document.createDocumentFragment();
for (let i = 0; i < arrayLength; i++) {
const attribute = attributes[i];
const li = document.createElement("li");
const anchor = document.createElement("a");
const img = document.createElement("img");
anchor.textContent = attribute.text;
anchor.href = attribute.to;
img.src = "img/bookmark.png";
img.alt = "ブックマーク";
ul.appendChild(li).appendChild(anchor).insertAdjacentElement('afterbegin',img)
}
});
これだと最後のappendChildを<li>の数だけ行うことになり、その分パフォーマンスが落ちてしまうことを知りました。
今回は、<li>が2つだけど、3つ4つ…と増えていったら…?と考えて、DocumentFragmentを使用してみることにしました。
DocumentFragmentはノードを保存しておくことのできる箱のようなものです。
保存するコードの開始前に宣言をします。
const fragment = document.createDocumentFragment();
最後のappendChild のコードを以下のように変更しました。
li.appendChild(anchor).insertAdjacentElement("afterbegin", img);
fragment.appendChild(li); //fragmentに追加
anchorとimgを追加してできあがった<li>を定数frangmentに追加しました。
for文を回すことで、fragmentに<li>が溜まっていきます。
最後に、for文の外でfragmentを<ul>に追加します。
ul.appendChild(fragment);
このように、DocumentFragmentを使用すると実際にDOMの機能を追加するのは1回で済みます。
DocumentFragmentは、こちらの記事にとてもお世話になりました。とてもわかりやすかったです。
いただいたレビュー
今回は、塾生のもなかさん(ruby443n)と、しょうさん(_syoyamamoto_)にレビューをしていただきました。ありがとうございます!!
最初に書いた配列で疑問点がありました。
const attributes = [
{ to: "a1.html", text: "a1" },
{ to: "a2.html", text: "a2" }
];
配列は、将来的に配列内の要素が増える可能性があるけど、letの方が適切なのか?という点です。
結論から言うと、constの方が適切です。
こちらは、もなかさんにいただいた記事で腑に落ちました。
記事には
But please, pay attention because when we say “const cannot be reassigned, nor re-declared” that does not mean
const
is immutable. That’s a topic that trips up literally every JavaScript developer I talk to. In fact any slightly more complex JavaScript data structure like array or object is more than mutable even when assigned to aconst
:
(しかし、「constは再割り当ても再宣言もできない」と言っても、constが不変であることを意味しているわけではないので、注意してください。これは、私が話すすべてのJavaScript開発者を悩ませるトピックです。実際のところ、配列やオブジェクトのような少し複雑なJavaScriptのデータ構造は、constに割り当てられていても、変更可能です。)
とあります。
その後自分でMDNを読んだらガッツリ書いてました。ごめんなさい。
For instance, in the case where the content is an object, this means the object’s contents (e.g., its properties) can be altered.
(例えば、コンテンツがオブジェクトの場合、オブジェクトのコンテンツ(プロパティなど)を変更することができるということです。)
constは不変というわけではないのですね。基礎的な部分を再確認することができました。感謝です。
//
学習に使用している本は、もりけん先生推奨の”JavaScript本格入門”です。
あとがき
今回も、JavaScript本格入門・MDN・JavaScript Primerを読んで課題を進めることで、たくさんのことを知ることができました。
また、塾のレビュー体制には本当に感謝です。
速度は遅いですが、1つ1つの課題を自分なりに深堀して進めていきたいです。
ちなみに、私が書いたコードはこちらから確認できます。
今日は以上です。
//
【もりけん塾で勉強しています】
もりけん先生(@terrace_tec)のHPはこちら