모각코/2024 동계 [뉴진주택]

[뉴진주택] 2. 알아두어야 할 자바스크립트

Su_Do 2023. 12. 28. 19:00

2.1 호출 스택, 이벤트 루프

· 이벤트 루프 : 이벤트 발생(setTimeout 등)시 호출할 콜백 함수들(위의 예제에서는 run)을 관리하고, 호출할 순서를 결정하는 역할

·  백그라운드 : 타이머나 I/O 작업 콜백, 이벤트 리스너들이 대기하는 공간. 여러 작업이 동시에 실행될 수 있음

·  태스크 큐 : 이벤트 발생 후 호출되어야 할 콜백 함수들이 순서대로 기다리는 공간

 

이벤트 루프가 호출 스택이 비워져 있을 때, 태스크 큐의 콜백을 호출 스택으로 올림

 

 

 

2.2 ES2015+

2.2.1 const, let

ES2015 이전에는 var로 변수를 선언

=> ES2015부터는 const와 let이 대체

var const let
함수 스코프
(함수 안에서 선언시 밖에서 사용 x)
블록 스코프
(함수 및 블록에서 선언시 밖에서 사용 x)
재선언 o, 재할당 o 재선언 x, 재할당 x 재선언 x, 재할당 o
if (true) {
  var x = 3;
}
console.log(x); // 3

if (true) {
  const y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined

2.2.2 템플릿 문자열

문자열을 합칠 때 + 기호 때문에 지저분함

=> ES2015부터는 `(백틱) 사용 가능. 백틱 문자열 안에 ${변수}처럼 사용

var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \'' + result + '\'';
console.log(string1); // 1 더하기 2는 '3'

// ES2015
const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}는 '${result2}'`;
console.log(string2); // 1 더하기 2는 '3'

2.2.3 객체 리터럴

훨씬 간결한 문법으로 객체 리터럴 표현 가능

· 객체의 메서드에 :function을 붙이지 않아도 됨

· { sayNode: sayNode }와 같은 것을 { sayNode }로 축약 가능

· [변수 + 값] 등으로 동적 속성명을 객체 속성 명으로 사용 가능

var sayNode = function() {
  console.log('Node');
};
var es = 'ES';
var oldObject = {
  sayJS: function() {
    console.log('JS');
  },
  sayNode: sayNode,
};
oldObject[es + 6] = 'Fantastic';
oldObject.sayNode(); // Node
oldObject.sayJS(); // JS
console.log(oldObject.ES6); // Fantastic

//ES2015
const newObject = {
  sayJS() {
    console.log('JS');
  },
  sayNode,
  [es + 6]: 'Fantastic',
};
newObject.sayNode(); // Node
newObject.sayJS(); // JS
console.log(newObject.ES6); // Fantastic

2.2.4 화살표 함수

function add1(x, y) {
  return x + y;
}

// add1을 화살표 함수로 나타낼 수 있음
const add2 = (x, y) => {
  return x + y;
};

// 함수의 본문이 return만 있는 경우 return 생략
const add3 = (x, y) => x + y;

// return이 생략된 함수의 본문을 소괄호로 감싸줄 수 있음
const add4 = (x, y) => (x + y);

// not1과 not2도 같은 기능을 함 (매개변수 하나일 때 괄호 생략)
function not1(x) {
  return !x;
}

const not2 = x => !x;

 

화살표 함수가 기존 function() {}을 대체하는 건 아님 (this가 달라짐)

· logFriends 메서드의 this 값에 주목

· forEach의 function의 this와 logFriends의 this는 다름

· that이라는 중간 변수를 이용해서 logFriends의 this를 전달

var relationship1 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends: function() {
    var that = this; // relationship1을 가리키는 this를 that에 저장
    this.friends.forEach(function (friend) {
      console.log(that.name, friend);
    });
  },
};
relationship1.logFriends();
/* 출력
  zero nero
  zero hero
  zero xero
*/

 

forEach의 인자로 화살표 함수가 들어간 것에 주목

· forEach의 화살표함수의 this와 logFriends의 this가 같아짐

· 화살표 함수는 자신을 포함하는 함수의 this를 물려받음

· 물려받고 싶지 않을 때 : function() {}을 사용

// ES2015
const relationship2 = {
  name: 'zero',
  friends: ['nero', 'hero', 'xero'],
  logFriends() {
    this.friends.forEach(friend => { // 화살표 함수를 쓰면 부모의 this를 물려받음
      console.log(this.name, friend); // this.friends의 this == this.name의 this
    });
  },
};
relationship2.logFriends();
/* 출력
  zero nero
  zero hero
  zero xero
*/

2.2.5 구조분해 할당

const example = { a: 123, b: { c: 135, d: 146 } };
const a = example.a;
const d = example.b.d;

// 구조분해 할당
const { a, b: { d } } = example;
console.log(a); // 123
console.log(d); // 146

·  const { 변수 } = 객체; 로 객체 안의 속성을 변수명으로 사용 가능

arr = [1, 2, 3, 4, 5];
const x = arr[0];
const y = arr[1];
const z = arr[4];

// 구조분해 할당
const [x, y, , , z] == arr;

·  const [변수] = 배열; 각 배열 인덱스와 변수가 대응됨

var candyMachine = {
  status: {
    name: 'node',
    count: 5,
  },
  getCandy: function() {
    this.status.count--;
    return this.status.count;
  },
};
// var getCandy, var count는 candyMachine부터 시작해서 속성을 찾아 들어가야 함
var getCandy = candyMachine.getCandy;
var count = candymachine.status.count;

// 구조분해 할당 (this가 있을 때는 안 하는게 좋음)
const { getCandy, status: { count } } = candyMachine;
// count처럼 속성 안의 속성도 변수명으로 사용 가능
// 단, getCandy()를 실행했을 때 결과가 candyMachine.getCandy()와는 달라짐 (this를 사용했기 때문)

2.2.6 클래스

프로토타입 문법을 깔끔하게 작성할 수 있는 Class 문법 도입

=> Constructor(생성자), Extends(상속) 등을 깔끔하게 처리할 수 있음

=> 코드가 그룹화되어 가독성이 향상됨

// 이전

// 생성자 함수
var Human = function(type) {
  this.type = type || 'human';
};

// static 메소드
Human.isHuman = function(human) {
  return human instanceof Human;
}

// instance 메소드
Human.prototype.breathe = function() {
  alert('h-a-a-a-m');
};

// 상속받는 과정
var Zero = function(type, firstName, lastName) {
  Human.apply(this, arguments);
  this.firstName = firstName;
  this.lastName = lastName;
};

Zero.prototype = Object.create(Human.prototype);
Zero.prototype.constructor = Zero; // 상속하는 부분
Zero.prototype.sayName = function() {
  alert(this.firstName + ' ' + this.lastName);
};
var oldZero = new Zero('human', 'Zero', 'Cho');
Human.isHuman(oldZero); // true
// 이후

class Human {
  // 생성자
  constructor(type = 'human') {
    this.type = type;
  }
  
  // static 메소드
  static isHuman(human) {
    return human instanceof Human;
  }
  
  // instance 메소드
  breathe() {
    alert('h-a-a-a-m');
  }
}

// 상속받는 과정
class Zero extends Human {
  constructor(type, firstName, lastName) {
    super(type); // super로 부모의 생성자 호출
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  sayName() {
    super.breathe(); // super로 부모의 breathe() 호출
    alert(`${this.firstName} ${this.lastName}`);
  }
}

const newZero = new Zero('human', 'Zero', 'Cho');
Human.isHuman(newZero); // true

2.2.7 프로미스

콜백 헬이라고 불리는 지저분한 자바 스크립트 코드의 해결책

콜백 헬

 

프로미스 : 내용이 실행은 되었지만 결과를 아직 반환하지 않은 객체

· then을 붙이면 결과를 반환함

· 실행이 완료되지 않았으면 완료된 후 then 내부 함수가 실행됨

 

resolve(성공 리턴값) => then으로 연결

reject(실패 리턴값) => catch로 연결

finally 부분은 무조건 실행됨

const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, rejcet) => {
  if (condition) {
    resolve('성공');
  } else {
    reject('실패');
  }
});
// 다른 코드가 들어갈 수 있음
promise
  .then((message) => {
    console.log(message); // 성공(resolve)한 경우 실행
  })
  .catch((error) => {
    console.error(error); // 실패(reject)한 경우 실행
  })
  .finally(() => { // 끝나고 무조건 실행
    console.log('무조건');
  });

 

프로미스의 then 연달아 사용 가능 (프로미스 체이닝)

· then 안에서 return 한 값이 다음 then으로 넘어감

· return 값이 프로미스면 resolve 후 넘어감

· 에러가 난 경우 바로 catch로 이동

· 에러는 catch에서 한 번에 처리

promise
  .then((message) => {
    return new Promise((resolve, reject) => {
      resolve(message);
    });
  })
  .then((message2) => {
    console.log(message2);
    return new Promise((resolve, reject) => {
      resolve(message2);
    });
  })
  .then((message3) => {
    console.log(message3);
  })
  .catch((error) => {
    console.error(error);
  });

 

콜백 패턴(3중첩)을 프로미스로 바꾸는 예제

// 이전

function findAndSaveUser(Users) {
  Users.findOne({}, (err, user) => { // 첫 번째 콜백
    if (err) {
      return console.error(err);
    }
    user.name = 'zero';
    user.save((err) => { // 두 번째 콜백
      if (err) {
        return console.error(err);
      }
      Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백
        // 생략
      });
    });
  });
}

// 이후
// findOne, save 메소드가 프로미스를 지원한다고 가정

function findAndSaveUser(Users) {
  Users.findOne({})
    .then((user) => {
      user.name = 'zero';
      return user.save();
    })
    .then((user) => {
      return Users.findOne({ gender: 'm' });
    })
    .catch(err => {
      console.error(err);
    });
  }

 

Promise.resolve(성공 리턴값) : 바로 resolve 하는 프로미스

Promise.reject(실패 리턴값) : 바로 reject 하는 프로미스

Promise.all(배열) : 여러 개의 프로미스를 동시에 실행

· 하나라도 실패하면 catch로 감

· allSettled로 실패한 것만 추려낼 수 있음

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
  .then((result) => {
    console.log(result); // ['성공1', '성공2']
  })
  .catch((error) => {
    console.error(error);
  });

2.2.8 async / await

프로미스 패턴 코드를 async / await으로 한 번 더 축약 가능

· 변수 = await 프로미스; 인 경우 프로미스가 resolve된 값이 변수에 저장

· 변수 await 값; 인 경우 그 값이 변수에 저장

// 이전

function findAndSaveUser(Users) {
  Users.findOne({})
    .then((user) => {
      user.name = 'zero';
      return user.save();
    })
    .then((user) => {
      return Users.findOne({ gender: 'm' });
    })
    .catch(err => {
      console.error(err);
    });
  }
  
// 이후
  
async function findAndSaveUser(Users) {
  try {
    let user = await Users.findOne({});
    user.name = 'zero';
    user = await user.save();
    user = await Users.findeOne({ gender: 'm' });
    // 생략
  } catch (error) {
    console.error(error);
  }
}

 

화살표 함수도 async / await 가능

const findAndSaveUser = async (Users) => {
  try {
    let user = await Users.findOne({});
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender: 'm' });
    // 생략
  } catch (error) {
    console.error(error);
  }
};

 

async 함수는 항상 promise를 반환(return)

=> then이나 await을 붙일 수 있음

async function findAndSaveUser(Users) {
  // 생략
}
findAndSaveUser().then(() => { /* 생략 */ });
// 또는
async function other() {
  const result = await findAndSaveUser();
}

 

for await (변수 of 프로미스 배열)

· 노드 10부터 지원

· resolve 된 프로미스가 변수에 담겨 나옴

· await을 사용하기 때문에 async 함수 안에서 해야 함

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
  for await (promise of [promise1, promise2]) {
    console.log(promise);
  }
})();

2.2.9 Map / Set

Map은 객체와 유사한 자료구조

const m = new Map();

m.set('a', 'b'); // set(키, 값)으로 Map에 속성 추가
m.set(3, 'c'); // 문자열이 아닌 값을 키로 사용 가능
const d = {};
m.set(d, 'e'); // 객체도 키로 사용 가능

m.get(d); // get(키)로 속성값 조회
console.log(m.get(d)); // e

m.size; // size로 속성 개수 조회
console.log(m.size); // 3

for (const [k, v] of m) { // 반복문에 바로 넣어 사용 가능
  console.log(k, v); // 'a', 'b', 3, 'c', {}, 'e'
} // 속성 간의 순서 보장

m.forEach((v, k) => { // forEach도 사용 가능
  console.log(k, v); // 'a', 'b', 3, 'c', {}, 'e'
});

m.has(d); // has(키)로 속성 존재 여부 확인
console.log(m.has(d)); // true

m.delete(d); // delete(키)로 속성 삭제
m.clear(); // clear()로 전부 제거
console.log(m.size); // 0

 

Set은 배열과 유사한 자료구조 (기존 배열의 중복을 제거할 때도 사용)

const s = new Set();
s.add(false); // add(요소)로 Set에 추가
s.add(1);
s.add('1');
s.add(1); // 중복이므로 무시
s.add(2);

console.log(s.size); // 4

s.has(1); // has(요소)로 요소 존재 여부 확인
console.log(s.has(1)); // true

for (const a of s) {
  console.log(a); // false 1 '1' 2
}

s.forEach((a) => {
  console.log(a); // false 1 '1' 2
})

s.delete(2); // delete(요소)로 요소 제거
s.clear(); // clear()로 전부 제거

const arr = [1, 3, 2, 7, 2, 6, 3, 5];

const s = new Set(arr);
const result = Array.from(s);
console.log(result); // 1, 3, 2, 7, 6, 5

2.2.10 널 병합, 옵셔널 체이닝

??(널 병합, nullish coalescing) 연산자

: || 대용으로 사용되며, falsy 값 중 null과 undefined만 따로 구분함

const a = 0;
const b = a || 3; // || 연산자는 falsy 값이면 뒤로 넘어감
console.log(b); // 3

const c = 0;
const d = c ?? 3; // ?? 연산자는 null과 undefined일 때만 뒤로 넘어감
console.log(d); // 0

const e = null;
const f = e ?? 3;
console.log(f); // 3

const g = undefined;
const h = g ?? 3;
console.log(h); // 3

 

?.(옵셔널 체이닝, optional chaining) 연산자

: null이나 undefined의 속성을 조회하는 경우 에러가 발생하는 것을 막음

const a = {}
a.b; // a가 객체이므로 문제 없음

const c = null;
try {
  c.d;
} catche (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading 'd')
}
c?.d; // 문제 없음

try {
  c.f();
} catch (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading 'f')
}
c?.f(); // 문제 없음

try {
  c[0];
} catche (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading '0')
}
c?.[0]; // 문제 없음

 

2.3 프런트엔드 자바스크립트

2.3.1 AJAX

서버로 요청을 보내는 코드

· 라이브러리 없이는 브라우저가 지원하는 XMLHttpRequest 객체 이용

· AJAX 요청 시 axios 라이브러리를 사용하는게 편함

· HTML에 아래 스크립트를 추가하면 사용할 수 있음

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
  // 여기에 예제 코드
</script>

 

GET 요청 보내기

· axios.get 함수의 인수로 요청을 보낼 주소를 넣으면 됨

· 프로미스 기반 코드라 async / await 사용 가능

axios.get('https://www.zerocho.com/api/get')
  .then((result) => {
    console.log(result);
    console.log(result.data); // {}
  })
  .catch((error) => {
    console.error(error);
  });

// async / await 사용
(async () => {
  try {
    const result = await axios.get('https://www.zerocho.com/api/get');
    console.log(result);
    console.log(result.data); // {}
  } catch (error) {
    console.error(error);
  }
})();

 

POST 요청을 하는 코드(데이터를 담아 서버로 보내는 경우)

· 전체적인 구조는 비슷하나 두 번째 인수로 데이터를 넣어 보냄

(async () => {
  try {
    const result = await axios.get('https://www.zerocho.com/api/get', {
      name: 'zerocho',
      birth: 1994,
    });
    console.log(result);
    console.log(result.data); // {}
  } catch (error) {
    console.error(error);
  }
})();

2.3.2 FormData

HTML form 태그에 담긴 데이터를 AJAX 요청으로 보내고 싶은 경우 사용

const formData = new FormData();
formData.append('name', 'zerocho'); // append로 데이터를 하나씩 추가
formData.append('item', 'orange');
formData.append('item', 'melon');
formData.has('item'); // true (has로 데이터 존재 여부 확인)
formDate.has('money'); // false
formData.get('item'); // orange (get으로 데이터 조회)
formData.getAll('item'); // ['orange', 'melon'] (getAll로 데이터 모두 조회)
formData.append('test', ['hi', 'zero']);
formData.get('test'); // hi, zero
formData.delete('test'); // delete로 데이터 삭제
formData.get('test'); // null
formData.set('item', 'apple'); // set으로 데이터 수정
formData.getAll('item'); // ['apple']

 

FormData POST 요청으로 보내기

· axios의 data 자리에 formData를 넣어서 보내면 됨

(async () => {
  try {
    const formData = new FormData();
    formData.append('name', 'zerocho');
    formData.append('birth', 1994);
    const result = await axios.get('https://www.zerocho.com/api/get', formData);
    console.log(result);
    console.log(result.data);
  } catch (error) {
    console.error(error);
  }
})();

2.3.3 encodeURIComponent, decodeURIComponent

주소창에 한글 입력하면 서버가 처리하지 못하는 경우 발생

=> encodeURIComponent로 한글 감싸줘서 처리

(async () => {
  try {
    const result = await axios.get(`https://www.zerocho.com/api/search/
    ${encodeURIComponent('노드')}`;
    console.log(result);
    console.log(result.data); // {}
  } catch (error) {
    console.error(error);
  }
})();

 

노드를 encodeURIComponent하면 %EB%86%B8%EB%93%9C가 됨

=> decodeURIComponent로 서버에서 한글 해석

decodeURIComponent('%EB%86%B8%EB%93%9C'); // 노드

2.3.4 data attribute와 dataset

HTML 태그에 데이터를 저장하는 방법

· 서버의 데이터를 프런트엔드로 내려줄 때 사용

· 태그 속성으로 data-속성명

· 자바스크립트에서 태그.datastet.속성명으로 접근 가능

    · data-user-job => dataset.userJob

    · data-id => dataset.id

· 반대로 자바스크립트 dataset에 값을 넣으면 data-속성이 생김

    · dataset.monthSalary = 10000 => data-month-salary="10000"

<ul>
  <li data-id="1" data-user-job="programmer">Zero<li>
  <li data-id="2" data-user-job="designer">Nero<li>
  <li data-id="3" data-user-job="programmer">Hero<li>
  <li data-id="4" data-user-job="ceo">Kero<li>
</ul>
<script>
  console.log(document.querySelector('li').dataset);
  // { id: '1', userJob: 'programmer' }
</script>