└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Object.freeze and Object.seal syntax 2 | 3 | ## Rationale 4 | 5 | Object.freeze and Object.seal are both useful functions, but they aren't particularly ergonomic to use, especially when dealing with deeply nested objects or trying to do complex things like create a superset of a frozen object. 6 | Also even frozen objects can be modified via their prototype chain: 7 | 8 | ```js 9 | > const x = Object.freeze({}); 10 | undefined 11 | > x.foo 12 | undefined 13 | > Object.prototype.foo = 3; 14 | 3 15 | > x.foo 16 | 3 17 | ``` 18 | 19 | To prevent this you can use `Object.freeze({ __proto__: null })` or 20 | `Object.freeze(Object.assign(Object.create(null), {}))` to ensure that frozen 21 | objects cannot be modified via their prototype chain. 22 | 23 | ```js 24 | > const x = Object.freeze({ __proto__: null }); 25 | undefined 26 | > x.foo 27 | undefined 28 | > Object.prototype.foo = 3; 29 | 3 30 | > x.foo 31 | undefined 32 | ``` 33 | 34 | In addition, it would be useful to have these syntaxes in other places. It'd be useful to seal a destructuring expression, or freeze function arguments to create immutable bindings. 35 | 36 | ## Sketches 37 | 38 | ### Basic freezing of an object 39 | 40 | ```js 41 | const foo = {# 42 | a: {# 43 | b: {# 44 | c: {# 45 | d: {# 46 | e: [# "some string!" #] 47 | #} 48 | #} 49 | #} 50 | #} 51 | #} 52 | ``` 53 | 54 |
Click here to see the desugared version 55 | 56 | ```js 57 | const foo = Object.freeze({ 58 | __proto__: null, 59 | a: Object.freeze({ 60 | __proto__: null, 61 | b: Object.freeze({ 62 | __proto__: null, 63 | c: Object.freeze({ 64 | __proto__: null, 65 | d: Object.freeze({ 66 | __proto__: null, 67 | e: Object.freeze([ "some string!" ]) 68 | }) 69 | }) 70 | }) 71 | }) 72 | }) 73 | ``` 74 | 75 |
76 | 77 | ### Basic sealing of an object 78 | 79 | ```js 80 | const foo = {| 81 | a: {| 82 | b: {| 83 | c: {| 84 | d: {| 85 | e: [| "some string!" |] 86 | |} 87 | |} 88 | |} 89 | |} 90 | |} 91 | ``` 92 | 93 |
Click here to see the desugared version 94 | 95 | ```js 96 | const foo = Object.seal({ 97 | __proto__: null, 98 | a: Object.seal({ 99 | __proto__: null, 100 | b: Object.seal({ 101 | __proto__: null, 102 | c: Object.seal({ 103 | __proto__: null, 104 | d: Object.seal({ 105 | __proto__: null, 106 | e: Object.seal(["some string!"]) 107 | }) 108 | }) 109 | }) 110 | }) 111 | }) 112 | ``` 113 | 114 |
115 | 116 | 117 | ### Sealing a functions destructured options bag 118 | 119 | ```js 120 | function ajax({| url, headers, onSuccess |}) { 121 | fetch(url, { headers }).then(onSuccess) 122 | } 123 | ajax({ url: 'http://example.com', onsuccess: console.log }) 124 | // throws TypeError('cannot define property `onsuccess`. Object is not extensible') 125 | ``` 126 | 127 |
Click here to see the desugared version 128 | 129 | ```js 130 | function ajax(_ref1) { 131 | const _ref2 = Object.seal({ url: undefined, headers: undefined, onSuccess: undefined }) 132 | Object.assign(_ref2, _ref1) 133 | let url = _ref2.url 134 | let headers = _ref2.headers 135 | let onSuccess = _ref2.onSuccess 136 | 137 | fetch(url, { headers }).then(onSuccess) 138 | } 139 | ajax({ url: 'http://example.com', onsuccess: console.log }) 140 | // throws TypeError('cannot define property `onsuccess`. Object is not extensible') 141 | ``` 142 | 143 |
144 | 145 | ### Freezing a functions destructured options bag 146 | 147 | ```js 148 | function ajax({# url, headers, onSuccess #}) { 149 | url = new URL(url) // throws TypeError('cannot assign to const `url`') 150 | fetch(url, { headers }).then(onSuccess) 151 | } 152 | ajax({ url: 'http://example.com', onSuccess: console.log }) 153 | ``` 154 | 155 |
Click here to see the desugared version 156 | 157 | ```js 158 | function ajax(_ref1) { 159 | const _ref2 = Object.seal({ url: undefined, headers: undefined, onSuccess: undefined }) // seal now, const later 160 | Object.assign(_ref2, _ref1) 161 | const url = _ref2.url 162 | const headers = _ref2.headers 163 | const onSuccess = _ref2.onSuccess 164 | 165 | url = new URL(url) // throws TypeError('cannot assign to const `url`') 166 | fetch(url, { headers }).then(onSuccess) 167 | } 168 | ajax({ url: 'http://example.com', onSuccess: console.log }) 169 | ``` 170 | 171 |
172 | 173 | 174 | ## Potential extensions - sketches 175 | 176 | ### Sealed function param bindings 177 | 178 | ```js 179 | function add(| a, b |) { 180 | return a + b 181 | } 182 | add(2, 2, 2) === 6 183 | // throws TypeError('invalid third parameter, expected 2`) 184 | ``` 185 | 186 |
Click here to see the desugared version 187 | 188 | ```js 189 | function add(_1, _2) { 190 | if (arguments.length > 2) { 191 | throws TypeError('invalid third parameter, expected 2') 192 | } 193 | let a = arguments[0] 194 | let b = arguments[1] 195 | 196 | return a + b 197 | } 198 | add(2, 2, 2) === 6 199 | // throws TypeError('invalid third parameter, expected 2`) 200 | ``` 201 | 202 |
203 | 204 | ### Frozen function param bindings 205 | 206 | ```js 207 | function add1(# a #) { 208 | a += 1 // throws TypeError `invalid assignment...` 209 | return a 210 | } 211 | add1(1) === 2 212 | ``` 213 | 214 |
Click here to see the desugared version 215 | 216 | ```js 217 | function add1(_1) { 218 | if (arguments.length > 1) { 219 | throws TypeError('invalid second parameter, expected 1') 220 | } 221 | const a = arguments[0] 222 | 223 | a += 1 // throws TypeError `invalid assignment...` 224 | return a 225 | } 226 | add1(1) === 2 227 | ``` 228 | 229 |
230 | 231 | ### Extending syntax to general destructing for better type safety 232 | 233 | ```js 234 | const foo = { a: 1, b: 2 } 235 | const {| a, b, c |} = foo 236 | // Throws TypeError 'invalid assignment to unknown property c' 237 | ``` 238 | 239 |
Click here to see the desugared version 240 | 241 | ```js 242 | const foo = { a: 1, b: 2 } 243 | if (!('a' in foo)) throw new TypeError('invalid assignment to unknown property a') 244 | const a = foo.a 245 | if (!('b' in foo)) throw new TypeError('invalid assignment to unknown property b') 246 | const b = foo.b 247 | if (!('c' in foo)) throw new TypeError('invalid assignment to unknown property c') 248 | const c = foo.c 249 | // Throws TypeError 'invalid assignment to unknown property c' 250 | ``` 251 | 252 |
253 | --------------------------------------------------------------------------------