【js 】DOM 文書ツリー間を行き来する ノードウォーキング
getElementByIdやquerySelectorAllメソッドはいずれもピンポイントで特定の要素ノード(群)を取得するためのメソッドです。
しかし、いちいち文書全体から目的の要素を検索するのは無駄が多くそれはそのままパフォーマンス低下の原因にもなります。
そこでDOMではあるノードを起点として相対的な位置関係からノードを取得することもできます。
これをノードウォーキングと呼びます。
コードを使用したページ
// カレントノード 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の情報を取得、変更、イベントの登録などができます。
今回は情報を取得するために要素の検索についてまとめます。
コードを使用したページ
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から言語としてモジュールがサポートされました。
モジュールとは、関連性を持たせて切り離された一塊のコード群のことです。
モジュールのメリット
モジュールを使うことで、以下のようなメリットがあります。
- 他のコードとの依存性が少なくなるので、変更や拡張がしやすくなる
- モジュール毎に適切な変数名(名前空間)を割り当てることで、変数名の競合リスクを減らすことができる
- どの機能がどこに書かれているのかが把握しやすくなる
- 汎用性の高いコードをモジュールにすることで再利用しやすくなる
モジュールの基本
// 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); // この右手にお宝(ぱんつ)を!
【js】クラスを定義する
ES6からjsでもclassを定義することができるようになりました。
オブジェクト指向に関わる箇所なので、是非マスターしたいところです。
オブジェクト指向については以前記事にまとめてあります。
https://takuma521.hatenablog.com/entry/2019/07/15/214850
クラスの定義
class Anime { // コンストラクター constructor(options) { this.title = options.title; } // メソッド callTitle() { return this.title; } } const anime = new Anime({ title: 'リゼロ' }); console.log(anime); // Anime {title: "リゼロ"} console.log(anime.callTitle()); // リゼロ
コンストラクターの名前はconstructor
で固定です。
※jsではpublic, protected, private
のようなアクセス修飾子は利用できません。
jsのクラスでは、全てのメンバーがpublicとなります。(どこからでもアクセスできる)
継承
継承とは、クラスの定義の共通する部分を別のクラスにまとめることです。
そうすることで、コードの重複を防ぐことができます。
class Rezero extends Anime { constructor(options) { super(options); this.deathCount = options.deathCount; } returnToDeath() { return `${this.deathCount}回死に戻りました。`; } } const rezero = new Rezero ({ title: 'リゼロ', deathCount:4 }) console.log(rezero); // Rezero {title: "リゼロ", deathCount: 4} console.log(rezero.callTitle()); // リゼロ console.log(rezero.returnToDeath()); // 4回死に戻りました。
クラスを継承するにはextends
を使います。
Rezeroクラスで定義されたreturnToDeath()メソッドはもちろん、
Animeクラスで定義されていたcallTitle()メソッドも呼び出すことができます。
オーバーライド
基底クラス(Animeクラス)で定義されたメソッド、コンストラクターはサブクラス(Rezeroクラス)で上書きすることもできます。
これを、メソッドのオーバーライドと言います。
今回は基底クラス(Animeクラス)で定義されていたcallTitle()メソッドにサブタイトルも返すようにオーバーライドします。
class Rezero extends Anime { constructor(options) { super(options); this.deathCount = options.deathCount; this.subTitle = options.subTitle; } callTitle() { return `${this.title} ${this.subTitle}`; } returnToDeath() { return `${this.deathCount}回死に戻りました。`; } } const rezero = new Rezero ({ title: 'リゼロ', deathCount:4, subTitle: '18話 ゼロから' }) console.log(rezero); // Rezero {title: "リゼロ", deathCount: 4, subTitle: "18話 ゼロから"} console.log(rezero.callTitle()); // リゼロ 18話 ゼロから console.log(rezero.returnToDeath()); // 4回死に戻りました。
コンストラクターの引数にはオブジェクトを渡しているので、コンストラクターにthis.subTitle = options.subTitle;
を追記します。
そして、インスタンス化の際にsubTitleを追加します。
callTitle()メソッドの中身を書き換えます。
すると、callTitle()を呼び出すとtitleだけでなく、subTitleも返すようになります。
【js】配列を操作するメソッドまとめ
今回は配列を操作するときに便利なメソッドをまとめます!
forEach
let titles = ['このすば', 'リゼロ', '幼女戦記', 'オバロ'] titles.forEach((title) => { console.log(title); }); // このすば // リゼロ // 幼女戦記 // オバロ
function showTitle(title) { console.log(title); } titles.forEach(showTitle); // このすば // リゼロ // 幼女戦記 // オバロ
気をつけてもらいたいのが、匿名関数を置かずに名前付きの関数を定義して使用する場合に、
titles.forEach(showTitle());
// undefined is not a function
showTitle()
とカッコを付けてしまうとエラーになるので注意です。
map
mapを使うと配列から新たな配列を作ることができます。
let titles = ['このすば', 'リゼロ', '幼女戦記', 'オバロ'] let secondSeasonTitles = titles.map((title) => { return `${title}2` }); console.log(secondSeasonTitles); // ["このすば2", "リゼロ2", "幼女戦記2", "オバロ2"]
mapの中でreturn
を忘れないようにしましょう。
filter
filterを使うことで条件に合致したものを返せます。
let characters = [ { name: 'スバル', gender: 'man' }, { name: 'レム', gender: 'woman' }, { name: 'ラム', gender: 'woman' }, { name: 'ロズワール', gender: 'man' }, ]; characters.filter((character) => { return character.gender === 'woman'; }); // 0: {name: "レム", gender: "woman"} // 1: {name: "ラム", gender: "woman"}
もちろん、forEachで記述することも可能ですが、可読性を考えるとforEachを使わない方が、読み手にわかりやすいです。
find
findでは条件に合った最初の要素を返します。
let characters = [ { name: 'トン' }, { name: 'チン', id: 1 }, { name: 'カン' }, { name: 'チン', id: 2 }, ]; characters.find((character) => { return character.name === 'チン'; }); // {name: "チン", id: 1}
every, some
everyは全て条件を満たす場合trueを返します。
someはどれかが条件を満たす場合trueを返します。
// 説明 // パワーリフターがsq, bp, dlの合計がjcpStandardRecordという標準記録を超えているかどうか // 超えるとjcpという大会に出場できる let powerLifters = [ { id:1, sq: 200, bp: 140, dl: 230 }, { id:2, sq: 180, bp: 120, dl: 200 }, { id:3, sq: 200, bp: 130, dl: 200 }, ]; const jcpStandardRecord = 510; let allParticipantsFlag ; // 全員出場できる let someParticipantsFlag ; // 誰かは出場できる allParticipantsFlag = powerLifters.every((powerLifter) => { return jcpStandardRecord <= (powerLifter.sq + powerLifter.bp + powerLifter.dl) }); someParticipantsFlag = powerLifters.some((powerLifter) => { return jcpStandardRecord <= (powerLifter.sq + powerLifter.bp + powerLifter.dl) }); console.log(allParticipantsFlag); // false console.log(someParticipantsFlag); // true
someとincludesの使い分け
someとincludesどちらも、特定の要素が含まれているかをtrue/falseで判定しますが、どう使い分けたらいいのでしょうか?
配列の場合、
const animes = [ 'このすば', 'リゼロ', '幼女戦記', 'オバロ'] let targetAnime = 'リゼロ'; animes.some((anime) => { return anime } ); // true animes.includes(targetAnime); // true
どちらもtrueを返します。
しかし、オブジェクトの配列の場合は、
const animes = [ { title: 'このすば', mainCharacter: 'カズマ' }, { title: 'リゼロ', mainCharacter: 'スバル' }, { title: '幼女戦記', mainCharacter: 'ターニャ ' }, { title: 'オバロ', mainCharacter: 'アインズ' } ]; let targetAnime = { title: 'リゼロ', mainCharacter: 'スバル' } animes.some((anime) => { return anime === targetAnime; } ); // false animes.includes(targetAnime); //false
どちらもfalseを返します。
実はオブジェクトを比較するときにその比較は同じ参照を指しているかどうかでテストされるのです。
const animes = [ { title: 'このすば', mainCharacter: 'カズマ' }, { title: 'リゼロ', mainCharacter: 'スバル' }, { title: '幼女戦記', mainCharacter: 'ターニャ ' }, { title: 'オバロ', mainCharacter: 'アインズ' } ]; let targetAnime = animes[1]; animes.includes(targetAnime); // true
このようにすればincludesを使えますが、
基本的にオブジェクトを比較する場合はsomeを用いて
const animes = [ { title: 'このすば', mainCharacter: 'カズマ' }, { title: 'リゼロ', mainCharacter: 'スバル' }, { title: '幼女戦記', mainCharacter: 'ターニャ ' }, { title: 'オバロ', mainCharacter: 'アインズ' } ]; animes.some((anime) => { return anime.title === targetAnime.title && anime.mainCharacter === targetAnime.mainCharacter; } ); // true
このようにします。
reduce
reduceは配列を左から右へ2つの値に対して、同時に関数を適用し単一の値にします。
(右から左へはreduceRightという関数があります。)
例えば、合計値を求めることができます。
let numbers = [1, 2, 3, 4, 5] let sum = numbers.reduce((sum, number) => { return sum + number; }, 0); console.log(sum); // 15
reduceでは、
配列.reduce(コールバック関数(前回の結果, 今回取り出した値), 初期値)
とします。
例でいうと初期値は0です。
コールバック関数の第一引数に前回の結果、一番最初は初期値が入ります。
【js】クロージャ
クロージャとは、ローカル変数を参照している関数内関数のことです。
なんのこっちゃわからないので、次のコードを見てください。
function countPoint(init) { let point = init; return () => { return ++point; } } let myCounter = countPoint(1); console.log(myCounter()); // 2 console.log(myCounter()); // 3 console.log(myCounter()); // 4 console.log(myCounter()); // 5 console.log(myCounter()); // 6
countPoint関数は戻り値は数値ではなく、数値をインクリメントするアロー関数を返しています。
引数や戻り値が関数である関数のことを高階関数と呼ぶのでした。
このように関数が戻り値として返されて起こることがクロージャの仕組みになります。
通常関数の中で使われたローカル変数(ここでいう変数point)は関数の処理が終了した時点で破棄されるはずです。
しかし、countPoint関数から返されたアロー関数がローカル変数のpointを参照し続けているので、
countPoint関数終了後もローカル変数pointは保持され続けるのです。
スコープチェーンで考えてみると、
- アロー関数を表すCallオブジェクト
- countPoint関数のCallオブジェクト
- グローバルオブジェクト
というスコープチェーンがアロー関数が有効である間は保持されるのです。
別の例を見てみましょう。
function countPoint(init) { let point = init; return () => { return ++point; } } let myCounter1 = countPoint(1); let myCounter2 = countPoint(100); console.log(myCounter1()); // 2 console.log(myCounter2()); // 101 console.log(myCounter1()); // 3 console.log(myCounter2()); // 102
ここでわかるように、呼び出しごとに生成されたCallオブジェクトは別物なのです。
つまり、myCounter1とmyCounter2のCallオブジェクトは別物ですし、
そのCallオブジェクトに属するローカル変数pointもそれぞれ別物なのです。
クロージャとはに付け加えると、一種の記憶域を提供する仕組みでもあると言えます。
まとめ
クロージャとは、
- ローカル変数を参照している関数内関数のこと
- 一種の記憶域を提供する仕組みでもある