Knowledge Base

お知らせや身辺のことを綴っています。

JavaScript の勉強

JavaScript (VanillaJS)の勉強を始めた。PHP にある変数のリファレンス渡しとかがなかったり、変数の扱いがゆるかったりしてすごい言語だなと驚かされた。DOM操作をマスターすることが最終目的であるが、その前に学ぶべきことも多くあると考えられるので、勉強がてらにどんどん内容を追加していく予定。

反復処理

thread_titles にクロージャを突っ込んで配列として代入してくれるコードをおれが書いた。(もちろんクソコード)

const parent = document.getElementsByClassName('title');
let parent_length = parent.length;

let thread_titles = function(){
    titles = new Array(); // URL を格納する配列

    for (let i = 0; i < parent_length; i++) {
        tmp_thread_titles = parent[i].getElementsByTagName('a')[0].text;
        titles.push(tmp_thread_titles);
    }

    return titles;
}();

let output = thread_titles;
console.log(output);

友だちがコードレビューしてくれた結果、let thread_titlesの部分に関して改善案をくれた。

const parent = document.getElementsByClassName('title');
const thread_titles = function(){
    const titles = [];

    for (const element of parent) {
        title = element.querySelector('a').text;
        titles.push(title);
    }

    return titles;
}();

メモ

const 宣言は、値への読み取り専用の参照を作ります。これは、定数に保持されている値は不変ではなく、その変数の識別子が再代入できないということです。たとえば、定数の中身がオブジェクトの場合、オブジェクトの内容(プロパティなど)は変更可能です。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const

本人だったらこう書くらしい

const parent = document.getElementsByClassName('title');
const thread_titles = Array.from(parent).map(element => (
    element.querySelector('a').text
));

関数式について

変数に代入して関数定義を行う構文を関数式という。一方で function を使って関数を定義する方法を関数宣言という。

いずれの際も、関数を使用する際、括弧なしでは関数オブジェクトが返ってきてしまうため、関数を呼び出す時は何らかの形で括弧を付けるようにする。その際、従来の関数構文を使う際、引数を早い段階で固定したいなら変数定義時に括弧を付けることもできるし、そうでなければ呼び出す時に括弧を付けても良い。

引数を取らない関数式

アロー関数を使用する際は、呼び出し元で括弧を付ける。お好みで return を付ける場合は、hello3() のように構文にカーリーブラケットを付ける。

{
  let hello1 = function() {
  	return 'やっはろー'
  }()
  console.log(hello1)
  // やっはろー

  let hello2 = () => 'やっはろー'
  console.log(hello2())
  // やっはろー

  let hello3 = () => {
    return 'やっはろー'
  }
  console.log(hello3())
  // やっはろー
}

引数を取る関数式

アロー関数を使用する際、取りうる引数が1つだけなら引数を囲む括弧は省略できる。

{
  let hello1_arg = function(obj) {
    return 'やっはろー' + obj
  }
  console.log(hello1_arg('!'))
  // やっはろー!
  
  let hello2_arg = obj => 'やっはろー' + obj
  console.log(hello2_arg('!'))
  // やっはろー!
}

関数型プログラミング

こういったループやカウンターを用いず、変数の代入や配列操作をその場の関数の定義によって行ってしまうような手法を手続き型プログラミングと対比して、関数型プログラミングというらしい。

単純な手続き型の手法で配列操作を行う際、まずは for ループが第一選択肢となる。しかし、この for ループはカウンタ変数を必要とする。foreach の場合も戻り値を返す関数の中で使うなら、一度配列から取り出した値を格納するための変数が必要である。いずれの場合も、ミュータブルな変数が必要になってくる。このように、ある目的を達成するために、目的以外の変数まで変化することを強いてしまうことを、巷では副作用を生むというらしい。

副作用を生むことは、扱う変数の数が増え、同時にプログラムの安全性が保証されづらくなってしまうことを意味する。一方で、関数型プログラミングの場合だとその配列操作は変数の定義ないし準備段階で完了してしまい、この操作は他の変数の著しい変異を伴うこともなく静的である。ゆえに、より安全なコードの記述が期待される。

map() を使った配列のzip化

{
  const fruits = ['apple', 'banana', 'grape'];
  const vegetables = ['carrot', 'tomato', 'peanut']
  // i はインデックス番号
  const combined = fruits.map((e, i) => [e, vegetables[i]])
  
  console.log(combined)

  // 0: Array(3) [ "apple", "banana", "grape" ]
  // 1: Array(3) [ "carrot", "tomato", "peanut" ]
}

オブジェクトの場合でも同様に可能。

{
  // 一人一人に野菜とフルーツをそれぞれ1つずつプレゼント
  const name = ['Ken', 'Hana', 'Kumi'];
  const fruits = ['apple', 'banana', 'grape'];
  const vegetables = ['carrot', 'tomato', 'peanut'];
  
  // i はインデックス番号
  const present = name.map((e, i) => {
    return {
      name: e,
      given: [fruits[i], vegetables[i]],
    }
  });
  
  console.log(present);
}

Try-Catch 文を用いた失敗処理

他の言語を使っているとき、特にエラーを Try-Catch 文を用いてハンドリングすることはなかった。自分はかなり簡単なプログラムを書いていたのもあって、それから正直勉強するのが面倒くさいと感じていたのもあって、今まで失敗処理を if 文を用いつつ無理やり exit() で押し流していた。ところが、exit() 関数のないJavaScript ではどうもそうはいかないようだ。

Map Object

キーと値のペアを保持し、キーが最初に挿入されたインデックスをその構造自体に記録しているが、同じキー名は存在できない。キー名が要らないなら普通に Array を使った方が良い。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map

{
  let contacts = new Map()
  
  contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
  contacts.set('Hilary', {phone: "617-555-4321", address: "321 S 2nd St"})
  // キー名を名無しにしても一応大丈夫だった
  contacts.set('', {phone: "512-742-8463", address: "213 S 1st Ret"})

  console.log(contacts);
}

コンソールの結果

演算子

演算子の使い方について興味深いものがあったので書き残していく。

論理演算子

AND 演算子は、複数の AND された値の中から最初に false となる値を探し、OR 演算子は最初に true となる値を探す!今まで if 節などで論理演算子を使いつつ条件判定を行ってきたが、特にその論理演算子自体が true や false を返すという考えには至っていなかった。ドキュメントで改めて該当箇所を読んだら本質的なところを理解できたような気がする。

この即時関数は、二つの値 a >= 10false の中から最初に true となるものを返す。与えられた引数は 10 で、a >= 10 が true となるので、返り値はその真偽値が返されることになる。

値が null あるいは undefined かどうかを確認したいときには、Null 合体演算子を使うと良い。

{
    (function is_this_more_than(a) {
        return a >= 10 || false;
    })(10);    // true
}

型を変換するために用いられる演算子

NOT 演算子を使って引数の型を Boolean 型に変換する例。

{
    function is_empty(a) {
        return !a; // Boolean(a) でもできる
    }
    is_empty(''); // true
    is_empty(9); // false
}

単項演算子を使って引数の型を Number 型に変換する例。

{
    typeof((function (a) {
        return +a;
    }))('9'); // 'number'
}