코드스피츠77 ES6_4회차_Abstract loop & lazy execution
🌕🌑🌑
TL;DR
복습할때나 이 문제가 까먹었을 즈음에 풀어보기
- iterable객체 완성하기
???구해보기 (2가지 방법으로)
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
return ???
}
}코드스피츠 강의 정리록입니다.
4강 키워드: Generator의 지연실행 측면과 이를 위한 추상루프화
1. abstract loop: 루프의 추상화
1.1 complex recursion 복잡한 반복 단순한 배열 루프인 경우 간단히 이터레이션을 작성할 수 있다.
{
[Symbol.iterator](){return this;},
data:[1,2,3,4],
next(){
return {
done:this.data.length == 0,
value:this.data.shift()
};
}
}복잡한 다층형 그래프는 어떻게 이터레이션 할 것인가?
es6와 es6이전의 객체 리터럴의 큰 차이점이 무엇인가
- es6이전
- 기존의 객체 리터럴에서는 객체 리터럴의 순서가 없다.
- 자바로 따지면 hash memory로 되어있다.
- es6
- es6의 객체 리터럴은에서는 객체 리터럴의 순서가 있다.
- linked hash memory로 되어있다.
- 반드시 순서대로 해석된다.
문제
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
return ???
}
}생각해보기 (글쓴이 생각..)
- iterable 프로토콜 유지하기
- 배열 요소들의 타입 검사
- 객체일 경우 객체 내부 키값의 요소들의 타입 검사
- 객체일 경우
- 배열일 경우
- 일반 값인 경우 => 처음 타입 검사와 같은 흐름이므로 해당 flow를 recursive 되도록 한다.
- 어떻게 같은 루프를 다시 태우지..
- 배열일 경우 배열 요소의 타입 검사
- 객체일 경우
- 배열일 경우
- 일반 값인 경우 => 처음 타입 검사와 같은 흐름이므로 해당 flow를 recursive 되도록 한다.
- 어떻게 같은 루프를 다시 태우지..
- 일반 값인 경우 값 반환
cf__1. 루프를 어떻게하면 잘 짤 수 있을까.
루프를 잘 짜고 보다 어려운 로직을 짜는 방법은 내 머리가 선언한 변수만큼을 추적할 수 있어야 한다…
기본적인 알고리즘 전략은 최대한 상태를 덜쓰는것. 내 머리가 추적할 정도로..
정답
각각이 컨테이너 형이라면 그 컨테이너를 다시 해체해서 다시 배열에 붙여준다. 더 이상 컨테이너 형이 아닐 때까지
// 프로토콜만 맞춰주면 된다.
// {
// done: boolean,
// value: 출력값..
// }
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
let v;
while(v = this.data.shift()) {
// shift가 더이상 안되면 undefined
switch(true){
case Array.isArray(v):
// 2번째 케이스문의 객체 타입가드 조건문이 array 타입도 포함되므로, array만을 위한 조거문을 먼저 작성한다.
this.data.unshift(...v);
break;
case v && typeof v === 'object':
for(var k in v) this.data.unshift(...x);
break;
default:
return {value:v, done: false}
}
return {done: true}
}
}
}정답 내의 오류
for(var k in v)
- for…in문은 prototype의 key까지 다 나오기 때문에
hasOwnProperty를 조건에 추가해야한다.
- hasOwnProperty는 자신의 고유 속성, 즉 상속받은 프로퍼티가 아닌 순수 자신의 속성인 경우에만 true라는 값을 반환하는 특징이 있습니다.
for (var k in v) {
if (v.hasOwnProperty(k)) this.data.unshift(...x);
}- 현재 상태에서는 순서 보장이 안된다.
let x = [];
for (var k in v) x.push(v[k]);
this.data.unshift(...x);한번더 리팩토링
값인 타입을 v instanceof Object로 판단할 수 있다!
{
[Symbol.iterator](){return this;}
data: [{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
next(){
let v;
while(v = this.data.shift()) {
if(!(v instanceof Object)) return {value: v};
if(!Array.isArray(v)) v = Object.values(v);
this.data.unshift(...v);
}
return {done: true}
}
}정리
자바스크립트 언어는 2가지로 되어있다.
- 언어자체의 문법적인 내용,
- 클래스 라이브러리 자바스크립트의 클래스 라이브러리는 코어객체 (Built-in) Math, Date, RegEx… => 표준으로 제공되고 있는 언어 표준의 일부 => 언어 스팩의 일부
코어객체에 있는 메서드들을 사용하는 것이 기본 언어자체의 문법을 사용하는 것보다 더 안전하다. ex. for..in보다 Object.values
재사용성 올리기
unshift나shift로 인해서 data의 배열이 빈배열이 되는 상황이 된다. 클래스로 묶어서 매번 부를때마다 인스턴스를 반환하는 형태로 만들자.
cf__2. 함수를 변수에 할당하자.
함수를 정의할때 3가지 스타일
- 함수 표현식
- 함수 선언문
- Function…
함수는 값이기 때문에 변수에 할당하는 형식이 맞다. 더 정확하게 호이스팅에 의존하지 않고 어느 시점에 함수를 만들었다를 명확하게 코드로 인지할 수 있기 때문에 function으로 시작하는 함수 정의방법은 아예 금지시키고 못쓰게 하는 경우가 많다. lint에도 함수선언문 금지룰이 있다.
클래스도 하나의 값이다. 변수에 할당이 된다. 변수의 할당 없이 class..로 시작하는 선언방법은 이 클래스가 언제 만들어졌는지 모호하게 만드는 관점이 있다.
const Compx = class {
constructor(data) {
this.data = data;
}
[Symbol.iterator]() {
const data = JSON.parse(JSON.stringify(this.data));
// 완전한 복사. 가장빨리 복사하는 방법..
return {
next() {
let v;
while ((v = data.shift())) {
if (!(v instanceof Object)) return { value: v };
if (!Array.isArray(v)) v = Object.values(v);
data.unshift(...v);
}
return { done: true };
},
};
}
};
const a = new Compx([{ a: [1, 2, 3, 4], b: '-' }, [5, 6, 7], 8, 9]);
console.log([...a]);
console.log([...a]);shift를 사용하는 것은 data가 배열이 와야하는 조건이 있기 때문에 data의 사본을 만들때 아예
배열화시킨다. const data = [JSON.parse(JSON.stringify(this.data));]
if절을 보면 mandatory가 아닌 optional.
const Compx = class {
constructor(data) {
this.data = data;
}
*gene() {
const data = [JSON.parse(JSON.stringify(this.data))];
let v;
while ((v = data.shift())) {
if (!(v instanceof Object)) yield v;
else {
if (!Array.isArray(v)) v = Object.values(v);
data.unshift(...v);
}
}
}
};
const a = new Compx([{ a: [1, 2, 3, 4], b: '-' }, [5, 6, 7], 8, 9]);
console.log([...a]);
console.log([...a]);1.2 abstract loop 위의 루프는 목적이 있는 루프이다. 목적이 있는 루프를 만들고 목적을 바꾸면
루프를 다시 짜야한다.
다양한 구조의 루프와 무관하게 해당 값이나 상황의 개입만 하고 싶은 경우
(data, f) => {
let v;
while ((v = data.shift())) {
if (!(v instanceof Object)) {
// v로 뭔가 하는 부분
f(v);
} else {
if (!Array.isArray(v)) v = Object.values(v);
data.unshift(...v);
}
}
};이 상황에서 f(v)를 호출하는 body에 본래 목적과 다른 로직이 추가 될 경우, 같은 로직을 복사하고 원하는 로직을 추가해야하는 상황이 발생한다. 문으로 로직을 작성할 때는 별다른 방법이 없다. => 제어문을 재활용할 수 없으므로 중복정의할 수 밖에 없다. => 문은 사용하고 나면 재활용할 수 없는데 이걸 어떻게 객체화하지?
결국 제어문을 직접 사용할 수 없고 구조객체를 이용해 루프 실행기를 별도로 구현
구조를 추상화해보자.
- 루프 공통 골격
- 개별구조객체
cf__3. if문을 제거하는 방법
우리가 배우는 거의 모든 priority 기법은 if를 어떻게하면 제거할까에 대한 연구일 수도 있다. 사람은 if가 많아지면 감당이 안된다. 조건이 많아져서.. if를 어떻게하면 제거할 수 있을까?
- 정단한 이유라면 바로 제거할 수 없다. 필요에 의해서 태어났기 때문에
- if로 나눠지는 경우의 수만큼의 값을 미리 만들어 놓고, 바깥쪽에서 그 값을 선택해서 들어오게 하는 수밖에 없다.
아래는 if가 3개이다.
- array일때
- object일때
- primitive일때 3개만큼의 객체를 만들어놓고 바깥쪽에서 객체를 결정하게 끔 만들어 주면 값으로 분리할 수 있게 된다. => 이렇게 if문이 하나씩 제거된다.
(data, f) => {
let v;
while ((v = data.shift())) {
if (!(v instanceof Object)) {
f(v);
} else {
if (!Array.isArray(v)) v = Object.values(v);
data.unshift(...v);
}
}
};
팩토리 + 컴포지트
- 선택기는 팩토리 패턴으로
- 각각의 조건문을 컴포지트 패턴으로
cf__4. 팩토리 패턴 참고
- 비슷한 객체를 공장에서 찍어내듯이 반복적으로 생성할 수 있게 하는 패턴
- 컴파일 시점에 구체적인 타입(클래스)을 몰라도 객체 생성이 가능하다
- 팩토리 패턴의 가장 흔한 사례는 Object() 를 이용한 객체 생성시, 주어지는 값의 타입에 따라 String, Boolean, Number 등으로 객체가 생성되는 것이다.
cf__5. composite 패턴
// 😵😵😵
const Operator = class {
static factory(v) {
if (v instanceof Object) {
if (!Array.isArray(v)) v = Object.values(v);
// --- object도 배열로 환원시켰기 때문에 현재 v는 배열임.
return new ArrayOp(v.map(v => Operator.factory(v)));
} else return new PrimaOp(v);
}
constructor(v) {
this.v = v;
}
operation(f) {
throw 'override';
}
};
// primitive 타입 처리하는 클래스
const PrimaOp = class extends Operator {
constructor(v) {
super(v);
}
operation(f) {
f(this.v);
}
};
// 배열 타입을 처리하는 클래스
const ArrayOp = class extends Operator {
constructor(v) {
super(v);
}
operation(f) {
for (const v of this.v) v.operation(f);
}
};
Operator.factory([1, 2, 3, { a: 4, b: 5 }, 6, 7]).operation(console.log);- 케이스를 더 많이 늘리는 것도 가능하다..