バグですか?仕様ですか?筋肉です💪

筋肉とプログラミング、その他アウトプットをしていきます。

筋肉エンジニアを名乗っていたニート時代を振り返る

今年2020年の3月から10月の間ニートだった。

社会人になってからこんなにまとまった時間を確保できるのは稀だと思う。

初期のニート時代の記憶はもう薄れてしまっているが、忘れないうちに貴重な?ニート期間に学んだ?ことを書き残したい。

最初は時系列的に、後半は細々したことを書き残す。

ニート初期:ニート概ねストレスはなくなるが、無職の罪悪感というストレスはある

前職はかなり忙しくて休日にも作業を進めないといけない状況だった。

大分記憶が薄れしまっているけど、休日にも"やらなければいけない"ことがあるというのは大きなストレスだった。

しかし、ニートになり、

いつでも好きなタイミングに、起きて、食べて、筋トレして、アニメ観て、寝る。

こんな幸せがあろうか!!いや、ないっっ!!!

という幸せを日々噛み締めニート初期時代を生きていた。

ただそのうち無職であるからこそ自分が、働いていない、社会に属していない、社会のお荷物、う◯こ製造機というストレスを抱えてしまうことになる。

抱える必要のないストレスを抱えたニートだが最初の頃は、ブログ更新、ポートフォリオ作成など勉強はしており、充実していた。(もちろん筋トレも)

ニート中期:ニート転職は難しい

当初、自分のニート計画では最初の1ヶ月Jsを勉強、その後数ヶ月ポートフォリオ作成、その後転職というくそ楽観的な未来を考えていた。

人生というのは自分の思い通りに進む事ばかりではない。

むしろうまくいかないことの方が多い。

特にこういう、

「まあ、なんとかなるっしょ!!」

という時が一番危なく、現実そうなってしまう。

そもそも自分の実力がない、面接が下手、応募の数が少ない、ということもあるが、転職は大変だったと感じた。

新卒でSIerで働き、2年目の5月に退職し約9ヶ月ほどwebでの経験はあった。

だけど、経験年数3年ないと応募できないところが多かった印象。

そんなところばかりではないけど、コロナ渦で、微妙な経験年数、1年前後で退職が2回というのが足かせになってたと思う。

(もちろん実力があるひとはすぐ決まると思うけどね)

反省でいうと、前職を辞めた直後から、もっと言えば、辞める前に転職活動を始めて転職先を決めておくべきだったなと。

普通はそうすると思うけど、自分はなぜかうまくいくと思っていた笑笑

ニート絶望期:ニート頑張っている人を見るのが嫌になる

転職が思うようにうまくいかず勉強のモチベが下がった頃、頑張っている人を見るのが嫌になってしまった。

特にツイッターでの話で、

自分とは反対に頑張っている人のツイートを見ると自分と比較してしまい嫌な気分になった。

なんで、俺は何もしていないんだろうと。

(もちろん筋トレはしている。むしろ筋トレで最低限の精神の安定を保っていた。)

ニート終末期:ニート無職でも楽しむ

勉強のモチベがほぼなくなっていた頃によく2ちゃんねる創設者のひろゆきの動画を見ていた。

特に天下一無職会という動画が面白かった。

無職の人の失敗談や楽しみなどの投稿を紹介するラジオ的なやつ。

最初、自分と同じように罪悪感を感じているニートばかりだと思ってたけど、そうではなかった。

無職だから、時間があるからこそ、それを有意義に使っている人がいた。

例えば、新しくギターを始めたり、拾った葉っぱをお茶にして飲んだりとか。

それら全部が羨ましいとは思うわけではないけど、

その時に、どんな状態でも幸せを感じることはできるし、逆にどんな良い状態でも気持ち次第で幸せと感じられないのかなと思った。

無職になってこれが一番気づけて良かったと思うことだった。

ニートの終わり:ニート転職す!

そんなこんなで、ありがたいお話があり転職を決めることができた。

転職を決められた要因は、

  • なんだかんだ経験年数があったこと
  • ポートフォリオ以外にもブログ更新とか作業したものをGithubに上げていたこと

かなと。(あと筋肉)

実際の正規ルート(応募して面接して採用)ではなかったのだけど、

転職の話を頂いた時に、自分のやったことをいくつも送られたことは、良かったのかなと思う。

ここから先は、細々とした学びを羅列していく。

時間があるからといって勉強するわけではない

これはそう。

人間メリハリが大事。(おそらくそうだけど、主語がでかい)

どんなに時間があっても惰性でyoutubeSNSを見てしまい時間を浪費する。

ではどうすれば勉強するのか。

それは、

  • 今後必要なことか興味のあるものを勉強する
  • ご褒美のために勉強する

かなと。(自分の場合はね)

  • 今後必要なことか興味のあるものを勉強する

やらなくてはいけない、やりたい、の両方もしくはどちらかでないと手をつけることは永遠にない。

  • ご褒美のために勉強する

これは意外に有効だった。

これを買うためにこの日までのこの作業を終わらせるといった感じでやると良かった。

期間を定めて集中できるし、達成できた時に、うぉぉおおおおやったぜぇえええええってなって脳内麻薬どっひゃぁあああああってなる笑

睡眠時間を確保することは心の健康に良い、あと筋肉にも良い

これはニートじゃなくてもできることだと思うし、やるべきことだと思う。

元から睡眠を削る方でないけど、前職では平日かなり時間がなくて7hくらいしか確保できなかった気がする。

(7hも寝てるじゃねーかと突っ込みたくなるかもしれないが、私は最低8h寝ないと日中眠くなってしまう。)

仮に私の睡眠時間が8h必要だったとして平日7hしか寝ていないと、平日5h分のツケを休日に支払うことになる。

事実、前職当時では休日昼過ぎまで寝ていた。

逆にニート時代と現在はしっかり睡眠時間を確保したおかげで、休日も概ね平日と同じように活動できている。

そして、筋肉にも良い影響が出ている気がする。

筋トレやり過ぎにご注意

平日の昼間に筋トレをする、これ以上の幸せがあろうか、いや、ないっっ!!

という幸せを日々噛み締め、毎日気付いたらジムで筋トレをしていた。

筋トレをめちゃくちゃやりこむ時期は必要だと思う。

最初のうちだったら、やったらやった分だけ結果は出るし、

初心者ボーナス期間が終わってても、量をこなす分トレーニングの新たな発見だったりトレーニング自体がうまくなる気がする。

ただ、やったらやった分だけ結果が出るのは前述したように初心者だけだと思う。(ナチュラルの場合は)

どういう理屈かというと、

初心者は神経系が発達していないため高重量を扱えずに筋肉に高負荷の刺激を入れられない。

なので、やり込んでも平気。

初心者を抜けると重量を扱えるようになるので、それをやり込みすぎてしまうと、回復に時間がかかる。

筋肉は、刺激を与え、栄養を入れ、回復して、より強い刺激を与え、成長していく。大きくなる。

やり込みすぎはこの回復がし切れないためにより強い刺激を与えることができず、結果、あまり成長しない。

ということが起こるのではないかと最近考えているし、最近の筋トレ界隈の風潮。

色々理論があるため、一概にこれが正しいとは言い切れないけど、何事も適切な量があると思う。

筋トレに関して、もっと書きたいことが出てきたので別の記事で書くことにする。

アニメはいいぞ

元々アニメはめちゃくちゃ観る方ではなかったけど、エンジニアになり周りで好きな人が多かったため、観るようになった。

ジャンルで言うと、可愛い女の子が出てくるものが多い。(自分で書いといてなんだけども草)

なので、これといって人生に役立つかと聞かれるとそれはないっと即断できる。(即断っだとっ//)

ただ、"面白いもの"に触れるというのは、人生の余暇としては良い時間の過ごし方だと思う。

それに、これは〇〇の役に立つから趣味にする!ってだけというのは個人的につまらない生き方かなと思う。

逆に、これ全く役に立たないんだけどめちゃくちゃ好きでついやっちゃうんだよね!っていう人の方が魅力的に映るし、そういう人の話を聞きたい。

ある意味"人間らしいな"って。

最後に

自分が感じたことでもいつか忘れてしまうので、書き残したいと思った。

ただ今回この文章を書いてみて、改めて気づくこともあったので今後も、ふとした時にブログを更新したいなと思う。

こんな拙い文章でも面白いなと感じた方がいらっしゃればTwitterでもフォローしといてください。

twitter.com

【js】DOM ノードを追加、置換、削除する

ノードの追加、置換、削除についてまとめます。

HTMLの編集には、innerHTMLプロパティを利用することもできますが、今回紹介する方法だと以下のようなメリットがあります。

  • オブジェクトツリーとして操作できるので、対象となるコンテンツが複雑になった場合にも、コードの可読性が悪くなりにくい
  • 要素、属性とテキストとを区別して扱えるので、ユーザーからの入力によってスクリプトが混入するような危険は回避しやすい

シンプルなコンテンツの編集 → innerHTMLプロパティ

複雑なコンテンツの編集 → 本節のアプローチ

ノードを追加

以下はフォームに入力した内容でリンクを追加するコードです。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>heading.html</title>
</head>
<body>
  <form>
    <div>
      <label for="name">サイト名:</label><br />
      <input id="name" name="name" type="text" size="30" />
    </div>
    <div>
      <label for="url">URL:</label><br />
      <input id="url" name="url" type="text" size="50" />
    </div>
    <div>
      <input id="btn" name="name" type="button" value="追加" />
    </div>
  </form>
  <div id="list"></div>
</body>
<html>
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('btn').addEventListener('click', () => {
    // テキストボックスを取得
    const name = document.getElementById('name');
    const url = document.getElementById('url');

    // <a>要素を生成
    let link = document.createElement('a');
    // <a>要素のhref属性を設定
    link.href = url.value;

    // テキストノードを生成
    const text = document.createTextNode(name.value);
    // テキストノードを<a>要素の直下に追加
    link.appendChild(text);

    // <br>要素を生成
    const br = document.createElement('br');

    // <div id="list">を取得
    let list = document.getElementById('list');

    // <div>要素の直下に<a>,<br>要素の順番で追加
    list.appendChild(link);
    list.appendChild(br);
  }, false);
}, false);

Image from Gyazo

1. 要素、テキストノードを作成する

コンテンツを追加するにはまず、createElement, createTextNodeメソッドを使用し、追加したい要素、テキストノードを生成します。

今回だと以下のような箇所で行われています。

// <a>要素を生成
let link = document.createElement('a');

// テキストノードを生成
const text = document.createTextNode(name.value);

// <br>要素を生成
const br = document.createElement('br');

他にも生成するノードに応じて以下のようなメソッドがあります

// 要素ノード
createElement(要素名)

// 属性名ノード
createAttribute(属性名)

// テキストノード
createTextNode(テキスト)

createHgehogeでノードを作成した時点では、お互いの階層関係を意識する必要はありません。

現状はバラバラのままなのでこの後の作業で組み立てる必要があります。

2. ノード同士を組み立てる

1の段階でバラバラになっているノードを組み立ててドキュメントに追加する必要があります。

この作業を行うのがappendChildメソッドです

appendChildメソッドは指定された要素を現在の要素の最後の子要素として追加します。

// テキストノードを<a>要素の直下に追加
link.appendChild(text);

link.firstChild;
// "qiita"

// <div>要素の直下に<a>,<br>要素の順番で追加
list.appendChild(link);
list.appendChild(br);

list.children;
//HTMLCollection(2) [a, br]length: 20: atarget: ""download: ""ping: ""rel: ""relList: DOMTokenList [value: ""]hreflang: ""type: ""referrerPolicy: ""text: "qiita"coords: ""charset: ""name: ""rev: ""shape: ""href: "https://qiita.com/"origin: "https://qiita.com"protocol: "https:"username: ""password: ""host: "qiita.com"hostname: "qiita.com"port: ""pathname: "/"search: ""hash: ""title: ""lang: ""translate: truedir: ""hidden: falseaccessKey: ""draggable: truespellcheck: trueautocapitalize: ""contentEditable: "inherit"isContentEditable: falseinputMode: ""offsetParent: bodyoffsetTop: 132offsetLeft: 8offsetWidth: 33offsetHeight: 16style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}innerText: "qiita"outerText: "qiita"oncopy: nulloncut: nullonpaste: nullonabort: nullonblur: nulloncancel: nulloncanplay: nulloncanplaythrough: nullonchange: nullonclick: nullonclose: nulloncontextmenu: nulloncuechange: nullondblclick: nullondrag: nullondragend: nullondragenter: nullondragleave: nullondragover: nullondragstart: nullondrop: nullondurationchange: nullonemptied: nullonended: nullonerror: nullonfocus: nullonformdata: nulloninput: nulloninvalid: nullonkeydown: nullonkeypress: nullonkeyup: nullonload: nullonloadeddata: nullonloadedmetadata: nullonloadstart: nullonmousedown: nullonmouseenter: nullonmouseleave: nullonmousemove: nullonmouseout: nullonmouseover: nullonmouseup: nullonmousewheel: nullonpause: nullonplay: nullonplaying: nullonprogress: nullonratechange: nullonreset: nullonresize: nullonscroll: nullonseeked: nullonseeking: nullonselect: nullonstalled: nullonsubmit: nullonsuspend: nullontimeupdate: nullontoggle: nullonvolumechange: nullonwaiting: nullonwheel: nullonauxclick: nullongotpointercapture: nullonlostpointercapture: nullonpointerdown: nullonpointermove: nullonpointerup: nullonpointercancel: nullonpointerover: nullonpointerout: nullonpointerenter: nullonpointerleave: nullonselectstart: nullonselectionchange: nullonanimationend: nullonanimationiteration: nullonanimationstart: nullontransitionend: nulldataset: DOMStringMap {}nonce: ""autofocus: falsetabIndex: 0enterKeyHint: ""onpointerrawupdate: nullnamespaceURI: "http://www.w3.org/1999/xhtml"prefix: nulllocalName: "a"tagName: "A"id: ""className: ""classList: DOMTokenList [value: ""]slot: ""attributes: NamedNodeMap {0: href, href: href, length: 1}shadowRoot: nullpart: DOMTokenList [value: ""]assignedSlot: nullinnerHTML: "qiita"outerHTML: "<a href="https://qiita.com/">qiita</a>"scrollTop: 0scrollLeft: 0scrollWidth: 0scrollHeight: 0clientTop: 0clientLeft: 0clientWidth: 0clientHeight: 0attributeStyleMap: StylePropertyMap {size: 0}onbeforecopy: nullonbeforecut: nullonbeforepaste: nullonsearch: nullelementTiming: ""previousElementSibling: nullnextElementSibling: brchildren: HTMLCollection []firstElementChild: nulllastElementChild: nullchildElementCount: 0onfullscreenchange: nullonfullscreenerror: nullonwebkitfullscreenchange: nullonwebkitfullscreenerror: nullnodeType: 1nodeName: "A"baseURI: "http://localhost:63342/memo/temp.html?_ijt=hbsc1amc7s3585dcv59sjlp0cf"isConnected: trueownerDocument: documentparentNode: div#listparentElement: div#listchildNodes: NodeList [text]firstChild: textlastChild: textpreviousSibling: nullnextSibling: brnodeValue: nulltextContent: "qiita"__proto__: HTMLAnchorElement1: br__proto__: HTMLCollection

ここでは、textを要素ノードlinkに追加した上で、この要素ノードlink、brをlist配下に追加しています。

appendChildメソッドは、insertBeforeメソッドに置き換えることもできます。

list.appendChild(link);
list.insertBefore(link, null);

insertBeforeメソッドは第1引数で指定したノードを、第2引数で指定した子ノードの直前に挿入します。

3. 属性ノードを追加する

属性ノードの設定は、属性と同名のプロパティを設定するだけです。

// <a>要素のhref属性を設定
link.href = url.value;

また、createAttributeメソッドで属性ノードを生成することもできます。

let href = document.createAttribute('href');
href.value = url.value;
anchor.setAttributeNode(href);
  • 属性ノードの値を設定するには、valueプロパティを使用
  • 属性ノードを要素ノードに関連づけるには、appendChild, insertBeforeではなく、setAttributeNodeメソッドを使用

このやり方の方が冗長ですが、属性名を文字列で指定できるので、スクリプトから動的に属性名を変更できるメリットがあります。

既存ノードを置換、削除

先ほどの例に、

  • すでにリンクがあった場合はリンクを変更
  • リンクを削除できる

といった処理を追加します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>heading.html</title>
</head>
<body>
  <form>
    <div>
      <label for="name">サイト名:</label><br />
      <input id="name" name="name" type="text" size="30" />
    </div>
    <div>
      <label for="url">URL:</label><br />
      <input id="url" name="url" type="text" size="50" />
    </div>
    <div>
      <input id="btn" name="name" type="button" value="追加" />
      <input id="del" type="button" value="削除" disabled/>
    </div>
  </form>
  <div id="list"></div>
</body>
<html>
document.addEventListener('DOMContentLoaded', () => {
  const name = document.getElementById('name');
  const url = document.getElementById('url');
  const del = document.getElementById('del');
  let list = document.getElementById('list');
  let btn = document.getElementById('btn');

  // 追加ボタンがクリックされた時の処理
  btn.addEventListener('click', () => {
    let link = document.createElement('a');
    link.href = url.value;

    const text = document.createTextNode(name.value);
    link.appendChild(text);

    // <div>要素配下に<a>要素が存在するか(リンクが追加されているか)
    if (list.getElementsByTagName('a').length > 0) {
      // 存在している場合はリンクを変更する
      list.replaceChild(link, list.lastChild);
      btn.value = '変更';
    } else {
      // 存在していない場合でサイト名が入力されているかどうか
      if (link.text) {
        // 新しいリンクを追加し、削除ボタン有効、追加ボタンを変更ボタンに
        list.appendChild(link);
        del.disabled = false;
        btn.value = '変更';
      }
    }
  }, false);

  // 削除ボタンがクリックされた時の処理
  del.addEventListener('click', () => {
    // <div id="list">配下の子要素を削除し、削除ボタンを有効、変更ボタンを追加ボタンに
    list.removeChild(list.lastChild);
    del.disabled = true;
    btn.value = '追加';
  }, false)
}, false);

Image from Gyazo

1. ノードを置換する

子ノードを置き換えるには、replaceChildメソッドを使用します。

list.replaceChild(link, list.lastChild);

第1引数に置き換えたいノードを、第2引数に置き換え対象のノードを指定します。

置き換え対象のノードは現在のノードに対する子ノードでならなければいけません。

2. ノードを削除する

子ノードを削除するには、removeChildメソッドを使用します。

list.removeChild(list.lastChild);

引数には、削除したい対象のノードを指定します。

こちらも同様に、対象のノードは現在のノードに対する子ノードでならなければいけません。

【js】DOM イベント操作

属性値やテキストを取得、設定する方法にについてまとめます。

コードを使用したページ

Qiita

特定の属性を取得、設定

多くの属性は要素ノードの同名のプロパティとしてアクセスできます。

// 設定前
document.querySelectorAll('.tr-Item_title')[3];
// <a class=<200b>"tr-Item_title" href=<200b>"/<200b>kaya517/<200b>items/<200b>31657ec26356efaa7688"><200b>[GitHub] 東京都公式 新型コロナウイルス対策サイトがプルリク募集してる[COVID-19]<200b></a><200b>

// aタグのhref属性の値を設定
document.querySelectorAll('.tr-Item_title')[3].href = 'https://b.hatena.ne.jp/';
//"https://b.hatena.ne.jp/"

// 設定後
document.querySelectorAll('.tr-Item_title')[3];
//<a class=<200b>"tr-Item_title" href=<200b>"https:<200b>/<200b>/<200b>b.hatena.ne.jp/<200b>"><200b>[GitHub] 東京都公式 新型コロナウイルス対策サイトがプルリク募集してる[COVID-19]<200b></a><200b>

例えばこのようにするとqiitaの記事なのにはてぶのトップページに飛ぶようになります。

しかし、classはclassNameでないとアクセスできません。

属性とプロパティとの名前の違いを意識したくないならば、getAttribute, setAttributeメソッドを使用しましょう。

// 設定前
document.getElementById('globalHeader').className;
// "st-Header"

// クラス名を設定する
document.getElementById('globalHeader').className = 'hogehoge';
// "hogehoge"

// 設定後
document.getElementById('globalHeader').className;
"hogehoge"


// getAttribute, setAttributeメソッドの場合

// 設定前
document.getElementById('globalHeader').getAttribute('class');
// "hogehoge"

// クラス名を設定する
document.getElementById('globalHeader').setAttribute('class', 'fugafuga');
// undefined

// 設定後
document.getElementById('globalHeader').getAttribute('class');
// "fugafuga"

不特定の属性を取得

特定の要素ノードに属する全ての属性を取得したい場合には、attributesプロパティを使用します。

// 全ての属性を取得
let attrs = document.getElementsByClassName('st-Header_searchInput')[0].attributes;

// forEachを使うために配列に変換
attrs = Array.from(attrs);
// (6) [class, placeholder, type, autocomplete, name, required]

全ての属性を表示
attrs.forEach((attr) => console.log(`${attr.name}: ${attr.value}`));
// class: st-Header_searchInput
// placeholder: キーワードを入力
// type: search
// autocomplete: off
// name: q
// required:

属性を追加、削除するには

// 新規にtitle属性を追加

// 全ての属性を取得
let attrs = document.getElementsByClassName('st-Header_searchInput')[0].attributes;

attrs
// NamedNodeMap {0: class, 1: placeholder, 2: type, 3: autocomplete, 4: name, 5: required, class: class, placeholder: placeholder, type: type, autocomplete: autocomplete, name: name, …}

let title = document.createAttribute('title');

title.value = 'タイトル';

title
// title=<200b>"タイトル"

// 属性を追加
attrs.setNamedItem(title);

attrs
// NamedNodeMap {0: class, 1: placeholder, 2: type, 3: autocomplete, 4: name, 5: required, 6: title, class: class, placeholder: placeholder, type: type, autocomplete: autocomplete, name: name, …}

// 既存のname属性を削除

// 属性を削除
attrs.removeNamedItem('name');

attrs
// NamedNodeMap {0: class, 1: placeholder, 2: type, 3: autocomplete, 4: required, 5: title, class: class, placeholder: placeholder, type: type, autocomplete: autocomplete, required: required, …}

テキストを取得、設定

要素は以下のテキストを取得、設定するにはinnerHTML, textContentというプロパティを使用します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>heading.html</title>
</head>
<body>
  <div id="text">
    <p style="color: Red">設定なし</p>
  </div>
  <div id="html">
    <p style="color: Red">設定なし</p>
  </div>
</body>
<html>
document.addEventListener('DOMContentLoaded', function() {
  document.getElementById('text').textContent = '<a href="https://qiita.com/">qiitaへのリンク</a>';
  document.getElementById('html').innerHTML = '<a href="https://qiita.com/">qiitaへのリンク</a>';
}, false);

f:id:takuma521:20200308183614p:plain

両者のプロパティに共通しているのは、

配下の子要素、テキストを完全に置き換えている

元々あった

要素は残っていません。

異なる点は、

与えられたテキストをHTML文字列として認識するかどうかです。

textContentプロパティでは、プレーンテキストとして埋め込まれるので、タグ文字列がそのまま表示されています。

innerHTMLプロパティでは、HTMLテキストを埋め込むのでリンクが有効になっています。

一般的に、HTML文字列を埋め込むのでなければ、textContentプロパティを優先して使用したほうが、高速でかつ、セキュリティー上の問題も発生しにくいです。

【js 】DOM 文書ツリー間を行き来する ノードウォーキング

getElementByIdやquerySelectorAllメソッドはいずれもピンポイントで特定の要素ノード(群)を取得するためのメソッドです。

しかし、いちいち文書全体から目的の要素を検索するのは無駄が多くそれはそのままパフォーマンス低下の原因にもなります。

そこでDOMではあるノードを起点として相対的な位置関係からノードを取得することもできます。

これをノードウォーキングと呼びます。

コードを使用したページ

Qiita

// カレントノード
const currentNode = document.querySelectorAll('.tr-Item')[3];
currentNode
// <div class=<200b>"tr-Item"><200b><a class=<200b>"tr-Item_userImage" href=<200b>"/<200b>tomikiya"><200b>…<200b></a><200b><div class=<200b>"tr-Item_body"><200b>…<200b></div><200b></div><200b>

// 親ノードを取得
currentNode.parentNode
// <div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>…<200b></div><200b><div class=<200b>"tr-Item"><200b>....


// 最初の子ノードを取得
currentNode.firstChild
// <a class=<200b>"tr-Item_userImage" href=<200b>"/<200b>tomikiya"><200b><img src=<200b>"https:<200b>/<200b>/<200b>qiita-user-profile-images.imgix.net/<200b>https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F97601%2Fprofile-images%2F1473707244?ixlib=rb-1.2.2&auto=compress%2Cformat&lossless=0&w=48&s=f1b22bd97397df6085c15efc4fbc0ad4" alt=<200b>"tomikiya"><200b></a><200b>

// 最後の子ノードを取得
currentNode.lastChild
// <div class=<200b>"tr-Item_body"><200b><a class=<200b>"tr-Item_title" href=<200b>"/<200b>tomikiya/<200b>items/<200b>11d7a21ce778038567c9"><200b>[Excel]実務で役に立たない!?VBAを使わないで3D迷路を作る<200b></a><200b><div class=<200b>"tr-Item_meta"><200b>…<200b></div><200b></div><200b>

// 子ノードに含まれるノード数を取得
currentNode.childNodes.length
// 2

// 直前のノード(兄ノード)
currentNode.previousSibling
// <div class=<200b>"tr-Item"><200b><a class=<200b>"tr-Item_userImage" href=<200b>"/<200b>youwht"><200b>…<200b></a><200b><div class=<200b>"tr-Item_body"><200b>…<200b></div><200b></div><200b>
// カレントノードが[3]番目を取得していたのに対し、previousSiblingは1つ手前の[2]番目のノードを取得している
currentNode.previousSibling === document.querySelectorAll('.tr-Item')[2];
//true

// 直後のノード(弟ノード)
currentNode.nextSibling
// <div class=<200b>"tr-Item"><200b><a class=<200b>"tr-Item_userImage" href=<200b>"/<200b>mi_upto"><200b>…<200b></a><200b><div class=<200b>"tr-Item_body"><200b>…<200b></div><200b></div><200b>
// カレントノードが[3]番目を取得していたのに対し、nextSiblingは1つ後の[4]番目のノードを取得している
currentNode.nextSibling === document.querySelectorAll('.tr-Item')[4];
// true

これでノードウォーキングをすることができますが、以下の例を見てください。

コードを使用したページ

スタック・オーバーフロー

// カレントノード取得
const currentNode = document.getElementsByTagName('li')[0];

// 弟ノード取得?
currentNode.nextSibling;
// #text

// ノードの種類判定
currentNode.nextSibling.nodeType
// 3(テキストノード)

実はnextSiblingなど上記で紹介した取得方法では、要素ノード以外も取得してしまいます。

この場合だとテキストノードを取得してしまいます。

currentNode.nodeType
// 1(要素ノード)

要素ノードを取り出したい場合はnodeTypeが1であるのでその条件で取得するのもありですが、

const nextNode = document.getElementsByTagName('li')[1];
nextNode
// <li class=<200b>"-item inbox-button-item"><200b>…<200b></li><200b>

currentNode.nextElementSibling;
// <li class=<200b>"-item inbox-button-item"><200b>…<200b></li><200b>

currentNode.nextElementSibling.nodeType
// 1

currentNode.nextElementSibling === nextNode;
// true

のようにnextElementSiblingを使えば要素ノードに限定してくれます。

// 最初の子要素ノードを取得
currentNode.firstElementChild

// 最後の子要素ノードを取得
currentNode.lastElementChild

// 直前の要素ノードを取得
currentNode.perviousElementSibling

// 直後の要素ノードを取得
currentNode.nextElementSibling

// 子ノードに含まれる要素ノード数を取得
currentNode.childElementCount

// 親ノードを取得
currentNode.parentNode

ちなみにnodeTypeの一覧です。

要素ノード    = 1
属性ノード    = 2
テキストノード  = 3
CDATAセクション = 4
実体参照ノード  = 5
実体宣言ノード  = 6
処理命令ノード  = 7
コメントノード  = 8
文書ノード    = 9
文書型宣言ノード = 10
文書の断片    = 11
記法宣言ノード  = 12

【js】DOM 要素の検索まとめ

DOM(Document Object Model)はHTMLをJavascriptから操作できるようにしたインターフェイスのことです。

JavascriptからはDOM APIを通じてHTMLの情報を取得、変更、イベントの登録などができます。

今回は情報を取得するために要素の検索についてまとめます。

コードを使用したページ

Qiita

id指定

指定したIDを持つ要素をElementオブジェクトとして返します。

document.getElementById('globalHeader');
// <div class=<200b>"st-Header" id=<200b>"globalHeader"><200b>…<200b></div><200b>

タグ名指定

タグ名をキーに要素を取得します。

document.getElementsByTagName('form');
// HTMLCollection(2) [form.st-Header_search, form.st-Header_searchModal]
// length: 2
// 0: form.st-Header_search
// 1: form.st-Header_searchModal
// __proto__: HTMLCollection

idをキーにする場合と異なり、複数の要素を取得する可能性があるので、getElementsByTagNameの戻り値は要素の集合になります。

また、引数に*を指定することで全ての要素を取得することもできます。

const allTag = document.getElementsByTagName('*');
// HTMLCollection(1040) [html, head, meta, title, meta, meta, meta, link, link, meta,,,,,

// リストに含まれる要素数
allTag.length
// 1040

// i番目の要素を取得(allTag[0]でも取得可能)
allTag.item(0)
// <html><200b><head><200b>…<200b></head><200b><body data-__gyazo-extension-added-paste-support=<200b>"true"><200b>…<200b></body><200b></html><200b>

// id,またはname属性が一致する要素を取得
allTag.namedItem('globalHeader');
// <div class=<200b>"st-Header" id=<200b>"globalHeader"><200b>…<200b></div><200b>

// 属性にアクセスする
allTag[11]
// <link rel=<200b>"shortcut icon" type=<200b>"image/<200b>x-icon" href=<200b>"https:<200b>/<200b>/<200b>cdn.qiita.com/<200b>assets/<200b>favicons/<200b>public/<200b>production-c620d3e403342b1022967ba5e3db1aaa.ico"><200b>
allTag[11].type
// "image/x-icon"

getElementsByTagNameメソッドの戻り値であるHTMLCollectionオブジェクトはlength, item(i), namedItem(name)が利用できます。

name属性指定

document.getElementsByName('q');
// NodeList(2) [input.st-Header_searchInput, input.st-Header_searchModalInput]

getElementsByNameメソッドでは戻り値がNodeListですが、namedItemメソッドが利用できないくらいで、ほぼHTMLCollectionと同じです。

class指定

class属性をキーに要素群(HTMLCollection)を取得します。

document.getElementsByClassName('st-Header st-Footer');
// HTMLCollection [div#globalHeader.st-Header, globalHeader: div#globalHeader.st-Header]

document.getElementsByClassName('class1 class2');のように空白区切りで複数のクラス名を記述することで、class1, class2両方が指定された要素だけを検索します。

document.getElementsByClassName('st-HeaderAlert st-HeaderAlert-warning');
// HTMLCollection [div.st-HeaderAlert.st-HeaderAlert-warning]

cssセレクターで指定

これまでのgetHogehogeメソッドが特定の名前、属性値をキーに要素を検索していたのに対して、

より、複雑な条件での検索を可能にするのが、querySelector, querySelectorAllメソッドです。

querySelectorは合致した最初の要素を返しquerySelectorAllは合致した全ての要素を返します。

// 最初に合致した要素を取得
document.querySelector('.tr-Item');
// div.tr-Item

// 合致した全ての要素を取得
document.querySelectorAll('.tr-Item');
// NodeList(30) [div.tr-Item, div.tr-Item, div.tr-Item, div.tr-Item,,,,
// 全ての要素を取得 *
document.querySelectorAll('*');
// NodeList(1040) [html, head, meta, title, meta, meta, meta, link,,,,,

// 指定したIDの要素を取得 #id
document.querySelector('#globalHeader');
// <div class=<200b>"st-Header" id=<200b>"globalHeader"><200b>…<200b></div><200b>

// 指定したクラス名の要素を取得 .class
document.querySelectorAll('.tr-Item');
// NodeList(30) [div.tr-Item, div.tr-Item, div.tr-Item, div.tr-Item,,,,

// 指定したタグ名を取得 elem
document.querySelectorAll('form');
// NodeList(2) [form.st-Header_search, form.st-Header_searchModal]

// parent要素の子要素childを取得
document.querySelectorAll('.st-Header_start > form');
// NodeList [form.st-Header_search]

// ancestor要素の子孫要素descendantを全て取得
document.querySelectorAll('html form');
NodeList(2) [form.st-Header_search, form.st-Header_searchModal]

// prev要素の直後のnext要素を取得
document.querySelector('.tr-Item + div');
// <div class=<200b>"tr-Item"><200b>…<200b></div><200b>

// prev要素の以降のsiblings兄弟要素を取得
document.querySelectorAll('.tr-Item ~ div');
// NodeList(29) [div.tr-Item, div.tr-Item, div.tr-Item, div.tr-Item,,,,,

// 指定した属性を持つ要素を取得
document.querySelector('input[type]');
// <input class=<200b>"st-Header_searchInput" placeholder=<200b>"キーワードを入力" type=<200b>"search" autocomplete=<200b>"off" name=<200b>"q" required><200b>

// 属性がvalue値に等しい要素を取得
document.querySelector('input[type= "text"]');
// <input class=<200b>"st-Header_searchModalInput" type=<200b>"text" autocomplete=<200b>"off" placeholder=<200b>"キーワードを入力" name=<200b>"q" required><200b>

// 属性がvalueから始まる値をもつ要素を取得
document.querySelector('a[href^="http"]');
// <a class=<200b>"st-Header_dropdownItem st-RealmItem" href=<200b>"https:<200b>/<200b>/<200b>qiita.com/<200b>"><200b>…<200b></a><200b><200b>

// 属性がvalueで終わる値をもつ要素を取得
document.querySelector('img[src$=".png"]');
// <img alt=<200b>"COTOHA API" src=<200b>"/<200b>/<200b>cdn.qiita.com/<200b>assets/<200b>public/<200b>qiita-x-cotoha-4424ecc70ff95b4106a44dab2749eade.png"><200b>

// 属性がvalueを含む値をもつ要素を取得
document.querySelector('[title*="Qiita"]');
// <link href=<200b>"/<200b>opensearch.xml" rel=<200b>"search" title=<200b>"Qiita" type=<200b>"application/<200b>opensearchdescription+xml"><200b>

// 複数の属性フィルタ全てにマッチする要素を取得
document.querySelector('img[src][alt]');
<img src=<200b>"https:<200b>/<200b>/<200b>qiita-user-profile-images.imgix.net/<200b>https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F1050777844039573504%2FP4feUgGa_bigger.jpg?ixlib=rb-1.2.2&auto=compress%2Cformat&lossless=0&w=48&s=ca8cb412d7885df16ba5dbae92cba870" alt=<200b>"t_fit_engineer"><200b>

【js】ES6 モジュール

ES6から言語としてモジュールがサポートされました。

モジュールとは、関連性を持たせて切り離された一塊のコード群のことです。

モジュールのメリット

モジュールを使うことで、以下のようなメリットがあります。

  1. 他のコードとの依存性が少なくなるので、変更や拡張がしやすくなる
  2. モジュール毎に適切な変数名(名前空間)を割り当てることで、変数名の競合リスクを減らすことができる
  3. どの機能がどこに書かれているのかが把握しやすくなる
  4. 汎用性の高いコードをモジュールにすることで再利用しやすくなる

モジュールの基本

// lib/Util.js
const AUTHOR = 'tappei nagatsuki'

export class Anime {
  constructor(title) {
    this.title = title;
  }

  callTitle() {
    return this.title;
  }
}

export class Workout {
  constructor(name) {
    this.name = name;
  }

  getTrainingVolume(weight, rep, set) {
    return `${}のトレーニングボリュームは${weight * rep * set}kgです`;
  };
}

モジュールは1つのファイルとして定義するのが基本です。

この例だとlib/Util.jsがそれです。

モジュールとして外部から使用するためにはexportキーワードを記述します。

ここでは、Animeクラス、Workoutクラスに記述していますが、それ以外に、

変数、定数、関数の宣言にも記述できます。

定数AUTHORにはexportの記述がないため外から参照することはできません。

// main.js
import { Anime, Workout } from './lib/Util'

const anime = new Anime('リゼロ');
console.log(anime.callTitle());
// リゼロ

const workout = new Workout('ベンチプレス');
console.log(workout.getTrainingVolume(100, 10, 5));
// ベンチプレスのトレーニングボリュームは5000kgです

別ファイルで定義されたモジュールをインポートするためにはimport命令が必要です。

ここでは、Animeクラス、Workoutクラスをインポートしています。

import命令の様々な記法

1. モジュール全体をまとめてインポート

// main2.js
import * as app from './lib/Util'

const anime = new app.Anime('リゼロ');
console.log(anime.callTitle());
// リゼロ

const workout = new app.Workout('ベンチプレス');
console.log(workout.getTrainingVolume(100, 10, 5));
// ベンチプレスのトレーニングボリュームは5000kgです

アスタリスク*でモジュール内の全ての要素をインポートできます。

この場合as句で別名を指定する必要があります。

2. モジュール内の個々の要素に別名を付与する

// main2.js
import { Anime as MyAnime, Workout as MyWorkout } from './lib/Util'

const anime = new MyAnime('リゼロ');
console.log(anime.callTitle());
// リゼロ

const workout = new MyWorkout('ベンチプレス');
console.log(workout.getTrainingVolume(100, 10, 5));
// ベンチプレスのトレーニングボリュームは5000kgです

モジュールの個々の要素に別名を付与することもできます。

3. デフォルトのエクスポートをインポートする

// lib/Anime.js

export default class {
  constructor(title) {
    this.title = title;
  }

  callTitle() {
    return this.title;
  }
}

モジュールに含まれる要素が1つであれば、デフォルトのエクスポートを宣言できます。

クラス名、関数名を書かずにdefaultキーワードをつけます。

// main_default.js
import Anime from './lib/Anime'

const anime = new Anime('リゼロ');
console.log(anime.callTitle());
// リゼロ

import命令により、Animeモジュールのデフォルトエクスポートに対して、Animeという名前でアクセスできます。

【js】ES6 オブジェクトリテラル

オブジェクトリテラル

ES6ではオブジェクトリテラルの構文もシンプルに表現できるようになりました。

const title = 'この素晴らしい世界に祝福を';
const mainCharacter = 'カズマ';

const Anime = {
  title: title,
  mainCharacter: mainCharacter,
  subTitle1: 'この自称女神と異世界転生を!',
  subTitle2: 'この中二病に爆焔を!',
  subTitle3: 'この右手にお宝(ぱんつ)を!',
  callInformation: function() {
    return console.log(`このアニメのタイトルは「${this.title}」です。主人公の名前は「${this.mainCharacter}」です。`);
  }
};

Anime.callInformation();
// このアニメのタイトルは「この素晴らしい世界に祝福を」です。主人公の名前は「カズマ」です。

console.log(Anime.subTitle1);
// この自称女神と異世界転生を!
console.log(Anime.subTitle2);
// この中二病に爆焔を!
console.log(Anime.subTitle3);
// この右手にお宝(ぱんつ)を!

これが以前の書き方ですが、

  • メソッド(関数型のプロパティ)としての定義をシンプルにできる
  • 変数を同名のプロパティに割り当てられる
  • プロパティを動的に生成できる

ことができるようになりました。

メソッド(関数型のプロパティ)としての定義をシンプルにできる

メソッド(関数型のプロパティ)としての定義をする場合には、: functionを省略できます。

const Anime = {
  callInformation() {
    return console.log(`このアニメのタイトルは「${this.title}」です。主人公の名前は「${this.mainCharacter}」です。`);
  }

変数を同名のプロパティに割り当てられる

プロパティ名: 変数名と記述していましたが、

プロパティ名と変数名が同名の場合は省略して以下のように書けます。

const Anime = {
  title,
  mainCharacter,

プロパティを動的に生成できる

プロパティ名をブラケット([]のこと)でくくり動的にプロパティ名を生成できます。

これをComputed property namesと呼ぶそうです。

let i = 0;

const Anime = {
  [`subTitle${++i}`]: 'この自称女神と異世界転生を!',
  [`subTitle${++i}`]: 'この中二病に爆焔を!',
  [`subTitle${++i}`]: 'この右手にお宝(ぱんつ)を!',

変数iに順に加算することでsubTitle1, subTitle2, subTitle3 ...のようなプロパティ名を生成できます。

まとめ

ES6からオブジェクトリテラルが以下のようによりシンプルに書けるようになった。

  • メソッド(関数型のプロパティ)としての定義をシンプルにできる
  • 変数を同名のプロパティに割り当てられる
  • プロパティを動的に生成できる
const title = 'この素晴らしい世界に祝福を';
const mainCharacter = 'カズマ';
let i = 0;

const Anime = {
  title,
  mainCharacter,
  [`subTitle${++i}`]: 'この自称女神と異世界転生を!',
  [`subTitle${++i}`]: 'この中二病に爆焔を!',
  [`subTitle${++i}`]: 'この右手にお宝(ぱんつ)を!',
  callInformation() {
    return console.log(`このアニメのタイトルは「${this.title}」です。主人公の名前は「${this.mainCharacter}」です。`);
  }
};

Anime.callInformation();
// このアニメのタイトルは「この素晴らしい世界に祝福を」です。主人公の名前は「カズマ」です。

console.log(Anime.subTitle1);
// この自称女神と異世界転生を!
console.log(Anime.subTitle2);
// この中二病に爆焔を!
console.log(Anime.subTitle3);
// この右手にお宝(ぱんつ)を!