もともとの発端はここから。
明示的にconst
かどうかを示すプロポーザルでまだチャンピオンは取得してない。
気付いたら
- Brian Terlson(現在のTC39のエディタ)
- Jordan Harband(AirbnbのTC39の会員)
- Mikeal Rogers(Node.js Foundationの設立者)
- Bradley Farias(TC39の会員でESMに一番くわしい人)
- Benedikt Meurer(V8チームのスゴイ人)
- Ingvar Stepanyan(ASTスゴイ詳しい人)
たちが議論していた。
今回の話でも上がってきたし、TC39のミーティングでのstageの変動があったObject.freeze/sealの話をしようと思う。
途中でnull prototypesの話になったのでそれはもし書きたくなったら書く。
プロポーザル
Brian TerlsonがFishrock123のプロポーザルとこれ似ているからって話があって思い出した。
今回のミーティングでstage-1となった。
JSの問題点
関数の引数に対して、代入はしても元はもちろん変わらないが、Objectの中身の変更はできる。
JSのconst
はあくまでも再代入をさせないだけであるので、中身の変更は可能である。
const a = { b: 1 }; function modify(a) { // a = {c: 1}; // 再代入はしても意味がない a.b += 1; a.c = 2; } modify(a); console.log(a); // { b: 2, c: 2 }
Objectの制御について
ES5から入った、preventExtensions
、 seal
、freeze
というメソッドがあります。
以下が対応表です。
method | プロパティの追加 | プロパティの変更 | プロパティの削除 | プロパティの属性の変更 | 確認 |
---|---|---|---|---|---|
preventExtensions | N | Y | Y | Y | Object.isExtensible |
seal | N | Y | N | N | Object.isSealed |
freeze | N | N | N | N | Object.isFrozen |
このような属性へ変更されます。
{ const a = { b: 1 }; console.log(Object.getOwnPropertyDescriptors(a)); // { b: { value: 1, writable: true, enumerable: true, configurable: true } } Object.preventExtensions(a); console.log(Object.getOwnPropertyDescriptors(a)); // { b: { value: 1, writable: true, enumerable: true, configurable: true } } Object.seal(a); console.log(Object.getOwnPropertyDescriptors(a)); /* { b: { value: 1, writable: true, enumerable: true, configurable: false } } */ Object.freeze(a); console.log(Object.getOwnPropertyDescriptors(a)); /* { b: { value: 1, writable: false, enumerable: true, configurable: false } } */ }
つまり、seal
ではconfigurable
がfalse
へ、freeze
ではwritable
とconfigurable
がfalse
となる。
また、強い方へしか変更ができなくなります。
const a = { b: 1 }; Object.freeze(a); Object.preventExtensions(a); Reflect.deleteProperty(a, 'b'); console.log(a); // { b: 1 }
他の言語もおそらく同じですが、これらはそのObject以下のすべてを適応するわけではなく、直下しか適用しません。
{ const a = { b: { c: [ 'test' ] } }; console.log(Object.getOwnPropertyDescriptors(a)); /* { b: { value: { c: [Array] }, writable: true, enumerable: true, configurable: true } } */ const b = Object.freeze(a); console.log(Object.getOwnPropertyDescriptors(b)); // 適応 /* { b: { value: { c: [Array] }, writable: false, enumerable: true, configurable: false } } */ console.log(Object.getOwnPropertyDescriptors(b.b)); // 適応されてない /* { c: { value: [ 'test' ], writable: true, enumerable: true, configurable: true } } */ }
つまり、複数の階層構造になっているObjectを制御したければ各行で明記しなければなりません。
糖衣構文
さすがに、毎回Object.~~
って書くのはめんどいのでそのために提案された。
freeze
const foo = {# a: {# b: {# c: {# d: {# e: [# "some string!" #] #} #} #} #} #} // 展開 const foo = Object.freeze({ a: Object.freeze({ b: Object.freeze({ c: Object.freeze({ d: Object.freeze({ e: Object.freeze([ "some string!" ]) }) }) }) }) })
seal
const foo = {| a: {| b: {| c: {| d: {| e: [| "some string!" |] |} |} |} |} |} // 展開 const foo = Object.seal({ a: Object.seal({ b: Object.seal({ c: Object.seal({ d: Object.seal({ e: Object.seal(["some string!"]) }) }) }) }) })
また、以下のような使い方もできる。
function ajax({| url, headers, onSuccess |}) { fetch(url, { headers }).then(onSuccess) } ajax({ url: 'http://example.com', onsuccess: console.log }) // throws TypeError('cannot define property `onsuccess`. Object is not extensible') // -------------------------------------- function add(| a, b |) { return a + b } add(2, 2, 2) === 6 // throws TypeError('invalid third parameter, expected 2`) // -------------------------------------- function add1(# a #) { a += 1 // throws TypeError `invalid assignment...` return a } add1(1) === 2 // -------------------------------------- const foo = { a: 1, b: 2 } const {| a, b, c |} = foo // Throws TypeError 'invalid assignment to unknown property c'
このように関数のパラメータの束縛したりすることによりバリデートすることが可能でデストラクチャリングに対してバリデートしやすくなる。
さいごに
ライブラリのコードではよく使われるが、業務で使われていたのを見たことがない気がする。