Section 26: Meta-Programming - Symbols, Iterators & Generators, Reflect API, Proxy API
The goals
๐ช๐ปWhat & How?
What?
Symbols | Iterators & Generators | Reflect API | Proxy API |
Primitive values | Create your own "loopable" values | API to control objects | Create "traps" for object operations |
Used as object property identifiers | What arrays, string etc. use internally | Standardiezed & grouped methods | Step in and execute code |
Built-in & creatable by developers | Control code usage / impact | ||
Uniqueness is guaranteed |
Iterators and generators
Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops.
Iterators
In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination.
Specifically, an iterator is any object which implements the Iterator protocol by having a next() method that returns an object with two properties:
valueThe next value in the iteration sequence.
doneThis is true if the last value in the sequence has already been consumed. If value is present alongside done, it is the iterator's return value.
Once created, an iterator object can be iterated explicitly by repeatedly calling next(). Iterating over an iterator is said to consume the iterator, because it is generally only possible to do once. After a terminating value has been yielded additional calls to next() should continue to return {done: true}.
The most common iterator in JavaScript is the Array iterator, which returns each value in the associated array in sequence.
While it is easy to imagine that all iterators could be expressed as arrays, this is not true. Arrays must be allocated in their entirety, but iterators are consumed only as necessary. Because of this, iterators can express sequences of unlimited size, such as the range of integers between 0 and Infinity.
Here is an example which can do just that. It allows creation of a simple range iterator which defines a sequence of integers from start (inclusive) to end (exclusive) spaced step apart. Its final return value is the size of the sequence it created, tracked by the variable iterationCount.
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false };
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true };
}
};
return rangeIterator;
}
Using the iterator then looks like this:
const it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log("Iterated over sequence of size: ", result.value); // [5 numbers returned, that took interval in between: 0 to 10]
Generator functions
While custom iterators are a useful tool, their creation requires careful programming due to the need to explicitly maintain their internal state. Generator functions provide a powerful alternative: they allow you to define an iterative algorithm by writing a single function whose execution is not continuous. Generator functions are written using the function* syntax.
When called, generator functions do not initially execute their code. Instead, they return a special type of iterator, called a Generator. When a value is consumed by calling the generator's next method, the Generator function executes until it encounters the yield keyword.
The function can be called as many times as desired, and returns a new Generator each time. Each Generator may only be iterated once.
We can now adapt the example from above. The behavior of this code is identical, but the implementation is much easier to write and read.
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
1. ๋ฉํํ๋ก๊ทธ๋๋ฐ
๋ณธ๊ฒฉ์ ์ผ๋ก Proxy๋ฅผ ์์๋ณด๊ธฐ์ ์์ ํ ๊ฐ์ง ๊ฐ๋ ์ ์๊ฐํ๋ ค ํ๋ค. Proxy ๋ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ์ด๋ค ๊ธฐ๋ฅ ๋๋ ๊ฐ๋ ์ ์ง์ํ๊ธฐ ์ํด ๋์จ ๋ฌธ๋ฒ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค. ๋ฐ๋ก ๋ฉํํ๋ก๊ทธ๋๋ฐ(Metaprogramming) ์ด๋ค.
๋ฉํํ๋ก๊ทธ๋๋ฐ์ด๋ ํ๋์ ์ปดํจํฐ ํ๋ก๊ทธ๋จ์ด ๋ค๋ฅธ ํ๋ก๊ทธ๋จ์ ๋ฐ์ดํฐ๋ก ์ทจ๊ธํ๋ ๊ฒ์ ๋งํ๋ค. ์ฆ, ์ด๋ค ํ๋ก๊ทธ๋จ์ด ๋ค๋ฅธ ํ๋ก๊ทธ๋จ์ ์ฝ๊ณ , ๋ถ์ํ๊ฑฐ๋ ๋ณํํ๋๋ก ์ค๊ณ๋ ๊ฒ์ด๋ค. ๋ฉํํ๋ก๊ทธ๋๋ฐ์ ์ด์ฉ๋๋ ์ธ์ด๋ฅผ ๋ฉํ ์ธ์ด๋ผ๊ณ ํ๊ณ , ์กฐ์ ๋์์ด ๋๋ ์ธ์ด๋ฅผ ๋์ ์ธ์ด๋ผ๊ณ ํ๋ค. ๋ฉํ ์ธ์ด์ ๋์ ์ธ์ด๋ ๊ฐ์ ์๋ ์๊ณ , ๋ค๋ฅผ ์๋ ์๋ค.
1.1. ๋ฉํ ์ธ์ด์ ๋์ ์ธ์ด๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ
์๋ ์์ ๋ JSP์์ HTML์ ๋ง๋ค์ด๋ด๋ ์ฝ๋์ธ๋ฐ ๋ฉํ ์ธ์ด๋ Java์ด๊ณ , ๋์ ์ธ์ด๋ HTML์ด ๋ ๊ฒ์ด๋ค. (๋๋ฆ๋๋ก ๊ฐ๋จํ ์์ ๋ฅผ ๋ง๋ค์ด๋ณธ ๊ฑด๋ฐ ๋์์ธ์ด๊ฐ HTML์ด๋ผ ์ฝ๊ฐ ์ ๋งคํ๊ธด ํ๋ค. ํ์ง๋ง ์ด ์์์ <script>๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํ๋ฉด ์ข ๋ ํ์คํด์ง์ง ์์๊น ํ๋ค.)
out.println("<html><body>");
out.println("<h1>Hello World</h1>");
out.println("The current time is : " + new java.util.Date());
out.println("</body></html>");
1.2. ๋ฉํ ์ธ์ด์ ๋์ ์ธ์ด๊ฐ ๊ฐ์ ๊ฒฝ์ฐ
์๋ ์์ ๋ ๋ฉํ ์ธ์ด์ ๋์ ์ธ์ด๋ฅผ ๋ ๋ค JavaScript๋ก ์์ฑํ๊ธฐ ์ํด eval()์ ์ฌ์ฉํ๋ค. eval์ ๋ฐํ์ ์ค์ ๋งค๊ฐ๋ณ์๋ฅผ ํ๊ฐํ๋ค. ์ด๋ ๊ฒ ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๊ฐ ์๊ธฐ ์์ ์ ๋ฉํ ์ธ์ด๊ฐ ๋๋ ๊ฒ์ ๋ฐ์(Reflection) ์ด๋ผ๊ณ ํ๋ฉฐ, ์ด๋ ๋ฐํ์ ์์ ์ ์์ ์ ๊ตฌ์กฐ์ ํ์๋ฅผ ๊ด๋ฆฌํ๊ณ ์์ ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
> eval('1 + 1')
2
๊ทธ๋ผ ๋ฉํํ๋ก๊ทธ๋๋ฐ์ ํ ๊ฐ๋์ธ ๋ฐ์์ ๋ํด ์์๋ณด๋๋ก ํ๊ฒ ๋ค.
1.3. ๋ฐ์ฌํ ํ๋ก๊ทธ๋๋ฐ(Reflective programming)
์ฐ๋ฆฌ๊ฐ ์์๋ณผ Proxy์ Reflect๋ ๋ฐ์์ ๊ตฌํํ ๊ฒ์ด๊ณ , ์ฌ๊ธฐ์๋ ๋๋ค์ ์ธ ๊ฐ์ง์ ์ข ๋ฅ๊ฐ ์๋ค.
- Type introspection
๋ฐํ์์์ ํ๋ก๊ทธ๋จ์ด ์์ ์ ๊ตฌ์กฐ์ ์ ๊ทผํ์ฌ ํ์ ์ด๋ ์์ฑ์ ์์๋ด๋ ๋ฅ๋ ฅ์ ๋ปํ๋ค. ์์๋ก Object.keys()๊ฐ ์๋ค. - Self-modification
๊ตฌ์กฐ๋ฅผ ์ค์ค๋ก ๋ณ๊ฒฝํ ์ ์๋ค๋ ์๋ฏธ๋ก, ์์๋ก ์ด๋ค ์์ฑ์ ์ ๊ทผํ๊ธฐ ์ํด [] ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ๊ณ , delete ์ฐ์ฐ์๋ก ์ ๊ฑฐํ๋ ๊ฒ ๋ฑ์ด ์๋ค. - Intercession
๋ง ๊ทธ๋๋ก ์ด๋ค ๊ฒ์ ๋์ ํ์ฌ ๊ฐ์ ํ๋ ํ์๋ฅผ ๋ปํ๋ฉฐ, ์ธ์ด๊ฐ ์ํ๋๋ ์ผ๋ถ ์๋ฏธ๋ฅผ ์ฌ์ ์ํ๋ ๊ฒ์ ๋งํ๋ค. ์ด๋ฅผ ์ง์ํ๊ธฐ ์ํด ES2015์์ Proxy๊ฐ ๋ง๋ค์ด์ก๋ค. (2ality์ ์ด์์์ธ Axel Rauschmayer์ ํํ(๋งํฌ ์ฐธ์กฐ)์ธ๋ฐ, ์ฌ์ค Object.defineProperty() ๊ฐ์ ๋ฉ์๋๋ค์ด ์ด๋ฏธ ๊ตฌํ์ด ๋์ด ์์ด ๊ธฐ์กด์ ์์๋ค๊ณ ๋ณด๊ธฐ์๋ ์ข ์ ๋งคํ์ง๋ง, Intercession์ ๊ฐ๋ ์ด ๋์์ ๋ณ์ด๋ฅผ ์ผ์ผํค์ง ์๋ ๊ฒ์ ์ด์ ์ด ๋ง์ถฐ์ง๋ค๋ฉด ๋ง๋ ๋ง์ผ ์๋ ์๊ฒ ๋ค.)
2. Proxy๋
ํ๋ก์ ๊ฐ์ฒด(Proxy object)๋ ๋์ ๊ฐ์ฒด(Target object) ๋์ ์ฌ์ฉ๋๋ค. ๋์ ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ๋ ๋์ , ํ๋ก์ ๊ฐ์ฒด๊ฐ ์ฌ์ฉ๋๋ฉฐ ๊ฐ ์์ ์ ๋์ ๊ฐ์ฒด๋ก ์ ๋ฌํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ฝ๋๋ก ๋๋ ค์ค๋ค.
์ด๋ฌํ ๋ฐฉ์์ ํตํด ํ๋ก์ ๊ฐ์ฒด๋ JavaScript์ ๊ธฐ๋ณธ์ ์ธ ๋ช
๋ น์ ๋ํ ๋์์ ์ฌ์ฉ์ ์ ์๊ฐ ๊ฐ๋ฅํ๋๋ก ํ๋ค. ๊ฐ์ฒด ์์ฒด๊ฐ ์ฒ๋ฆฌํ๋ ํน์ ๋ช
๋ น์ ์ฌ์ ์ํ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค. ์ด๋ฐ ๋ช
๋ น์ ์ข
๋ฅ๋ ์์ฑ ๊ฒ์, ์ ๊ทผ, ํ ๋น, ์ด๊ฑฐ, ํจ์ ํธ์ถ ๋ฑ์ด ๋ํ์ ์ด๋ค.
Sources
Symbol - JavaScript | MDN
Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any ot
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
Iterators and generators - JavaScript | MDN
Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops.
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
Reflect - JavaScript | MDN
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
developer.mozilla.org
https://meetup.toast.com/posts/302
JavaScript Proxy. ๊ทผ๋ฐ ์ด์ Reflect๋ฅผ ๊ณ๋ค์ธ : NHN Cloud Meetup
Vue 3 Reactivity๋ Proxy๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ํ์ง๋ง ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ๊ทธ ์์ Reflect๊ฐ ์กด์ฌํ๋๋ฐ์. ์ด๋ฒ ํฌ์คํธ์์๋ Proxy์ Reflect๋ฅผ ํจ๊ป ์ฌ์ฉํ๊ฒ ๋ ์ด์ ๋ฅผ ์์๋ณด์์ต๋๋ค.
meetup.toast.com