※スマホ対応はしてません。

カテゴリー: JavaScript

Vue.jsで文字列が空のとき “Mismatching childNodes vs. VNodes” になった。

カテゴリー: JavaScript

Vue.jsで、こんなエラー(というか警告)が出た。

console.error()で &quot:[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content."

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.

再現コード

はこんな感じ。

<template>
  <section>
    <p><img />{{text}}</p>
  </section>
</template>

<script>
export default {
  data () {
    return {
      text: '',
    }
  },
}
</script>

たぶん、 <p> の直下には「 <img> 」と「 {{text}} から成るテキストノード」の二つがあるはずなんだけど、文字列が空だとそこにテキストノードが用意されないので、なんか予定と違うぞーと怒ってる感じだろうか。

解決策

とにかくテキストノードが存在していれば良いので、空白文字とか置いておこう。

text: '' を text: ' ' にするとか、同様に <template> の方にスペース追加するとか。

余白が嫌なら幅なしのやつ &zwnj; にしよう。

<template>
  <section>
    <p><img />&zwnj;{{text}}</p>
  </section>
</template>

とっぴんぱらりのぷう。

ES6とES2015に違いはないです。(現代的JavaScriptおれおれアドベントカレンダー2017 – 24日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 24日目

昔はES5とかES6とか言ってたはずなのに気付いたらES2015とか呼ばれるようになってて、なんかマリオが64になって急に数字が増えたなあみたいなのに近い感覚だったんですが、そんなことありませんでしたかそうですか。

概要

  • ES6とES2015は同じもの
  • わかりやすいからES2015と呼ぼうぜ
  • ES2015以降は毎年更新
  • 正式名称は “ECMA-262 edition 6” とかそういうの

ES vs JS

JavaScript (JS) はプログラミング言語ですが、その大本の仕様となるのがECMAScript (ES) です。JSはESの一種であり、ESの仕様で定められたものはJSで動きます。(あるいは、動くことが期待される。)

JSは各ブラウザが環境を用意していますが、ESの方はEcma Internationalという団体の中のTC39という委員会が仕様策定を行っています。

ベンダーはESの仕様通りにJSの動作を実装することになります。ES側は、逆にベンダーの提案と先行実装から仕様をまとめてる感じです。(たぶん。ここらへん怪しい。) 二つ以上の実装が存在するまで正式な仕様にはなりません。

ECMAScriptの仕様

ESも、具体的には “ECMA-262” という名前の仕様です。

Ecma International

色々と仕様策定を行ってる団体みたいです。ES以外にもC#とか。

E-C-M-A

前はECMA = European Computer Manufacturers Association(欧州電子計算機工業会)という団体でしたが、なんか国際化した現状に合わせて1994年に名前を変えたそうです。Windows 95より前だ。

なので “ecma” という英単語が存在するわけではないですが、かといって現在は略語というわけでもありません。

ES6 vs ES2015

この二者は同じものを指します。一般には後者の呼び方が推奨されます。

ES6

本来はESの第6版のことです。仕様の正式名称としては “ECMA-262” で、その “Edition 6” と。 class やアロー関数 => 等、「いまどき」の機能が多数追加されました。

前身のES5が2009年の発表で、2011年のES5.1を挟んで久しぶりに、かつ大きく更新されました。その発表年が、2015年です。

ES2015

というわけで、ES6こと “ECMA-262 Edition 6” のことです。

ウェブ界隈の進みが早いのでもっと細かく、毎年仕様を策定していこうという話になりました。それなら連番はわかりづらいよね、と。(ES7, ES8, ES9, ES10, …) そこで連番よりも発行年で、ES6ならES2015と呼ぶことが推奨されるようになりました。

ES2016, ES2017

それぞれ公開されています。

ES2015に比べて影が薄いけど、都度新機能なりが追加されます。

年で呼ぶことを推奨

ESの5、5.1、6版の仕様策定を引っ張ったAllen Wirfs-Brock氏のブログから。

So, why the year-based designation? The 6th edition of ECMA-262 took a long time to develop, arguably 15 years. As ES6 was approaching publication, TC39 (the Technical Committee within Ecma International that develops the ECMAScript specifications) already knew that it wanted to change its process in a way that enabled yearly maintenance updates. That meant a new edition of ECMA-262 every year with a new edition number. After a few years we would be talking about ES6, ES7, ES8, ES9, ES10, ES11, etc. Those numbers quickly loose any context for people who aren’t deeply involved in the standards development process. Who would know if the current standard ES7, or ES8, or ES9? Was some feature introduced in ES6 or ES7? TC39 couldn’t eliminate the actual edition numbers (standards organizations love their document numbers) but it could change the document title. We decide that TC39 would incorporate the year of release into the documents title and to encourage people to use the year when referring to a specific edition. So, the “newest version of JavaScript” is ECMA-262, Edition 8 and its title is ECMAScript 2017 Language Specification. Some people still refer to it as ES8, but the preferred shorthand name is ECMAScript 2017 or just ES2017.

では、何故年ごとの呼称になるのでしょうか。 ECMA-262の第6版は長い時間がかかりました。 15年ですよ。 ES6の公開が近づいていますが、その作業工程を変更し毎年更新することが望まれていると、TC39 (Ecma International内のECMAScript仕様策定の専門委員会)はわかっていました。 そう、毎年新しいECMA-262、そして新しい版番号です。 数年後、私たちは ES6, ES7, ES8, ES9, ES10, ES11, etc. について会話することになります。 標準化作業に明るくない方々にとって、これらの番号はすぐわけがわからないものになってしまうでしょう。 現在の標準がES7なのかES8なのか、それともES9なのか、誰が知っているというのでしょうか。 ある機能が追加されたのはES6? それともES7?  TC39は実際の版番号を消し去ることはできませんでしたが(標準化組織は文書番号が好きなのです)、文書のタイトルを変えることはできました。 TC39は発行年を文書タイトルへ組み込み、また特定の版へ言及する場合はこの年を使うことを推奨することにしました。 ですので、「JavaScript最新版」はECMA-262の第8版であり、そのタイトルはECMAScript 2017 Language Specificationとなります。 (訳注: 2017/08/31当時)  これをES8と呼ぶ人もいますが、簡略化する場合はECMAScript 2017、あるいはただES2017とするのが良いでしょう。

(訳注: “edition number” を日本語で「版次」というらしいんだけど、あんまり一般的じゃなさそうなので「版番号」としました。いやまあおれが知らないだけかもしらんけど。)

そもそもESではなくJSと呼んだ方が

JSはESではありますが、本当にES自体についての文脈でなければJSの名前で呼んだ方が良いだろう、との提言もしておいでです。(前項引用箇所の次の段落。) 同感です。

「現代的なJavaScript」という呼び方

版番号が関係する場合でも、単純に新旧で分けるなら、ES2015以前を「古いJS (legacy JavaScript) 」、以後を「現代的JS (modern JavaScript) 」と呼びましょう、と。

ES2015は大きな変更でしたから、そこで分けるのは妥当だと思います。

まだIE 11(2013年リリース)とか対応しなきゃとかってのはあると思うんだけど、そこはBabelを使う等して、できるだけ現代的な書き方でやっていきたいっすねー。便利だもの。

その他

他のもさー

ついでにIE 9とかIE 11とかじゃなくて、IE2009とかIE2013とか呼びたくない?

あとAndroid 4.4じゃなくてAndroid 2013とか。iOSは……まあいいか。いいか?

おしまい

というわけで「現代的JavaScriptおれおれAdvent Calendar」全24回でした。ここまでお付き合い頂きありがとうございました。

良いお年を!

(でもいくつか触れ損ねた話題もあるので、もうちょっとだけ続くんじゃ。既存記事も少し書き足したりします。)

参考

ジェネレータと自作イテレータで各種オブジェクトもぐーるぐる。(現代的JavaScriptおれおれアドベントカレンダー2017 – 23日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 23日目

概要

function* で、イテレータを返すジェネレータ関数を作成できます。中で yield が使えます。

function* createIdGenerator() {
  let currentId = 100;
  while (true) {
    yield currentId++;
  }
}

const iterator = createIdGenerator();
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);

これはオブジェクトを反復可能(イテラブル)にするのに便利です。

const obj = {
  *[Symbol.iterator]() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj) {
  console.log(item);
}

イテレータ

著名なデザインパターンのひとつです。配列でないオブジェクトでも、共通のインターフェイスで反復処理を行えるようにします。

JavaScript (ES2015+) では(たぶん一般的な定義とは異なり)イテレータは next() メソッドを持つオブジェクトです。具体的なインターフェイスは後述します。

JavaScriptでのイテレータ

イテラブルなオブジェクトで [Symbol.iterator]() というすごい名前のメソッドを実行すると、イテレータを得られます。

const iterable = new Set([100, 200, 300]);

// イテレータ生成
const it = iterable[Symbol.iterator]();

while (true) {
  // 反復処理を進め、現段階の状態を得る
  const result = it.next();

  // 繰り返し条件を確認
  if (result.done) {
    break;
  }
  
  // 項目取得
  const item = result.value;

  console.log(item);
}

Set オブジェクトはイテラブルなオブジェクトなので、 [Symbol.iterator]() を実行するとイテレータ it を生成して返してくれます。

そのイテレータ it は next() メソッドを持ちます。このメソッド呼び出しを繰り返して反復 (iterate) していきます。

next() メソッドは実行するたびに、なんだろ、結果オブジェクト? result を生成して返します。結果オブジェクトって呼び方で良いかな。 IteratorResult という名前のインターフェイス(後述)を満たすオブジェクトです。

この結果オブジェクトはまた、二つのプロパティを持ちます。 value が反復途中の現段階の値で、 done がもう反復し終えたかが格納される真偽値です。

これらを使って配列でも何でもぐるぐるできます。

for-of で使う

オブジェクトがイテラブルである、つまり前述の各要素を満たす場合、 for-of を使って簡単に反復処理を実現できます。

const iterable = new Set([100, 200, 300]);

for (let item of iterable) {
  console.log(item);
}

わあ短い。

ジェネレータで生成する

[Symbol.iterator]() をちゃんと自作すればどんなオブジェクトでもイテラブルにできます。

next() メソッドを自前で実装しても良いんだけど、面倒なので、ジェネレータ関数というのを使って作ると簡単です。

ジェネレータ

function* を使ってジェネレータ関数を宣言できます。ジェネレータ関数を実行するとジェネレータオブジェクトを得られます。

function* createGenerator() {
  const values = [100, 200, 300];

  for (let i = 0; i < values.length; i++) {
    const item = values[i];
    yield item;
  }
}

const generator = createGenerator();

ジェネレータオブジェクトは加えてイテラブルでもあるで for-of で使えます。わーい。

for (let item of generator) {
  console.log(item);
}

generator[Symbol.iterator]() の結果は元のオブジェクト generator になります。

また同時にイテレータでもあるので next() を持ちます。コードは書かなくても良いかね。

yield

さらっと使ったけど、ジェネレータ関数の中では yield という式を使えます。この式は右辺を value 、また done: false という設定で、 next() で返す IteratorResult オブジェクトを生成します。

必ずしも for 文で使う必要はないです。

function* oneTwoThree() {
  yield 1;
  yield 2;
  yield 3;
}

const it = oneTwoThree();

let result;
result = it.next();
console.log(result.value);  // => 1
result = it.next();
console.log(result.value);  // => 2
result = it.next();
console.log(result.value);  // => 3

[Symbol.iterator]() を実装する

いよいよ [Symbol.iterator]() です。 * を付けて、ジェネレータ関数というかジェネレータメソッドになります。

const obj = {
  *[Symbol.iterator]() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj) {
  console.log(item);
}

他のイテレータ生成メソッドを作る

obj じゃなくて obj.iterator() のようにメソッドを呼んでやる必要があるけれど、他の名前でも大丈夫。

目的別に何通りか用意しても良いかもね。

const obj = {
  *iterator() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj.iterator()) {
  console.log(item);
}

イテレータのインターフェイス

いっぱい出てきたのでまとめ。

  • 反復可能(イテラブル)なオブジェクト … [Symbol.iterator]() をもつオブジェクト( Iterable )
  • [Symbol.iterator]() … イテレータを返すメソッド
  • イテレータ … next() をもつオブジェクト( Iterator )
  • next()value 、 done を持つオブジェクト( IteratorResult )を返し、内部状態を進めるメソッド
  • value … 反復処理各段階における値
  • done … 反復処理が終了しているかどうか

[Symbol.iterator]()

Symbol.iterator というシンボルが、名前というか何だろ、識別子?になってるメソッドです。 [] は動的にプロパティ名を決めるやつです。別稿参照。あとシンボル Symbol についても。

なんならどこかで Symbol.iterator = 'iterate' とか定義されてると考えてください。

Iterable インターフェイス

オブジェクトがこのインターフェイスを満たしていると、 for-of とか ... とかが使えます。

  • [Symbol.iterator]() … イテレータオブジェクトを返す

Iterator インターフェイス

このインターフェイスを満たすオブジェクトをイテレータオブジェクトと呼びます。

  • next() … イテレータオブジェクトを返す
  • return() (optional) … イテレータオブジェクトを返す
  • throw() (optional) … イテレータオブジェクトを返す

return() を呼び出すと「もう終了するぞ」と、 throw() を呼び出すと「なんかおかしいぞ」を、呼び出し先となるオブジェクトに伝えることが、できる、そう、です。単純に配列みたいな情報を扱う上ではいらなさそうだけど、何か外部から情報を引っ張ってくるようなやつで使うときに便利なんだろか。わかんない。

IteratorResult インターフェイス

next() 他のメソッドはこのインターフェイスを満たすオブジェクトを返す必要があります。

  • done
  • value

分割代入やスプレッド演算子 ...

これら実はイテラブルオブジェクトが対象です。配列じゃなくても自作のオブジェクトでも、 [Symbol.iterator]() を備えていれば使えます。

function* oneTwoThree() {
  yield 1;
  yield 2;
  yield 3;
}

// 分割代入
const [one, two, three] = oneTwoThree();
console.log(one);
console.log(two);
console.log(three);

// スプレッド演算子
function say(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}
say(...oneTwoThree());

ES2018では普通のオブジェクトも分割代入したりスプレッド演算子で展開したりできるようになるっぽいんだけど、じゃあ普通のオブジェクトも全部イテラブルになるのかな? 仕様策定の様子は追ってないので思っただけだけど。

分割代入と ... の使い方は別稿参照。

その他

[Symbol.iterator]() て何だよ

なんで普通の名前じゃないんだ Symbol なんだ、 toString() みたいにしなかったんだ。

だいたいイテレータはイテラブル

「イテレータ」と「イテラブル」は別インターフェイスなので同時に満たす必要はないんだけど、内部で %IteratorPrototype% というイテレータ共通のプロトタイプがありまして、こいつがイテラブルだったりします。

配列とかのイテレータはこのプロトタイプを継承しているので、JavaScriptネイティブなイテレータはだいたいイテラブルになるっぽい。 TypedArray は専用のものを持たないが、普通の配列 Array の処理を利用している。

ちなみに %IteratorPrototype% の [Symbol.iterator]() は this を返します。

イテラブルではないイテレータ

の例。

class MyIterator {
  constructor(values) {
    this.values = values;
    this.index = 0;
  }

  // 自前で実装するぞ
  next() {
    const value = this.values[this.index];
    this.index += 1;
    return {
      value: value,
      done: this.index >= this.values.length,
    };
  }
};

const values = [100, 200, 300];

// イテレータとして利用
const it = new MyIterator(values);
let result;
result = it.next();
console.log(result.value);

// イテラブルとして利用(できない)
// Exception: TypeError: (new MyIterator(...)) is not iterable
for (let item of new MyIterator(values)) {
  console.log(item);
}

普通のイテレータ

Java方面の話で聞いたときは next() で次に移動しつつ値を戻り値でそのまま得て、続きがあるかどうかは hasNext() という別のメソッドを使うみたいな流れだったと思うんだけど、なんで違うのかしらん。

時代が変わってイテレータパターン自体が変わった?

インターフェイス vs プロトコル

ちなみにMDNだとイテレータとかで「プロトコル (protocol)」という表現が用いられているけれど、仕様書だと「インターフェイス (interface)」です。まあ同じものでしょ。

たしかSwiftはprotocolという表現してたよね。

function と * の間で改行できる

適当に空白文字を置ける様子。

function
* goo() {
}

async はだめだったのに~。

参考

更新履歴

  • 2017/12/23 TypedArray がイテラブルじゃないみたいな勘違いしてたのを修正

イテレータとfor-of文で配列以外もぐーるぐる。(現代的JavaScriptおれおれアドベントカレンダー2017 – 22日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 22日目

概要

配列とか Map とかは for-of 文を使って、添え字アクセス? [] を使わずにぐるぐるできます。

const arr = ['Hello', 'World', '!'];

for (let item of arr) {
  console.log(item);
}

普通のオブジェクトでは使えないけど、インターフェイスを追加すれば使えるようにもなります。

使い方

普通の for 文を使って配列で arr[i] する代わりに for-of でいきなり値を取ってこれます。

const arr = ['Hello', 'World', '!'];

for (let item of arr) {
  console.log(item);
}

配列以外でも反復可能なオブジェクト(後述)で使えます。

const map = new Map([[1, 11], [2, 22], [3, 33]]);

for (let [key, value] of map) {
  console.log(`${key}: ${value}`);
}

文字列と絵文字

文字列でも使えます。絵文字とかのサロゲートペアを上手に扱ってくれます。やったね。

const str = '#sushi🍣';

console.log('--- for ---');
for (let i = 0; i < str.length; i++) {
  console.log(str[i]);
}

console.log('--- for-of ---');
for (let item of str) {
  console.log(item);
}

--- for ---
#
s
u
s
h
i
�
�
--- for-of ---
#
s
u
s
h
i
🍣

反復可能なオブジェクト

for-of 文は反復可能なオブジェクトに対して利用することができます。

対応してないオブジェクトだとエラーに。

const obj = {};

for (let item of obj) {  // TypeError: obj[Symbol.iterator] is not a function
    console.log(item);
}

反復可能なオブジェクト

この「反復可能(イテラブル)なオブジェクト (iterable object)」とは、「イテレータ」を作成するインターフェイスを整えたオブジェクトのことです。配列 Array や Map 等が該当します。

インターフェイスが整っていれば何でも良いので、自作することもできます。

// 反復可能なオブジェクト
const iterable = {
  // for-ofに必要なインターフェイス
  [Symbol.iterator]() {
    const max = 10;
    let n = 0;

    const iterator = {
      next() {
        return { value: n++, done: n > max };
      },
    };

    return iterator;
  },
};

// よっしゃー使うぞー
for (let item of iterable) {
  console.log(item);
}

詳細は別稿参照。

その他

for-in と for-of

もちろん別物なんだけど、同じ項 (13.7.5) でまとめて定義されてるので、兄弟みたいなものらしい。

for-in はカンマ , 区切りの値を置ける

なんでOKなんだろ。

const arr1 = [100];
const arr2 = [200];

arr1.foo = 123;
arr2.bar = 123;
for (let index in arr1, arr2) {
  console.log(index);
}

もちろん末尾のもの arr2 が有効になります。

0
bar

for-of でやると構文エラーです。

const arr1 = [100];
const arr2 = [200];

for (let item of arr1, arr2) {  // SyntaxError: Unexpected token ,
  console.log(item);
}

for-in で初期値を書ける場面が

"use strict" の厳格モードでない場面で、 var をここで使用する場合のみ、初期値を置ける。 let を使ったり、別の場所で var 宣言してからの代入だとだめ。

const arr = [1, 2, 3];

for (var index = 'wow' in arr) {
  console.log(index);
}

といってもその初期値が適用される場面てあるんだろか。

謎仕様だなー。後方互換のためなのかな。

参考

  • ECMAScript® 2017 Language Specification
    • 13.7.5 The for-in and for-of Statements
    • 13.7.5.12 Runtime Semantics: ForIn/OfHeadEvaluation ( TDZnames, expr, iterationKind ) … イテレータの作成。初期処理的な
    • 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation ( lhs, stmt, iterator, iterationKind, lhsKind, labelSet ) … イテレータを使って反復処理する
    • 13.7.5.15 EnumerateObjectProperties ( O ) … for-in 用のイテレータ
    • 6.1.5.1 Well-Known Symbols … Symbole.iterator
    • 7.4.1 GetIterator ( obj [ , method ] )
    • 7.4.5 IteratorStep ( iterator ) … イテレータで反復
    • 25.1 Iteration
    • B.3.6 Initializers in ForIn Statement Heads … for-in で初期値使える
  • for…of – JavaScript | MDN
  • for…in – JavaScript | MDN

WeakMap、WeakSetで「弱い参照」を使えるようになったぞ。(現代的JavaScriptおれおれアドベントカレンダー2017 – 21日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 21日目

概要

基本的な使い方は Map や Set と一緒です。キーに使われるオブジェクトの参照カウントを増やさないとかそういうアレで、「弱い参照」で保持します。メモリリーク対策とかに。

Map 、 Set との違い

基本は Map 、 Set と同様なんだけど、キーが弱い参照(後述)になります。

できること:

  • get() (WeakMap)
  • set() (WeakMap)
  • add() (WeakSet)
  • has()
  • delete()

できないこと:

  • size
  • forEach()
  • clear()
  • keys()
  • 他

キーはオブジェクトのみ

Map は何でもキーに使えたんだけど、 WeakMap はオブジェクトだけです。

まあ意味ないしね。

弱い参照?

JavaScript界隈では新しい概念のはず。

プログラム内で使ってる情報ってメモリに置かれるじゃないですか。あれって使われてる間は保持されてて、使われなくなったら消してくれるんですよ、誰かが。上位のプログラムが。

で、問題はどうやって「使われているか」を判断するかなんだけど、変数から参照されてる数を覚えておく、というのがよくある基本的な仕組みです。そんでその数を「参照カウンタ」とか呼んだりします。参照カウンタが0になったらもう誰も見てないから消しちゃおう、ていうね。

参照を数える様子。

変数はスコープが終わると消えて、参照カウンタが減って、他から参照されてなければ参照先の情報も消える、と。

弱い参照だと、その参照カウンタを増やさずにおくことができます。なので、弱い参照が残っていてもメモリ上の情報は消えてしまうこともあります。

WeakMapのキーは弱い参照

WeakMapのキーとしてのみ使われている場合、そのリソースは削除されます。

WeakMap objects are collections of key/value pairs where the keys are objects and values may be arbitrary ECMAScript language values. A WeakMap may be queried to see if it contains a key/value pair with a specific key, but no mechanism is provided for enumerating the objects it holds as keys. If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap. WeakMap implementations must detect and remove such key/value pairs and any associated resources.

WeakMapオブジェクトはkey/valueペアのコレクションです。keyはオブジェクト、valueはECMAScript言語の任意の値を取ります。WeakMapはあるkey/valueペアを格納しているかを、特定のkeyを用いて確認できるよう要求されるかもしれませんが、キーとして保持しているオブジェクトの列挙についての手順は提供されていません。もしWeakMapのkey/valueペアのキーとして使われているオブジェクトが、そのWeakMap内から開始する参照チェインを辿ってのみ到達できる場合、そのkey/valueペアはアクセスできず、また自動的にそのWeakMapから削除されます。WeakMapの実装は、このようなkey/valueペア及び関連リソースを検出し、削除する必要があります。

(強調は引用者。)

弱参照とか言ったけど、仕様書ではそういう単語は使ってないし、実装方法が任意なので参照カウンタが用いられているとも限らないです。たぶん使ってると思うんだけど。

値の方は普通の参照です。

継承して

仕様書によると「サブクラスとして使えるように設計されている」とのことです。

内部の初期処理が必要なので、コンストラクタで super 呼び出ししないと怒られます。

class ElementData extends WeakMap {
  constructor() {
    // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from deriv
  }
}

HTML要素をキーに

DOMノードだったら同じ内容で別インスタンスみたいなものってあんまり作られない感じがするので、キーにするのに良さそう。jQueryの data() みたいなやつ。

const elementData = new WeakMap();

const el = document.querySelector('#foo');
elementData(el, { text: '追加情報 ' });

console.log(elementData.get(el));

Reactとかでがんがん要素再生成しますーみたいな設計の場合は、たぶん識別子とか持ってるんじゃないかな。そんなら普通の Map で十分そう。いや自動削除ないか。

実際の利用ケース

Stack Overflowの解答から。

Some use cases that would otherwise cause a memory leak and are enabled by WeakMaps include:

  • Keeping private data about a specific object and only giving access to it to people with a reference to the Map. A more ad-hoc approach is coming with the private-symbols proposal but that’s a long time from now.
  • Keeping data about library objects without changing them or incurring overhead.
  • Keeping data about a small set of objects where many objects of the type exists to not incur problems with hidden classes JS engines use for objects of the same type.
  • Keeping data about host objects like DOM nodes in the browser.
  • Adding a capability to an object from the outside (like the event emitter example in the other answer).

WeakMapを使わないとメモリリークを引き起こす利用ケース:

  • あるオブジェクトに関するプライベートな情報を保持し、Mapへの参照を経由してのアクセスのみを公開する。より直接的な手段としてプライベートシンボルが提案されているが、まだ時間かかりそう
  • ライブラリオブジェクトに関する情報を、ライブラリを変更したりオーバーヘッドを招かずに保持する
  • 小さなオブジェクトの組み合わせに関する情報を保持する。この種の多数のオブジェクトは、JSエンジンが同種のオブジェクトのために使う隠しクラスに関する問題を引き起こさないために存在する(訳注: ごめんイミフ)
  • ブラウザのDOMノードのような、ホストオブジェクトに関する情報を保持する
  • あるオブジェクトの外側から能力 (capability) を追加する(他の解答にあるイベントエミッタの例みたいに)

載せといてなんだけど正直何言ってんのかよくわかんない……。

その他

キー一覧機能の提供は禁止

速度問題に発展しがちなので、そういうのは実装しちゃだめ (must not) と仕様書に書かれてます。

(あれ、そういう理解で合ってるよね?)

参考