const a = 2;
console.log('h' + a + 'i');
// Template Literal === ``
// Expression Interpolation === ${}
console.log(`h${a}i`);
// Tagged Templates: 템플릿 리터럴로 감싸진 내용들을 인자로 하여 함수에 대입.
// 첫 번째 인자: Expression Interpolation 을 기준으로 문자열들이 분할되어 배열로 전달된다.
// 이후의 인자들: Expression Interpolation 의 값들이 전달됨.
const name = '레쉬';
const age = 22;
function flip(strings, personName, personAge) {
const str0 = strings[0]; // Hello,
const str1 = strings[1]; // , you are
return personAge + str1 + personName + str0;
}
const input = `Hello, ${name}, you are ${age}`;
const output = flip`Hello, ${name}, you are ${age}`;
console.log(input); // Hello, 레쉬, you are 22
console.log(output); // 22, you are 레쉬Hello,
// Raw strings
function tag(strings) {
// ↵ 와 \n 의 차이!
console.log(strings); // ['string text line 1 ↵ string text line 2', raw: ['string text line 1 \n string text line 2']];
console.log(strings.raw[0]); // 'string text line 1 \n string text line 2'
}
tag`string text line 1 \n string text line 2`;
//
QUIZ: 템플릿 리터럴 vs 표현식 삽입법 vs 태그된 템플릿 vs Raw string
위의 코드 참고.
const arr = [ 1, 2, 3, 4, 5 ];
console.log([arr[0], ...arr]); // [ 1, 1, 2, 3, 4, 5 ];
const [first, ...rest] = arr;
console.log(first, rest); // 1, [ 2, 3, 4, 5, ];
const obj = {
a: 1,
b: 2,
c: 3
}
const obj2 = {
d: 4
}
console.log({...obj, ...obj2}); // { a: 1, b: 2, c: 3, d: 4};
QUIZ: 두 객체를 병합할 때 중복된 키값들은 어떻게 되는가?
뒤의 값이 앞의 값을 덮어쓴다.
- let vs const
QUIZ: Immutable 에 대해 let 과 const 를 엮어 설명해 보시오.
let 은 가변이며 const 는 불변이다.
이때의 "불변"은 주소 값에 대한 불변이므로 해당 주소 안의 데이터(값)은 변경이 된다.
따라서 const arr = [];
와 같이 const 로 선언된 배열에 push 등으로 값을 변경할 수 있다.
Immutable 은 "불변"으로 주로 Object.freeze
와 같이 더이상 값 또한 변할 수 없는 상태를 뜻한다.(freeze 또한 완벽한 불변을 줄 순 없다)
QUIZ: 두 객체를 병합할 때 중복된 키값들은 어떻게 되는가?
뒤의 값이 앞의 값을 덮어쓴다.
QUIZ3: 가변 내장 함수와 무결성 내장 함수를 선택하시오
가변 함수 | 무결성 함수 |
---|---|
push | concat |
splice | slice |
pop | slice |
기존 자바스크립트 문법에는 클래스 표현식이 없어 prototype
으로 클래스를 표현했다.
function Shape(x, y) {
this.name = 'Shape';
this.move(x, y);
}
Shape.create = function(x, y) { return new Shape(x, y); }; // static method
Shape.prototype.move = function(x, y) { // instance method
this.x = x;
this.y = y;
}
class Shape {
static create(x, y) { return new Shape(x, y); }
name = 'Shape';
constructor (x, y) {
this.move(x, y);
}
move(x, y) {
this.x = x;
this.y = y;
}
area() {
return 0;
}
}
const a = Shape.create(1, 2); // OK.
a.create(2, 3); // ERROR! -> a.create is not a function.
prototype
은 객체를 생성함에 있어서 같은 메모리를 공유하므로 메모리 자원을 절약할 수 있다.
prototype
객체는 new
연산자로 생성되는 객체 안에서 this 연산자의 함수 및 변수 선언 위치를 참조할 수 있다.
QUIZ: static method 의 장단점을 설명해 보시오
정적 메소드는 "인스턴스 없이 호출 가능" 하며 "인스턴스 객체에서는 호출할 수 없다". utility 함수에 적합(Math.max)
단점으로는 Global 하게 어디서든 접근할 수 있어 "상태"를 가지게 될 경우 치명적인 버그로 이어질 수 있다.
function a(a) { // function a : 이름이 a 인 함수
this.a = a;
console.log(a, this.a);
};
const a2 = (a) => { // () => {}: 익명함수
this.a = a;
console.log(a, this.a);
};
class MyClass {
value = 10;
constructor() {
var addThis2 = function(first, second) {
return this.value + first + second;
}.bind(this);
// THIS IS SAME
var addThis3 = (first, second) => this.value + fist + second;
}
}
- 화살표 함수는 익명 함수다.
- 화살표 함수는 콜백 함수의
this
범위로 생기는 오류를 피하기 위해 자동으로bind()
함수를 사용하여this
객체를 전달한다. - 이로 인해 직관적으로 상위 스코프의
this
를 가리키게 된다.(이를Lexical this
라 한다.)
QUIZ: function vs arrow function
기존의 함수는 new
키워드를 통해 인스턴스 생성을 할 수 있다. 하지만 화살표 함수는 "익명 함수"이며 인스턴스 생성을 할 수 없다.
scope
의 차이가 나기 때문에 바라보는 this
가 서로 다르다.
const x = 0;
const y = 1;
const con = { x, y };
const str = `1ilsang`;
const obj = {
[str + '-dev']: `1ilsang-dev`,
go() {
return 'wow';
}
};
console.log(obj['1ilsang-dev'], obj.go());
const arr = [0, 1];
let [a, b] = arr;
[b, a] = [a, b];
console.log(a, b);
const obj2 = {
a: 1,
b: 2,
};
const {
a: A,
b,
c = 'Default Value'
} = obj2;
console.log(A, b, c, obj2);
const [item1, ...otherItems] = [0, 1, 2];
const {key1, ...others} = { a: 1, b: 2, key1: 'KEY1' };
console.log(item1, otherItems, key1, others);
INFO: 구조 분해 할당에서의 유의점
const b = undefined;
const {
a = 'default!'
} = b;
// ERROR! Cannot read property 'a' of undefined.
분해할 객체 자체가 없을 경우 에러가 발생한다. 자주 일어날 수 있으므로 값을 명확히 해주어야 한다.
기존의 자바스크립트는 라이브러리를 <script src="..." />
엘리먼트를 이용해 관리했다.
- 이렇게 할 경우 순서 가 매우 중요해진다.(로드 되기 전의 스크립트 파일을 사용하면 에러)
위의 문제를 해결하고자 ES6 에서 import
구문을 통해 script
엘리먼트 없이 연결된 파일 및 의존 파일을 모두 먼저 내려 받고 코드를 구동 하도록 변경했다.
forEach, map, reduce
forEach
가 요소 자체를 순회하는 것이라면 map
함수는 요소를 순회하면서 "반환한 값으로 새 배열을 만든다".
reduce
함수는 요소를 순회하면서 누적해서 값을 만들고, 최종적으로 누적된 값을 리턴한다.
const arr = [1, 2, 3];
const obj = arr.reduce((acc, cur) => {
acc.value += cur;
return acc;
}, {value: 0});
console.log(obj, obj.value);
reduce
함수는 주로 배열을 특정 자료형으로 변환하는데 사용한다.
QUIZ: forEach vs map
forEach 는 리턴 없이 요소 호출만 하지만, map 의 경우 요소 호출마다 기존 배열과 동일한 사이즈의 새로운 배열을 반환한다.
이로인해 forEach 보다 map 이 70% 정도 느리다.
map 이 각광받는 이유는 다른 함수들과 "조합"이 가능하기 때문.
arr.map(e => e * 2).filter(e => e % 2);
와 같이 사용할 수 있다.
ES6의 비동기 함수 처리 방법 알아보기
const work1 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업1 완료!'), 100);
});
const work2 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업2 완료!'), 200);
});
const work3 = () =>
new Promise(resolve => {
setTimeout(() => resolve('작업3 완료!'), 300);
});
function urgentWork() {
console.log('긴급 작업');
}
const nextWorkOnDone = msg1 => {
console.log('done after 100ms:' + msg1);
return work2();
};
work1()
.then(nextWorkOnDone)
.then(msg2 => {
console.log('done after 200ms:' + msg2);
return work3();
})
.then(msg3 => {
console.log(`done after 600ms:${msg3}`);
});
urgentWork();
const work1and2 = () =>
work1().then(msg1 => {
console.log('done after 100ms:' + msg1);
return work2();
});
work1and2()
.then(msg2 => {
console.log('done after 200ms:' + msg2);
return work3();
})
.then(msg3 => {
console.log('done after 600ms:' + msg3);
});
QUIZ: 위 코드의 실행 결과는?
// 긴급 작업
// done after 100ms: 작업1 완료!
// done after 100ms: 작업1 완료!
// done after 200ms: 작업2 완료!
// done after 200ms: 작업2 완료!
// done after 600ms: 작업3 완료!
// done after 600ms: 작업3 완료!
- "지연 처리"에 대해 다룬다.
- 디바운스(debounce)는 "마지막에 입력된 내용"을 요청한다.
- 예: 자동 완성(유저가 빠르게 타이핑하는 동안은 무시하다 잠깐 멈출 때 요청)
- 스로틀(throttle)은 "특정 시간의 첫 번째 내용"을 요청한다.
- 예: 무한 스크롤(스크롤이 마지막 즈음으로 갈 때 최초 한 번 요청하고 일정 시간동안 액션 홀딩)
무한 스크롤에서 디바운스를 사용하게 될 경우 유저가 계속해서 스크롤을 내리고 있다면 무한히 Delay 만큼의 여유가 있는 마지막 입력을 기다리게 되므로 새로운 페이지가 뜨지 않게된다.
// Debounce
// inDebounce 를 기준으로 timeout 을 거는 형태.
// 입력이 계속해서 들어오면(return function 이 실행되면) clearTimeout 으로 timeout 을 지움.
// 이로 인해 delay 만큼의 충분한 시간이 지난 경우에만 실행 됨.
export function debounce(func, delay) {
let inDebounce;
return function(...args) {
if(inDebounce) clearTimeout(inDebounce);
inDebounce = setTimeout(() => func(...args), delay);
}
};
const run = debounce(val => console.log(val), 100);
run('a');
run('b');
run(2);
// After 100ms
// 2
// Throttle
function throttle(func, delay) {
let lastFunc;
let lastRan;
return function(...args) { // 아래 코드의 window.addEventListener 의 인자가 args 로 들어오게 된다.
const context = this; // this hold
if(!lastRan) { // 최초의 실행에 해당. 즉시 함수를 실행시키며 현재 시간을 lastRan 에 넣어준다.
func.call(context, ...args);
lastRan = Date.now();
} else {
// 마지막에 입력받은 타이머가 있다면 클리어 시킨다.(딜레이 까지 모두 무시)
if(lastFunc) clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
// 현재 시간과 이전에 실행시킨 시간을 뺀 값과 delay 를 비교해 실행 시기를 정한다.
if((Date.now() - lastRan) >= delay) {
func.call(context, ...args);
lastRan = Date.now();
}
}, delay - (Date.now() - lastRan));
}
}
}
const checkPosition = () => {
const offset = 500;
const currentScrollPosition = window.pageYOffset;
const pageBottomPosition = document.body.offsetHeight - window.innerHeight - offset;
if(currentScrollPosition >= pageBottomPosition) { // 바텀의 근처로 스크롤이 진입하면
// fetch('/page/next');
console.log('다음 페이지 로딩');
}
};
const infiniteScroll = throttle(checkPosition, 300); // 첫 이벤트 시작 후 300ms 동안의 이벤트는 무시하는 throttle 함수를 가져온다.
window.addEventListener('scroll', infiniteScroll); // 스크롤 이벤트가 일어날 때마다 infiniteScroll 함수를 실행시킨다.