Skip to content

Instantly share code, notes, and snippets.

@teppeis
Last active April 29, 2023 15:04
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teppeis/c50743a60832560aa1df to your computer and use it in GitHub Desktop.
Save teppeis/c50743a60832560aa1df to your computer and use it in GitHub Desktop.
Break the Web: Object staticメソッドがES6で仕様変更になった件について

Break the Web: Object static methods no longer throw errors for primitives!

Object.keys等のstaticメソッドがES6で挙動が変わったことで面倒なことになってる話

何が変わったか

ES5でプリミティブを受け取ると例外を投げていたObject.keys等のメソッドが、ES6では例外を吐かなくなった。

  • Object.getPrototypeOf ( O )
  • Object.getOwnPropertyDescriptor ( O, P )
  • Object.getOwnPropertyNames ( O )
  • Object.seal ( O )
  • Object.freeze ( O )
  • Object.preventExtensions ( O )
  • Object.isSealed ( O )
  • Object.isFrozen ( O )
  • Object.isExtensible ( O )
  • Object.keys ( O )

各ES5/6仕様書へのリンク: https://gist.github.com/teppeis/5284739aa1c6823b2ea9

各環境の実装情況

Firefox, Chromeは今週ぐらいにリリースされるバージョンから反映される。

  • SpiderMonkey
    • Firefox 33: Object.getOwnPropertyNamesのみ対応
    • Firefox 35: 全メソッド対応 (Bug 1038545)
  • V8: Object.getOwnPropertyNamesObject.keysのみ対応 (issue3443)
    • Chrome 40
    • io.js

(注: Node.js自体はv8のバージョンが古いためv0.11/v0.12では反映されない。io.jsはv8をアップデートしてるので反映されてる。)

何が壊れるか

これらメソッドの投げる例外に依存してプリミティブとオブジェクトを判別しているロジックは壊れる。

影響範囲の大きいところとしては、Node.jsのassert.deepEqual

// https://github.com/joyent/node/blob/v0.11.14/lib/assert.js#L213
try {
  var ka = Object.keys(a),
      kb = Object.keys(b),
      key, i;
} catch (e) {//happens when one is a string literal and the other isn't
  return false;
}

Node.js自体はv0.11/0.12が採用するv8のバージョンが古いためにassertが壊れることはないが、これをフォークしたio.jsはv8を上げてしまったので壊れた。 また、Node.jsのassertから派生した各種ブラウザ用アサーションライブラリも壊れた。

  • io.js
  • commonjs-assert (browserifyが置換するassert互換パッケージ)
    • power-assert (browserify経由のcommonjs-assert依存)
  • Jxck/assert
  • expect.js

これによって、プリミティブとオブジェクトの比較、具体的には以下のようなassertが通ってしまうので致命的。

assert.deepEqual(1, {}); // should fail, but actually pass...

独自ロジックでdeepEqual相当の処理をしてるChaiやJasmineは大丈夫だった(逆に言うと、これらはCommonJS AssertのdeepEqualの仕様とは異なる)。

直したもの

以下はプルリクを送って直した。

expect.jsも直さないと。。

副産物: commonjs-assertのIE8対応が進んだ

これまでcommonjs-assertは、プリミティブに対して例外を吐かないやる気のないObject.keysのshim実装を使っていたため、このshimが使われるIE8では上記の例外に依存するロジックが正しく動作せず、assert.deepEqualでプリミティブとオブジェクトを正しく比較できないという致命的な状態だった。

今回の対応で例外に依存しないロジックになったため、Object.keysのshimは相変わらずやる気が無いものの、致命的な欠陥は無くなった。

現存するcommonjs-assertのIE8でのバグはおそらく以下。

  • assert.deepEqualargumentsを渡す比較が正しくない
  • assert.deepEqualtoStringなどのnot-enumarableなプロパティを持つオブジェクトの比較が正しくない

どちらもコーナーケースなので、IE8でもassert/power-assertは十分使えるようになったと言える。

polyfillは?

  • es6-shim: Object.keysのみ対応 (#220)

他のcore.js(6to5)等の大多数のpolyfillは ES5版Objectメソッドの挙動でpolyfillしている 情況。 つまり、polyfillしてるつもりでも実は環境毎に挙動が違うという情況が発生している。

今後はES5版とES6版でpolyfillを区別する必要が出てくる。 ES6版polyfillは、その環境でObject.keysが存在しても、ES5版の挙動で実装している環境(IE11以下等)に対してはpolyfill実装を適用しなければならない。 例えば以下のようにpolyfillする必要があるはず。

// ES6 polyfill
if (!hasES6ObjectKeys()) {
  Object.keys = es6ObjectKeysShim;
}

function hasES6ObjectKeys() {
  if (typeof Object.keys !== 'function') {
    return false;
  }

  try {
    return isEmptyArray(Object.keys(1));
  } catch (ignore) {
    return false;
  }
}

一方、ES5 polyfillは、ES6版Object.keysが実装されている環境で読まれた場合、ES5版Object.keysで上書きしてダウングレードすべきなのだろうか? ES5を想定したライブラリを動かしたいときはそうしてほしい気がするけど、そんなケースねーよって言われると無いような気もする。

polyfill業界、どうするんだろ。

教訓

ちゃんと書いていてもWebは壊れる。

参考

@teppeis
Copy link
Author

teppeis commented Aug 29, 2016

expect.jsがまだ直ってなかったのでとりあえずissue登録した。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment