Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って始めたブログ

JavaScript Primerを読んだ話

JavaScriptへの理解を深めるために、JavaScript Primerを読み始めた。
第一部を読み終えたので、理解定着のためはじめて知った事大事だと思った事を書いておく。

セミコロンは付けておいた方が良い。

セミコロンを省略して書いている人のコードをよく見かける(私自身も省略して書く)のだが、「暗黙的なものへ頼ると意図しない挙動が発生する」との記述があり、セミコロンは常に書くようにしたほうが良いとのこと。

何故varがダメだったのか?

私がJavaScriptのコードを書くようになった時点で、既にletがあり、なんとなくvarは使ってはいけない風潮があったので特に気にはしていなかった。
しかし、「歴史を知ることは同じ過ちを繰り返さない事に繋がる」と、おじいちゃんに言われていた事を思い出し、歴史を知ることにした。

varがダメな理由は、大きく以下の2つがある。

  1. 同じ名前の変数を再定義できる
  2. 変数の巻き上げと呼ばれる意図しない挙動がある

1.は以下の通り。

var x = 'hoge'
var x = 'fuga'
console.log(x) // fuga

2.は以下の通り。巻き上げとは、ブロックスコープを無視して最も近い変数に紐づけてしまうこと。

// var宣言より前に参照してもエラーにならない
console.log(x); // => undefined
var x = "varのx";

function fn() {
    // 内側のスコープにあるはずの変数`x`が参照できる
    console.log(x); // => undefined
    {
        var x = "varのx";
    }
    console.log(x); // => "varのx"
}
fn();

たしかに、こんな事ができてしまうと意図しない挙動が起きる可能性が高そうです。
letが実装されたのも頷けます。

Nullish coalescing演算子

私の場合、PHPの方で先に知ったかもしれない・・・
JavaScriptにもあったんですね。nullishな値なのでundefinedもちゃんと含みます。

// 左辺がnullishであるため、右辺の値の評価結果を返す
console.log(null ?? "右辺の値"); // => "右辺の値"
console.log(undefined ?? "右辺の値"); // => "右辺の値"

// 左辺がnullishではないため、左辺の値の評価結果を返す
console.log(true ?? "右辺の値"); // => true
console.log(false ?? "右辺の値"); // => false
console.log(0 ?? "右辺の値"); // => 0
console.log("文字列" ?? "右辺の値"); // => "文字列"

Arrow Functionの特徴

これはだいたい知っている内容だったんですが、1つ気になる記述が・・・

  • arguments変数を参照できない

arguments変数って何?

function fn() {
    // `arguments`はインデックスを指定して各要素にアクセスできる
    console.log(arguments[0]); // => "a"
    console.log(arguments[1]); // => "b"
    console.log(arguments[2]); // => "c"
}
fn("a", "b", "c");

おぉ、引数を定義していないのに要素にアクセスできるのか!!
正直知らなかった(= = ;;

オブジェクトの複製

本書にはshallow copydeep copyのサンプルコードが載っていたので、これは是非とも参考にしたい。

in演算子とObject#hasOwnPropertyの違い

  • hasOwnPropertyメソッド ・・・ そのオブジェクト自身が指定したプロパティを持っているかを判定する。
  • in演算子 ・・・ オブジェクト自身が持っていなければ、そのオブジェクトの継承元であるprototypeオブジェクトまで探索して持っているかを判定する。
const foo = {
  foo: "Foo",
  bar: {
    bar: "Bar"
  }
};

// そのオブジェクト自身が指定したプロパティを持っているかを判定
console.log(foo.hasOwnProperty('foo')); // true
console.log(foo.hasOwnProperty(toString)); // false

// prototypeオブジェクトまで探索して持っているかを判定
console.log('foo' in foo); // true
console.log('toString' in foo); // true

こういった細かい違いは、はじめて知りました(= = ;;

JavaScript文字コードの話

このあたりはとても興味深い話だった。
JavaScript文字コードとしてUnicodeを採用しいて、エンコード方式としてUTF-16を採用している。と書いてあって、最初は「ふーん」だったのですが、そこからCode PointCode Unitの話になると、「あれ?これ知ってないとマズくない?」と思いました。

配列をフラットに・・・

Array#flatメソッドというのがあるようです。これは初めて知りました(= = ;;
おぉ、便利そう・・・

const array = [[["A"], "B"], "C"];

// 引数なしは 1 を指定した場合と同じ
console.log(array.flat()); // => [["A"], "B", "C"]
console.log(array.flat(1)); // => [["A"], "B", "C"]
console.log(array.flat(2)); // => ["A", "B", "C"]

// すべてをフラット化するには Infinity を渡す
console.log(array.flat(Infinity)); // => ["A", "B", "C"]

正規表現を使った文字取得

String#matchAllメソッドというのがあるようです。これも初めて知りました(= = ;; 今までRegExp#execメソッドで使っていた実装していたところが、String#matchAllメソッドで置き換え可能っぽいですね。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// matchAllはIteratorを返す
const matchesIterator = str.matchAll(alphabetsPattern);
for (const match of matchesIterator) {
    // マッチした要素ごとの情報を含んでいる
    console.log(`match: "${match[0]}", index: ${match.index}, input: "${match.input}"`);
}
// 次の順番でコンソールに出力される
// match: "ABC", index: 0, input: "ABC あいう DE えお"
// match: "DE", index: 8, input: "ABC あいう DE えお"

クロージャ

本書のクロージャーの説明は「どういった仕組みでクロージャーを実現しているのか?」をメインに書いており、とても面白かったし、わかり易い説明だった。
クロージャー・・・あまり使う機会がないなぁと思いつつ、以下で使われてるよー。と書いてあり、このあたりも意識していきたい。

this

これは色々なところで記事が書かれていて、よく目にします。
が・・・正直、本書が一番わかり易い。

プロトタイプオブジェクトとプロトタイプチェーン

JavaScriptは、プロトタイプベースのオブジェクト指向言語であるわけですが、他にプロトタイプベースのオブジェクト指向言語ってあるんですかね?

  • プロトタイプオブジェクト ・・・ JavaScriptの関数オブジェクトのprototypeプロパティに自動的に作成される特殊なオブジェクト。
  • プロトタイプチェーン ・・・ オブジェクト自身からPrototype内部プロパティへと順番に探す仕組みのこと。

例外処理

「throw文はあらゆるオブジェクトを例外として投げられますが、基本的にErrorオブジェクトのインスタンスを投げることを推奨」という事が書いてあり、ちょっとドキッとしました。

Map / Set

Set便利ですよね。よく使います。
Weakシリーズ(WeakMap / WeakSet)の存在を初めてしりました。(= = ;;
普通のMap / Setとは挙動が異なる様なので、使いどころを選びそうです。