정직하게 일정 값(파라미터)을 넣으면, 일정한 값(반환값)이 나오며, 프로그램의 어느것도 건들지 않음
Difficult to Reason 디버깅 및 프로그램 흐름 분석이 어려워짐 : 암살자에 의해 어디에선가 객체가 변동
외부 상태를 변경 : 해당 객체가 무엇에 의해 변경되고있는지 추적 난해
1. 어떤 개발자가 자신 의도대로 외부 객체를 변경 시 어떤 누군가는 의도치않게 변경된 객체 사용 = 어리둥절
2. 파라미터를 통해 바꿀 객체를 전달하니 1번 보단 낫지만, 반환값 없이 파라미터 객체를 변경하는 것은 비명시적
3. 2번에서의 명시적이지 않은 문제는 해결했지만, ① 파라미터 객체가 바뀐 문제 ② 바꾼 객체가 파라미터 객체와 동일
const member = Object.freeze({ name: 'Seonga', social: '010407-0000000' })
console.log(member) // { name: 'Seonga', social: '010407-0000000' }
member.social = '961028-1111111'
console.log(member) // { name: 'Seonga', social: '010407-0000000' } <- 변하지 않음
object
는 객체를 가리키는 주소값을 가짐 : 주소를 복사하는가? 값을 복사하는가?Object.assign
은 얕은 복사(Shallow Copy)에 해당자바스크립트에도 여타 프로그래밍 언어와 같이 버전이 존재
변수 선언은 자바스크립트 버전인 ECMAScript 6 (2015) 이전에는 var
하나의 방법으로만 선언
ECMAScript 6 버전 이후로 변수 선언은 let
과 const
로 선언할 수 있다.
let
: 가변 변수 선언 시 - 예) `let heroLevel = 1` 레벨이 올라감에 따라 변경되는 레벨값const
: 불변 변수 선언 시 - 예) `const TOTAL_MONTHS = 12` 고정된 12 라는 값재선언 | 재할당 | 스코프 | |
---|---|---|---|
var | O : 재선언 시 덮어쓰기 | O | 함수 |
let | X : 방어 | O : 가변 변수 | 블록 |
const | X : 방어 | X : 불변 변수 | 블록 |
var
: 재선언 가능 + 재할당 가능 + 함수레벨 스코프let
: 재선언 불가 + 재할당 가능 + 블록레벨 스코프const
: 재선언 불가 + 재할당 불가 + 블록레벨 스코프함수 스코프와 블록 스코프는 어떤것이며, 어떤 점에서 다른가
0
은 숫자 리터럴‘hello’
는 문자열 리터럴function() {}
은 함수 리터럴{}
는 객체 리터럴 등자바스크립트 엔진은 자바스크립트 파일을 실행한다고 생각하지만 사실 함수를 실행하고, 파일은 main 함수인격
따라서 자바스크립트 엔진의 자바스크립트 파일 구동 방식은 자바스크립트 함수 구동 방식과 동
이에 자바스크립트 엔진의 자바스크립트 함수 구동 방식에 대해 아래 간단히 2개의 Phase 로 나누어 진행
자바스크립트 엔진은 Stack + Heap 두개만 기억 = 자바스크립트 함수 동작 원리 이해도 마찬가지로 두개만 기억
프로그램에 메모리와 CPU 자원을 할당한 뒤 구동하면 프로세스
main()
메서드 혹은 함수의 Stack 에서 처음 시작global
window
: 함수(파일) 분석하며 메모리(실행 컨텍스트, 렉시컬 환경) 내 변수, 함수 적재
잘못된 오해 : const 와 let 는 호이스팅 X → 여전히 호이스팅 O
var 의 예시 : 변수 호이스팅되나, 초기화가 되기때문에 할당 및 사용 시 에러 미발생
console.log(hoisting) // **변수 호이스팅 (선언 X, 초기화 O)** = undefined
var hoisting = 'hello'
hoisting = 'world' // **변수 호이스팅 (선언 X, 초기화 O)** = 'world'
var hoisting = 'hello'
let
의 예시 : 변수 호이스팅되나, 초기화가 안되기때문에 할당 및 사용 시 에러 발생
console.log(hoisting) // **변수 호이스팅** **(선언 X, 초기화 X)** = ReferenceError: hoisting is not defined
let hoisting = 'hello'
hoisting = 'world' // **변수 호이스팅** **(선언 X, 초기화 X)** = ReferenceError: Cannot access 'hoisting' before initialization
let hoisting = 'hello'
function
도 어김없이 함수 호이스팅
: 함수 실행을 위해 먼저 메모리(실행 컨텍스트, 렉시컬 환경) 확보 후 실행
let
은 선언과 동시에 초기화되지 않아, 호이스팅이 발생해도 할당 전에 초기화가 안되어있어 오류여기서 말하는 초기화 라는 것은 undefined
라는 빈 값을 변수에 할당해주는 것 (예비 빈 값을 넣어주는것)
자바스크립트도 컴파일을 한다는 사실
let
, const
도 결국 var
와 동일하게 변수 호이스팅이 발생하는데, 왜 ReferenceError 가 발생하지?
**ReferenceError**: Cannot access ? before **initialization**
var name = 'zero';
function log() {
console.log(name);
}
function wrapper() {
name = 'nero'; // 이때 어떤 결과가 나올지 생각해보고, var name = 'nero'; 일땐 어떨까?
log();
}
wrapper();
Lexical(Static) Scope 면접 문제 풀이 : name = “zero” 사례
Lexical) Scope Chain 시 Global Execution Context 까지 다 찾았음에도 없다면 가장 마지막에 생성
ReferenceError: X is not defined
발생 = 선언하지 않음function doublewrapper() {
name = 'zero';
function log() {
console.log(name);
}
function wrapper() {
name = 'nero';
log();
}
wrapper();
}
doublewrapper();
function () {}
= 함수 선선언hoisting() // **함수 호이스팅** = function is hoisted
function hoisting() {
console.log('function is hoisted')
}
undefined
/ ReferenceError
= 변수 선선언 + 초기화 (var 의 경우)var
의 예시
console.log(hoisting) // **변수 호이스팅 (초기화 O)** = undefined
var hoisting = 'hello'
hoisting = 'world' // **변수 호이스팅 (초기화 O)** = 'world'
var hoisting = 'hello'
let
의 예시
console.log(hoisting) // **변수 호이스팅** **(초기화 X)** = ReferenceError: hoisting is not defined
let hoisting = 'hello'
hoisting = 'world' // **변수 호이스팅** **(초기화 X)** = ReferenceError: Cannot access 'hoisting' before initialization
let hoisting = 'hello'
function func() {
console.log('함수')
}
func() // '함수'
const object = {
field: '+필드',
method: function() { console.log('메서드' + this.field) }
}
object.method() // '메서드+필드'
함수 선언문 : 함수 호이스팅 발생 → 함수 선선언 (정의)
함수 표현식 : 변수 호이스팅 발생 → 변수 선선언 + 할당
hoisting() // **변수 호이스팅** = ReferenceError: Cannot access 'hoisting' before initialization
let hoisting = function () {
console.log('variable is hoisted')
}
let named = function name() {
console.log('기명함수')
}
let unnamed = function () {
console.log('익명함수')
}
함수 표현식은 2가지 방식으로 표현 : (traditional) function() {} 그리고 (alternative) () ⇒ {}
함수 자체의 this, arguments, super, new.target 바인딩을 갖지않음, 이 중 가장 핵심은 this
function traditional() {
console.log(this)
console.log(arguments)
console.log(new.target)
}
new traditional()
var param = 'global param'
let printParam = function() {
console.log(this.param) // this = object
/* function() {} 일반 함수 표현법은
**동적 바인딩**이라 해당 함수가 **호출**된 **객체**의 this 를 참조
- **호출**된 **객체** : object 객체에서 **호출**됨 */
}
let object = {
param: 'object param',
func: printParam
}
object.func() // object param
var param = 'global param'
let printParam = () => {
console.log(this.param) // this = window(global)
/* () => {} 화살표 함수 표현법은
정적 바인딩이라 해당 함수가 선언된 함수의 this 를 참조 (앞서 배운 Lexical Scope 와 동일)
- 선언된 함수 : global object(function, main() 함수 개념)에서 호출됨 */
}
let object = {
param: 'object param',
func: printParam
}
object.func() // global param
var param
이 아닌 let param
으로 바꾸었을때 undefined
가 출력let
전역 변수는 보이지 않는 개념적인 블록 내에 존재하게 되기 때문var
전역 변수는 전역 객체의 프로퍼티가 되지만let
전역 변수는 전역 객체의 프로퍼티가 아님function() {}
와 달리 () ⇒ {}
는 메서드가 될 수 없다 = 필드 접근 불가const object = {
field: '+필드',
not_method: () => { console.log('넌 내가 아직도 메서드로 보이니', this.field) }
}
object.not_method() // 출력 결과 : '넌 내가 아직도 메서드로 보이니 undefined'
function helloworld() {
console.log('h')
}
function helloworld() {
console.log('w')
}
helloworld()
var helloworld = () => {
console.log('h')
}
var helloworld = () => {
console.log('w')
}
helloworld()
let helloworld = () => {
console.log('h')
}
let helloworld = () => {
console.log('w')
}
// SyntaxError: Identifier 'helloworld' has already been declared.
(1) 일반 함수 표현식 내, (2) 화살표 함수 표현식 내
→ (1) 일반 함수 표현식 의 경우에 함수로도 사용가능하지만, 메서드로도 사용가능하기에 (1.A) 과 (1.B) 로 나뉜다.
this
: (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)this
: (함수) 정적 바인딩 = 정적 스코프 (Static, Lexical Scope)function Constructor() { // 호출지 : Constructor 객체
this.field = 0; // (위와 동일) Constructor 객체 내 this.field = 0
function inner() { // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
console.log(this.field); // (위와 동일) this.field = undefined (전역객체에 var 선언)
}
inner();
}
var object = new Constructor();
= 기술면접을 위해 별의 별 짓으로 다 꼬아놓아도, 초기화되지 않았으면 → 무지성 window(global)
function Constructor() { // 호출지 : Constructor 객체
this.field = 0; // (위와 동일) Constructor 객체 내 this.field = 0
setInterval(function inner() { // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
console.log(this.field); // (위와 동일) this.field = undefined (전역객체에 var 선언)
}, 1000);
}
var object = new Constructor();
객체화 혹은 초기화가 안됐으면 그냥 밖으로 나가면 된다. 그리고 Global Object(전역객체)를 만나서 없으니 var
새로 생성
this.field = 0;
은 new Constructor()
객체화때문에 객체 내 갇힘. inner()
함수는 객체화 되지 않아 밖으로 나감
추가 예시를 통한 이해
function Constructor1() { // 호출지 : Constructor1 객체
this.field = 0; // (위와 동일) Constructor1 객체 내 this.field = 0
console.log(this.field) // (위와 동일) Constructor1 객체 내 this.field = 0
function doublewrapper() { // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
this.field = 1; // (위와 동일) 전역객체 내 this.field = 1
console.log(this.field); // (위와 동일) 전역객체 내 this.field = 1
function Constructor2(){ // 호출지 : Constructor2 객체
// field = 4; // - 추가 케이스 1: 주석을 걷어내면 어디에 값이 설정될까?
// this.field = 4; // - 추가 케이스 2: 주석을 걷어내면 어디에 값이 설정될까?
console.log(this.field); // (위와 동일) Constructor2 객체 내 this.field = undefined
}
new Constructor2();
}
doublewrapper();
}
new Constructor1();
console.log(field); // 전역객체 내 field = 1
var field = 100;
와 field = 100;
차이 ← 아래 코드의 동작 방식var field = 100;
: Global Variable in Global Scope = Global Execution Contextfield = 100;
: Global Property of Global Object (Scope Chain 에도 변수 선언이 없는 경우)// var field = 100; // Global Variable in Global Scope = Global Execution Context
// field = 100; // Global Property of Global Object (implicit assignment)
function Constructor1() { // 호출지 : Constructor1 객체
this.field = 0; // (위와 동일) Constructor1 객체 내 this.field = 0
console.log(this.field) // (위와 동일) Constructor1 객체 내 this.field = 0
function doublewrapper() { // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
this.field = 1; // (위와 동일) 전역객체 내 this.field = 1
console.log(this.field); // (위와 동일) 전역객체 내 this.field = 1
}
doublewrapper();
}
new Constructor1();
console.log(field); // 전역객체 내 field = 1
this
: (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)this
const Object = {
field: 0,
method() {
console.log(this.field);
}
}
Object.method();
this
: (함수) 정적 바인딩 = 정적 스코프 (Static, Lexical Scope)console.log('Outer : ' + field);
출력값은var object = **new Constructor**();
일때 : ReferenceError: field is not defined
var object = **Constructor**();
일때 : Outer : 0
출력this.field = 0;
통해 Global Object 전역객체에 Property 추가function Constructor() {
this.field = 0;
setInterval(() => {
console.log('Callback : ' + this.field);
}, 1000);
}
var object = new Constructor();
console.log('Outer : ' + field); // new Constructor(); 일때와 Constructor(); 일때가 다르다
왼쪽 : 일반 함수 / 오른쪽 : 화살표 함수 | 우리가 앞서 배웠던 Lexical Scope 를 그대로 적용 → Constructor
함수를 바라봄
종합 문제
const object = {
nickname: 'Aaron',
method: function() { console.log(this.nickname) },
method_shorten() { console.log(this.nickname) },
arrow_function: () => console.log(this.nickname),
}
object.method();
object.method_shorten();
object.arrow_function();
const object = {
// arrow_function: () => console.log(this.nickname), // Global Property in Global Object
arrow_function: () => console.log(nickname), // Global Variable in Global Scope = Global Execution Context
}
object.arrow_function(); // ReferenceError: nickname is not defined
// this.nickname -> undefined 로 표기 = Global Object 전역객체에 없는 프로퍼티기 때문
// nickname -> ReferenceError: nickname is not defined = 선언되어있지 않은 변수이기에
(2) Explicit Binding 명시적 바인딩 (call, apply, bind)
방금 배운 내용 2가지만 제대로 복습한다면, 명시적 바인딩은 좀 더 쉽게 이해
this
: (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)function() {}
와 달리 () ⇒ {}
는 메서드가 될 수 없다 = 필드 접근 불가this
= 객체call
, apply
, bind
을 통해 이뤄짐Explicit Binding 명시적 바인딩이 this
를 사용하는 함수에 명시적으로 객체를 할당해주면 메서드가 됨
Explicit Binding 명시적 바인딩에 따른 (1.B) 메서드와 (2) 화살표 함수 간 this
차이
function createObject() {
// this.foo = 21;
console.log('Inside `createObject`:', this.foo)
return {
foo: 42,
bar: function() { console.log('Inside `bar`:', this.foo) },
};
}
createObject.call({ foo: 21 }).bar()
function createObject() {
// this.foo = 21;
console.log('Inside `createObject`:', this.foo)
return {
foo: 42,
bar: () => console.log('Inside `bar`:', this.foo),
};
}
createObject.call({ foo: 21 }).bar()
<head></head>
<div **class=”example”**>
var person = {
name: "Seonga",
sayName: function() {
console.log(this.name);
}
};
class Person {
name = "Seonga";
sayName = function() {
console.log(this.name);
}
};
var person = {
name: "Seonga",
sayName() {
console.log(this.name);
}
};
JSON (JavaScript Object) 보면 알 수 있듯 Java 와 같이 객체 사용에 꼭 클래스가 필요한건 X
일반적으로 메서드를 포함하지 않은 JSON 으로 데이터 전송 시 유용하게 사용 - Java 의 DTO 와 유사
{
name: "Seonga",
age: 25,
}
SOLID 나 디자인 패턴같은 객체지향적 요인으로 코드 재사용성을 증대시키기 위해선 클래스를 사용
class User {
constructor(firstname, lastname) {
this.firstname = firstname
this.lastname = lastname
}
fullname() {
alert(`${this.firstname} ${this.lastname}`)
}
}
1. 클래스 내 Private 필드 설정과 Getter 와 Setter 메서드
class User {
#first_name;
#last_name;
constructor(first_name, last_name) {
this.#first_name = first_name;
this.#last_name = last_name;
}
information() {
console.log(`${this.#fullname} - 사람 이름 입니다.`)
}
get #fullname() {
return `${this.#first_name} ${this.#last_name}`;
}
set fullname(value) {
[this.#first_name, this.#last_name] = value.split(" ")
}
}
const user = new User('Seonga', 'Lee');
user.fullname = 'Seonga Lee'
Object.getOwnPropertySymbols(user)
console.log(user.firstname)
console.log(user.lastname)
console.log(user.fullname)
firstname
에 접근이 가능한것처럼 보이나?class User {
#first_name;
**last_name;**
constructor(first_name, last_name) {
this.#first_name = first_name;
**this.last_name = last_name;**
}
information() {
return `${this.#fullname} - 사람 이름 입니다.`;
}
get #fullname() {
return `${this.#first_name} ${**this.last_name**}`;
}
set fullname(value) {
[this.#first_name, **this.last_name**] = value.split(" ")
}
}
const user = new User('Seonga', 'Lee');
user.fullname = 'Seonga Lee'
**user.first_name = 'Jeonga'**
Object.getOwnPropertyNames(user); // [ 'last_name', 'first_name' ]
console.log(user.first_name); // 'Jeonga'
console.log(user.last_name); // 'Lee'
console.log(user.information()); // 'Seonga Lee - 사람 이름 입니다.'
<script>
를 통해 필요한 라이브러리들을 받아 사용하였으니, 아래의 수 많은 문제가 발생<script>
로드 시 Async 혹은 Defer 전략 사용 가능<script>
스크립트들은 기본적으로 전역 스코프로 변수 이름 충돌이나 오염 발생웹 브라우저는 페이지 표기를 위해 HTML 파일을 위에서 아래로 순서대로 읽으며 DOM Tree 생성
<script>
를 만난다면 이 또한 DOM 요소이기에 완벽하게 로드 및 실행 필요<script>
의 JS 파일을 다 실행한다면 아래 문제 발생<script>
아래 위치한 DOM 접근 불가<script>
를 맨 아래로 내리면 스크립트 늦게 실행→ 스크립트 로드 시점 조율한다면 따라 웹 브라우저 내 HTML (DOM) 웹 페이지 로드 속도 향상 가능
일반적으로 CPU 가 멈추는걸 의미
- Blocking : 하나의 작업이 간간히 방해받으며 수행
- 방해 : 주기적인 프로그램 실행 상태를 확인하는 등
- Non-Blocking : 하나의 작업이 어떠한 방해도 받지않고 수행
일반적으로 병렬처리를 의미
- 동기 Synchronous : 앞선 작업이 완료되어야 그 다음 작업을 수행
- 비동기 Asynchronous : 앞선 작업이 완료되든말든 그 다음 작업을 수행
- 작업이 완료되면, 완료되었다는 응답 : Callback
앞선 호랑이 담배피던 시절의 전역 스코프가 아닌 모듈 스코프를 통해 필요할 때만 가져다 쓸 수 있게 제공
import
와 export
를 완벽하게 지원하지 않아 Webpack 같은 모듈 번들러 함께 사용export const sum = (x, y) => x + y
export const minus = (x, y) => x - y
import **{** sum, minus **}** from './util.mjs'
console.log(sum(2, 4))
export default (x, y) => x + y
import ssam from './util.mjs'
console.log(ssam(2, 4))
const sum = (x, y) => x + y
export default sum
import ssam from './util.mjs'
console.log(ssam(2, 4));
웹 브라우저(프론트엔드)에서가 아닌 웹 서버(백엔드)에서 자바스크립트 모듈 쓰려면 파일 단위 모듈화 절실
다양한 라이브러리 등장 및 웹 서버 자바스크립트 코드량이 많아지면서 변수, 함수의 모듈화 필요
일반적으로 CJS 는 Node.js(서버) 에서 사용 ESM 은 브라우저에서 사용
여러분들이 웹 브라우저와 웹 서버 자바스크리트 런타임을 지원하는 자바스크립트 라이브러리를 만든다면
Callback ≠ Asynchronous
함수(콜백)를 파라미터로 넘겨(일급함수) 파라미터를 받은 함수에게 실행권을 넘기는 것
function callback(param1, param2) {
console.log(param1 + " + " + param2);
}
function caller(callback) {
callback(arguments[1], arguments[2]);
}
caller(callback, "hello", "world");
function callback(param1, param2) {
console.log(param1 + " + " + param2);
}
function caller(callback) {
setTimeout(
/* 무엇을 호출할래요? */() => callback(arguments[1], arguments[2]),
/* 몇초뒤 호출할래요? */2000
);
}
caller(callback, "hello", "world");
Callback 결과값이 순차적으로 필요할때 발생 = Callback 결과값이 서로 의존성으로 연결
function eggToChick(egg, handleChick) {
const chick = "🐥"
console.log("egg to chick : ", egg, "", chick)
handleChick(chick)
}
function chickToHen(chick, handleHen) {
const hen = "🐓"
console.log("chick to hen : ", chick, " → ", hen)
handleHen(hen)
}
function henToEgg(hen, handleEgg) {
const egg = "🥚"
console.log("hen to egg : ", hen, " → ", egg)
handleEgg(egg)
}
function eggToFried(egg) {
const fried = "🍳"
console.log("egg to fried : ", egg, " → ", fried)
}
const egg = "🐣"
eggToChick(
/* 1단계 달걀 */egg,
/* 2단계 콜백 */(chick) => {
chickToHen(
/* 2단계 삐약 */chick,
/* 3단계 콜백 */(hen) => {
henToEgg(
/* 3단계 꼬꼬 */hen,
/* 4단계 콜백 */(egg) => {
eggToFried(egg)
}
)
}
)
}
)
Callback + Asynchronous = Promise 는 두 개로 이해
그래서 Promise 를 Producer-Consumer Pattern on Asynchronous 라고 표현하기도
function caller(callee) {
var produced = producing()
callee(produced)
}
caller(function callee(produced) {
consuming(produced)
})
resolve()
reject()
function caller(resolve, reject) {
const produced = producing() // API 호출해줘, 이미지 가져와줘
if (succeeded) resolve(produced)
if (failed) reject(produced)
}
caller(
function resolve(produced) { consuming(produced) },
function reject(produced) { consuming(produced) }
)
function producing() {
// return { success: true, value: '성공했습니다 :)' };
return { success: false, value: '실패했습니다 :(' };
}
function caller(/* 성공 콜백 */resolve, /* 실패 콜백 */reject) {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
}
caller(
/* 성공 콜백 */function resolve(/* 성공 결과 */value) { console.log('성공👍 ' + value) },
/* 실패 콜백 */function reject(/* 실패 결과 */value) { console.log('실패👎 ' + value) }
)
.then
과 .catch
로 주입된다..then()
내부에 resolve()
성공 콜백 함수.catch()
내부에 reject()
실패 콜백 함수new Promise(
function caller(resolve, reject) {
const produced = producing()
if (succeeded) resolve(produced)
if (failed) reject(produced)
}
)
.then(function resolve(produced) { consuming(produced) })
.catch(function reject(produced) { consuming(produced) })
function producing() {
// return { success: true, value: '성공했습니다 :)' };
return { success: false, value: '실패했습니다 :(' };
}
new Promise(
function producer(/* 성공 콜백 */resolve, /* 실패 콜백 */reject) {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
}
)
.then(/* 성공 콜백 */function resolve(/* 성공 결과 */fulfilled) { console.log('성공👍 ' + fulfilled) })
.catch(/* 실패 콜백 */function reject(/* 실패 결과 */rejected) { console.log('실패👎 ' + rejected) })
new Promise((resolve, reject) => {
const **result** = producing()
if (**result.success**) resolve(**result.body**)
if (**result.failed**) reject(**result.error**)
})
.then((**body**) => { consuming(**body**) })
.catch((**error**) => { consuming(**error**) })
function producing() {
// return { success: true, value: '성공했습니다 :)' };
return { success: false, value: '실패했습니다 :(' };
}
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
})
.then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })
.catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })
Pending 상태 후 → 성공 (이행) Fulfilled / 실패 (거부) Rejected 상태에 따라 다른 Callback 함수 호출
.then()
내 성공 Callback 함수인 resolve()
에서 처리.catch()
내 실패 Callback 함수인 reject()
에서 처리성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태 = Promise.resolve 객체 반환
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
})
Promise.resolve('성공했습니다 :)')
.then
콜백 함수를 통해 처리 가능new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
})
.then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })
Promise.resolve('성공했습니다 :)')
.then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })
Promise.**reject
객체 반환 → .catch
에서 처리**
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
})
Promise.reject('실패했습니다 :(')
.catch
콜백 함수를 통해 처리 가능.catch
의 결과가 또 새로운 Promise 기 때문new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
var produced = producing()
if (produced.success) { resolve(produced.value) }
if (!produced.success) { reject(produced.value) }
})
.catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })
Promise.reject('실패했습니다 :(')
.catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })
.finally()
new Promise
가 생성되는 즉시 Caller (Executor) 함수가 실행 = 즉시 실행new Promise((resolve, reject) => { **resolve**('성공') })
= Promise.**resolve**('성공')
new Promise((resolve, reject) => { **reject**('실패') })
= Promise.**reject**('실패')
.then
으로 전달하는 성공 Callback 에서 받아 사용하지말 것step1(value1)
.then((value2) => {
step2(value2)
.then((value3) => {
step3(value3)
.then((value4) => {
console.log(value4)
})
})
})
.then
에서 이어받아 사용step1(value1)
.then((value2) => {
return step2(value2)
})
.then((value3) => {
return step3(value3)
})
.then((value4) => {
console.log(value4)
})
Callback 따로 정의하지 않고, Promise 호출한 (외부) 함수가 처리하는것 = Callback 의 역할을 대신
const value2 = await step1(value1)
const value3 = await step2(value2)
const value4 = await step3(value3)
console.log(value4)
const result = **await** caller()
let promise = new Promise(
function caller(resolve, reject) {
const produced = producing()
if (succeeded) resolve(produced)
if (failed) reject(produced)
}
)
// .then(function resolve(produced) { consuming(produced) })
// .catch(function reject(produced) { consuming(produced) })
let fulfilled = promise // Promise {<fulfilled>, '완료!'}
let result = await promise // '완료!'
async
= Promise 상자 포장 (해당 함수의 반환값을 Promise 객체로 포장하여 반환)await
= Promise 상자 개봉 (Promise 객체를 기다렸다가 상자를 열어 내부 값을 반환)async
가 붙은 함수는 반드시 Promise 객체를 반환하고, Promise 가 아닌 것은 Promise 로 감싸 반환return
값을 반환한 경우 : 성공 = Promise.**resolve
객체 반환**throw new Error()
Exception 을 던진 경우 : 실패 = Promise.**reject
객체 반환**return
값을 반환한 경우 : 성공 = Promise.**resolve
객체 반환**async function return_promise() {
return 1 // = Promise.resolve(1) 를 해도 동일한 결과 반환
}
return_promise() // Promise { <fulfilled>: 1 }
await return_promise() // 1
return_promise().then(console.log) // 1
throw new Error()
Exception 을 던진 경우 : 실패 = Promise.**reject
객체 반환**async function throw_promise() {
throw new Error(2)
}
throw_promise() // Promise { <rejected>: Error: 2 at throw_promise ... }
await throw_promise() // Uncaught Error: 2 at throw_promise ...
throw_promise().catch(console.log) // Error: 2 at throw_promise ...
try { await throw_promise() } catch(e) { console.log(e) } // Error: 2 at throw_promise ...
await
키워드를 만나면 Promise 가 처리될 때까지 대기async function returningpromise() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('완료!'), 1000)
})
let result = await promise // 프라미스가 이행될 때까지 대기 (*)
console.log(result) // '완료!'
}
returningpromise()
async function returningpromise() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('완료!'), 1000)
});
let result = promise // 프라미스를 기다리지않고, 프라미스 객체 그대로 반환
console.log(result) // Promise { <pending> }
}
returningpromise()
return
이 아니라 resolve
로 결과 반환return
로 결과 반환하게 되면 아래와 같이 Pending (정상 사용 X)function hello() {
return new Promise((resolve, reject) => {
return 'Seonga' // 정상 사용 X
})
}
const user = hello()
console.log(user)
resolve
로 결과 반환해야 정상 처리 Ofunction hello() {
return new Promise((resolve, reject) => {
resolve('Seonga') // 정상 처리 O
})
}
const user = hello()
console.log(user)
function hello() {
return new Promise((resolve, reject) => {
resolve('Seonga') // O
})
}
const user = hello()
user.then(console.log)
function hello() {
return new Promise((resolve, reject) => {
resolve('Seonga') // O
})
}
const user = await hello()
console.log(user)
async function hello() {
return 'Seonga' // O
}
const user = hello()
user.then(console.log)
async function hello() {
return 'Seonga' // O
}
const user = await hello()
console.log(user)
.catch
에서 처리하기 위해서는 Promise 내 Caller 에서 Reject 로 반환function hello() {
return new Promise((resolve, reject) => {
reject('Seonga')
})
}
const user = hello()
user.catch(console.log) // 'Seonga'
function hello() {
return new Promise((resolve, reject) => {
reject(new Error('Seonga'))
})
}
const user = hello()
user.catch(console.log) // Error: 'Seonga'
function hello() {
return new Promise((resolve, reject) => {
reject('Seonga')
})
}
const user = await hello()
console.log(user) // Uncaught (in promise) Seonga
function hello() {
return new Promise((resolve, reject) => {
reject('Seonga')
})
}
try {
const user = await hello()
console.log(user)
} catch(e) {
console.log(e) // 'Seonga'
}
function hello() {
return new Promise((resolve, reject) => {
reject(new Error('Seonga'))
})
}
const user = await hello()
console.log(user) // Uncaught (in promise) Error: Seonga
function hello() {
return new Promise((resolve, reject) => {
reject(new Error('Seonga'))
})
}
try {
const user = await hello()
console.log(user)
} catch(e) {
console.log(e) // Error: 'Seonga'
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200 || xhr.status === 201) {
console.log(xhr.responseText);
} else {
console.error(xhr.responseText);
}
}
};
xhr.open('GET', 'http://localhost');
xhr.send();
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function (res) { console.log(res); },
error: function (res) {console.log(res); }
});
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
try {
let response = await fetch(url);
let data = await response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
tsconfig.json
파일의 lib
옵션에 esnext
로 표기const object
→ const { name, age } = object
object.
반복해서 사용하지 않음)favor
미사용)// 1. 객체 비구조화
const object = { name: 'Seonga', age: 25, favor: 'Game' }
const { name, age } = object
console.log(object.name) // 👎
console.log(name) // 👍
console.log(age)
const array
→ const [ first, second ] = array
// 2. 배열 비구조화
const array = [ 'Seonga', 'Jeonga', 'Cheonga' ]
const [ first, second ] = array
console.log(array[0]) // 👎
console.log(first) // 👍
console.log(second)
const array = [ 'Seonga', 'Jeonga', 'Cheonga' ]
const [ _, __, third ] = array
console.log(array[2]) // 👎
console.log(third) // 👍
ex ) React - React Component 의 Props 사용 시
<Component value={state} onChange={() => setState(state + 1)} />
function Component({ value, onChange }) {
return <button onClick={(e) => onChange()}>{value}</button>
}
function Component(props) {
return <button onClick={(e) => props.onChange()}>{props.value}</button>
}
ex ) DTO(Data Transfer Object, 함수 간 데이터 전달 시 사용하는 객체) 사용 시
function tooManyArguments(a, b, c, d, e) { ... }
function simplifyMany
const carname = "애스턴마틴 DB11"
const round = 11
const normal = { // 👎
carname: carname,
round: round,
}
const carname = "애스턴마틴 DB11"
const round = 11
const shorten = { // 👍
carname,
round,
}
function parse({ name, age, favor }) {
return { // 👎
name,
favor,
}
}
function parse({ name, age, favor }) {
return { // 👍
name: name,
favor: favor,
}
}
ex ) Function 함수에서 객체 반환 시
const users = [
{ name: 'Seonga', age: 10, favor: 'Game' },
{ name: 'Jeonga', age: 20, favor: 'Soccer' },
]
const parsed = users.map(({ name, age, favor }) => {
return { name, favor }
})
const array = [1, 2, 3]
// array = [1, 2, 3]
// ...array = 1, 2, 3 => 대괄호 [] 제거
const added = [...array, 4, 5]
console.log(added)
/* [ 1, 2, 3, 4, 5 ] */
ex ) 여러 개의 배열을 하나로 합칠 때
const array_1 = [1, 2]
// array_1 = [1, 2]
// ...array_1 = 1, 2
const array_2 = [3, 4]
// array_2 = [3, 4]
// ...array_2 = 3, 4
const combine = [...array1, ...array2]
// combine = [1, 2, 3, 4]
const object = { name: 'Seonga', age: 25 }
// object = { name: 'Seonga', age: 25 }
// ...object = name: 'Seonga', age: 25 => 중괄호 {} 제거
const modified = {...object, name: 'Jeonga'}
console.log(modified)
/* { name: 'Jeonga', age: 25 } */
ex ) React - setState 통한 상태변경 시 = 불변성 보장을 위해
setState
내 주입setState(state)
: state
이전 주소값과 새로 주입하는 객체의 주소값이 동일setState({ ...state, age: state.age + 1 }))
: 새 주소값function Component() {
const [state, setState] = useState({ name: 'Aaron', age: 10 })
return (
<>
<div>{state.name}</div>
<div>{state.age}</div>
<button onClick={() => setState({ ...state, age: state.age + 1 })}></button>
</>
)
}
이거는 사실 안썼을 때 얻을 실익은 없기 때문에, 사실상 안쓰면 바보인 문법
const object = {
name: 'Seonga',
age: 25,
favor: 'Game' // 👎
}
**const object = {
name: 'Seonga',
age: 25,
favor: 'Game', // 👍
}**
const array = [
'Seonga',
'Jeonga',
'Cheonga' // 👎
]
const array = [
'Seonga',
'Jeonga',
'Cheonga', // 👍
]
⛔ Trailing Comma 에 심취해 JSON 파일에서까지 사용하려고 하지말기
const numbers = [ 1, 12, 34, 12, 3, 2, 1423, 1314, 345, 35, 123, 4567, 345 ]
numbers.includes(12)
→ Spread Syntax 에서 배운 개념을 거꾸로 뒤집기
function foo(first, ...rest) { // 1, 2, 3, 4 = ...rest
console.log(rest) // [ 2, 3, 4 ] = rest
console.log(...rest) // 2, 3, 4 = ...rest
}
foo(1, 2, 3, 4) // 2, 3, 4 = ...rest
function create({ name = 'Unnamed', age = 1, favor = undefined }) {
return { name, age, favor }
}
const created_user_1 = create({ name: 'Seonga' })
const created_user_2 = create({ name: 'Jeonga', age: 25 })
const created_user_3 = create({ age: 25, favor: 'Game' })
console.log(created_user_1)
console.log(created_user_2)
console.log(created_user_3)
ex ) React - Component 에서 받지않아도되는 Props 에 기본값 설정
function Component({ value = '', color = 'red', type = 'text' }) {
return (
<input type={type} value={value} style={{ color }} />
)
}
<Component type='password' />
<Component color='blue' />
<Component value='initialied' />
주의 : Map 과 Set 의 경우
jsconfig.json
내lib
옵션에ES2015
을 등록해주어야 사용 가능
const user = { name: 'Seonga', age: 25, favor: 'Game' }
// 👎
console.log('제 이름은 ' + user.name + '이고, 나이는 ' + user.age + '이며, ' + user.favor + '를 좋아합니다.')
const user = { name: 'Seonga', age: 25, favor: 'Game' }
// 👍
console.log(`제 이름은 ${user.name}이고, 나이는 ${user.age}이며, ${user.favor}를 좋아합니다.`)
const initialized = new Map([
[1, 'first'],
[2, 'second'],
[3, 'third'],
['Seonga', { phone: '010-000-0000', address: 'Earth' }],
['Jeonga', { phone: '010-111-1111', address: 'Mars' }],
['Cheonga', { phone: '010-222-2222', address: 'Moon' }],
]);
initialized.set("Seonga", { phone: '010-333-3333', address: 'Jupiter' });
initialized.has("Seonga"); // true
initialized.get("Jeonga"); // undefined
initialized.set("Jeonga", { phone: '010-444-4444', address: 'Venus' });
initialized.get("Seonga"); // { phone: '010-333-3333', address: 'Jupiter' });
initialized.delete("Seongwoo"); // false
initialized.delete("Seonga"); // true
console.log(initialized.size); // 6
initialized.clear()
initialized.forEach(function)
initialized.entries()
initialized.keys()
initialized.values()
배열과 다른점은 Set 은 중복 요소를 허용하지 않는다는 것
const initialized = new Set([ 1, 2, 3, 'Seonga', 'Jeonga' ]);
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' }
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' }
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' } 아무리 추가해도 그대로
initialized.has(1); // true
initialized.has(5); // false
initialized.add({ phone: '010-222-2222', address: 'Moon' });
console.log(initialized.size); // 6
initialized.delete(5); // false
initialized.delete(1); // true
initialized.has(1); // false, 1 은 제거되었습니다.
for .. of
와 for .. in
의 차이 + forEach
for .. of
: Element (요소) ← iterableconst array = [ 'first', 'second', 'third' ]
for (let element of array) {
console.log(element)
// 'first'
// 'second'
// 'third'
}
for .. in
: Key (프로퍼티명) ← enumerableconst array = [ 'first', 'second', 'third' ]
for (let key in array) {
console.log(key)
// '0'
// '1'
// '2'
}
const object = { name: 'Aaron', age: 10, favor: 'Game' }
for (let key in object) {
console.log(key)
// 'name'
// 'age'
// 'Game'
}
.forEach
: Element (요소) ← iterable 다른 방식 | index
가 있어서 순서 활용 가능const array = [ 'first', 'second', 'third' ]
array.forEach((each, index) => {
console.log(`${index + 1}번째 요소는 ${each}`)
// '1번째 요소는 first'
// '2번째 요소는 second'
// '3번째 요소는 third'
})
for (let [ index, each ] of array.entries()) {
console.log(`${index + 1}번째 요소는 ${each}`)
// '1번째 요소는 first'
// '2번째 요소는 second'
// '3번째 요소는 third'
}
const 진짜배열 = [ 'first', 'second', 'third' ]
<const 유사배열 = {
0: 'first',
1: 'second',
2: 'third',
length: 3,
}
console.log(진짜배열[1]) // 'second'
console.log(유사배열[1]) // 'second'
console.log(진짜배열.length) // 3
console.log(유사배열.length) // 3
console.log(진짜배열.includes('first')) // true
console.log(유사배열.includes('first')) // TypeError: 유사배열.includes is not a function
// 바로 위 코드를 보면 알 수 있듯, 유사 배열인 이유는 배열이 갖는 메서드를 유사 배열은 갖지 않기 때문
const 전환배열 = Array.from(object)
Array.isArray(진짜배열) // true <- 배열
Array.isArray(유사배열) // false <- 유사 배열 = 객체
Array.isArray(전환배열) // true <- 배열 (유사 배열로부터 만든)
실습 : API 혹은 유저가 입력한 객체가 Null 혹은 Undefined 일때 내부 프로퍼티 접근 시 에러 방지
object**?.**property
: Null Cascading = object
가 Null 인 경우 property
에게 전달object**!.**property
: Non-null Assertion = object
가 Null 될 수 없음을 개발자가 보증?.
쓰고 Null 처리하시길?.
은 런타임에서 Null 방어가 되지만 !.
은 런타임에서 여전히 문제 발생 (컴파일에서만 해결)let user = { name: 'Aaron', age: 10 }
user = undefined
console.log(user?.name)
let user = { name: 'Aaron', age: 10 }
user = undefined
console.log(user!.name)
실습 : React 에서 정말로 많이 사용하는 ??
+ &&
+ ||
??
: Default 기본값 정의 시 사용&&
: 조건에 맞는 경우 노출할 값 정의 시 사용1 && **1** = **1**
← 앞이 true
이면, 뒤엣것을 표기하는 이걸 노리는 것1 && **0** = **0**
← 앞이 true
이면, 뒤엣것을 표기하는 이걸 노리는 것||
: 조건에 맞지 않는 경우 노출할 값 (에러 혹은 검증메세지) 정의 시 사용0 || **1** = **1**
← 앞이 false
이면, 뒤엣것을 표기하는 이걸 노리는 것0 || **0** = **0**
← 앞이 false
이면, 뒤엣것을 표기하는 이걸 노리는 것undefined ?? '기본값'
true && '선택적 렌더'
false || '에러메세지'
==
은 암시적 타입 변환을 해주어 비교를 해주는데, 편하기보다 예상치 못한 결과를 받기 쉬워 사용하지말 것===
은 어떠한 타입 변환없이 값뿐만 아니라 타입도 동일한지를 비교해주기에, 이것만 사용할 것1 == '1' // Type Coercion 암시적 타입 변환
1 == Number('1') // Type Conversion 명시적 타입 변환
IIFE = Immediately Invoked Function Expression : “함수 정의부 + 함수 호출부” 한줄에 처리하는 것
- Snapshot (딱 한번만 호출) : 재사용성이 있는 함수는 아니지만, 지금 당장 필요할 때
- Function (Module) Encapsulation : 함수 은닉 (Private 화)
(function(parameter) { ... })(argument)
main: ((converted) => (converted.length > 0 ? converted : forModify ? [INITIAL_SKILL] : []))(
(resume?.skills ?? [])
.filter((personalSkill) => personalSkill.main === true)
.map((personalSkill) => ({
id: personalSkill.id,
// main: personalSkill.main,
priority: personalSkill.priority,
stale: personalSkill.stale,
// Skill
skillId: personalSkill.skill.id,
category: personalSkill.skill.category !== null ? personalSkill.skill.category : undefined, // nullable
name: personalSkill.skill.name,
order: personalSkill.order,
})),
), // 빈배열 들어올 경우 초기값 할당
외부에서 호출되지 않길바라는 함수 혹은 모듈 정의 시
((param: number) => {
const privateFunction = (param) => {
return param * 3
}
console.log(privateFunction(param))
})(3)
.map
으로 DB 내 데이터를 컴포넌트에 맞게 표기 및 기본 폼 노출이력서에는 유저가 입력해야할 폼들이 많다. 그 중 회사 정보는 DB 와 입력폼 사이 변경 필요
const companies = [
{ name: 'A Company', start: 2021, years: 3 },
{ name: 'B Company', start: 2019, years: 2 },
]
const converted = companies
.map((company) => ({
name: company.name,
start: company.start,
end: company.start + company.years,
}))
const forms = converted.length === 0
? [{ name: '', start: 2024, years: 0 }]
: converted
다음과 같은 로직을 아래와 같이 변경하면 변수를 여러개쓰지 않고 한번에 해결 가능
const forms = ((converted) =>
converted.length !== 0 ? converted : [{ name: "", start: 2024, years: 0 }])(
companies.map((company) => ({
name: company.name,
start: company.start,
end: company.start + company.years,
}))
);
함수를 반환하는 함수 = 함수를 만드는 메타함수
- 불필요한 반복 파라미터 사용 방지
- 파라미터를 계층화 하기에 뛰어난 방법 = 가독성 및 재사용성 향상
function currying(parameter) {
return () => { ... }
}
enum EVENT_TYPE {
ERROR,
INFO,
}
const threadLogger = (threadNumber: string) => {
return ({ desc, type }: { desc: string, type: EVENT_TYPE }) => {
console.log(`[${threadNumber}] ${type} : ${desc}`)
}
}
const thread1Logger = threadLogger('dsadmkasnf38r0')
const thread2Logger = threadLogger('dch89eddedjlfd')
thread1Logger({ type: EVENT_TYPE.ERROR, desc: 'Not Found' })
thread1Logger({ type: EVENT_TYPE.ERROR, desc: 'Not Found' })
Non-Currying : 커링 미사용 시
function noncurrying(operand, multiple) {
console.log(operand * multiple)
}
// 구구단 2단 시작!
noncurrying(2, 1)
noncurrying(2, 2)
noncurrying(2, 3)
// 구구단 3단 시작!
noncurrying(3, 1)
noncurrying(3, 2)
noncurrying(3, 3)
Currying : 커링 사용 시
function currying(operand) {
return function multiply(multiple) {
console.log(operand * multiple)
}
}
const multiply2 = currying(2)
const multiply3 = currying(3)
// 구구단 2단 시작!
multiply2(1)
multiply2(2)
multiply2(3)
// 구구단 3단 시작!
multiply3(1)
multiply3(2)
multiply3(3)
function closure(parameter) {
const encapsulated = 1
return () => { ... }
}
const visitorCombiner = (id: string, pw: string) => {
const connectedDatabase = SQL.connection(id, pw)
const visitor = 0 // State + Encapsulation
return {
increase: () => { visitor += 1 }
send: () => { connectedDatabase.create(
{ id: id, visitor: visitor })
}
}
}
const createdVisitorCombiner = visitorCombiner('id', 'pw')
/**
* 접근 불가
* - createdVisitorCombiner.visitor
* - createdVisitorCombiner.connectedDatabase.save()
* - createdVisitorCombiner.connectedDatabase.delete()
*/
....onClick(() => {
createdVisitorCombiner.increase()
})
....userSessionExit(() => {
createdVisitorCombiner.send()
})
// 일반적인 함수
function sayHello() {
let hello = 'Hello';
console.log(hello)
}
sayHello() // 'Hello'
함수 내부에 있는 지역 변수는 함수 내부에서만 참조할 수 있기 때문에
일반적인 함수는 실행이 끝나면 내부에 있는 지역변수를 사용 불가
*즉, 함수에 있던 지역변수는 메모리에서 삭제*
(어디서도 참조하고 있지 않은 지역변수는 이후 가비지 컬렉터에 의해 메모리에서 정리)
// 클로저
function makeCounter() {
let count = 0;
return function() {
return ++count
}
}
const increase = makeCounter()
increase() // 1
increase() // 2
하지만 클로저는 외부 함수의 실행이 끝나더라도 내부 함수가 외부 함수의 지역변수를 참조할 수 있기 때문에 내부 함수에서 사용이 가능하므로, 외부 함수에 있던 지역변수는 메모리에서 삭제 불가
(내부 함수에서 외부함수의 지역변수를 참조하고 있기 때문에 가비지 컬렉터에 의해 제거 불가)