soju 스터디 3주차 - JS 기본 개념

thousand_yj·2023년 1월 10일
0

soju 스터디

목록 보기
4/7

#1. JavaScript의 탄생배경

동적인 웹사이트를 만들기 위해 scripting 언어를 추가하게 된 것이 시작.

  • ECMAScript : JS 공식 문서라고 이해하면 편함. 개발자들이 브라우저의 환경에 구애받지 않도록 하기 위해 jQuery가 각광받는 등 Chrome이 등장하면서 여러 브라우저끼리 ECMA6로 다같이 협의를 봤음! 라이브러리의 도움 없이도 모든 웹사이트에서 잘 작동하는 JS를 쓸 수 있게 됨.

  • SPA : 하나의 페이지 안에서 부분적으로 데이터를 받아서 업데이트할 수 있게 해주는 것이 대세일 뿐만 아니라 nodejs도 등장하고~ 점점 js는 강력해짐

#2. async & defer

  • Web api : 브라우저가 이해할 수 있는 함수들. console은 node,js와 브라우저 모두에서 사용 가능한 api

  • Html에서 js를 어떻게 포함하는 것이 더 효율적인가? : Async vs defer

  • head 태그 안에 포함하게 되면

    • js 로딩이 끝나지 않으면 페이지가 로드되지 않음
  • Body 태그 끝에 포함하게 되면

    • 페이지 로드가 완료되고 js fetch를 시작하여 페이지 로드에는 문제가 없으나 만약 화면에 표시되는 데이터가 js 의존적이라면 (서버에서 데이터를 받아오는 등) 사용자가 페이지가 다 로드됬음에도 불구하고 js fetching, executing을 기다려야되는 단점이 있음
  • Head 안에 async 키워드와 함께 포함

    • 병렬로 실행되며, html parsing동안 js fetching하므로 시간적 이점 존재. 하지만 만약 js 안에서 html요소를 조작하는 코드가 포함되어 있는 경우 html 파일이 아직 준비되어 있을 수도 있어 위험. 사용자가 페이지를 보던 중 js executing이 발생할 수 있어 사용자는 여전히 화면을 보던 중 기다림을 느낄 수 있음.
  • head 안에 defer 키워드와 함께 포함

    • 파싱하던 중 키워드를 만나면 js fetching하고, 페이지가 모두 준비되면 js를 실행함. 가장 좋은 옵션.

js 상단에 ‘use strict’; 키워드를 적어두면 조금 더 상식적인 범위 안에서 js를 작성할 수도 있고, js 성능에도 긍정적 영향을 미침!

#3. data type

var vs let & const : 스코프의 차이를 보임.
(var은 문제가 많아 더이상 사용을 권장하지 않음. 호이스팅 문제도 있음)
(호이스팅이란? 선언이 아래에서 위로 끌어올려지는 것)

  • var : 함수 스코프
  • let & const : 블록 스코프
var name = 'yj';
var name2 = 'yj2'; //  문제 X
let name = 'yeji';
let name = 'yeji2'; // 에러 발생

let vs const

  • let : 재할당 가능
  • const : 선언과 동시에 할당. 재할당 불가능

자료형

Primitive type

  • number : 정수, 실수를 모두 포함! (기존 범위보다 더 큰 bigInt라는 자료형이 추가됨)
  • string : 문자, 문자열 모두 포함. "", '', `` 3가지로 감싸는 경우가 해당
  • boolean : true, false (truthy, falsy 개념이 존재하하므로 주의할 것! 가령 "hi"의 경우 true로 null의 경우 false로 해석됨)
  • null : 값을 의도적으로 비워둔 것
  • undefined : 선언만 하고 값을 넣지 않아 그 안이 비어있는 경우
  • symbol : object별 고유한 식별자를 부여하는 자료형

reference type (object type)

  • object : 객체. 다른 언어에서 배열 등 다양한 자료형에 해당하는 부분이 모두 해당. 데이터가 저장되어 있는 주소를 가리킴.

function : 함수

dynamic typing

let text = "hello"; // string
text = 1; // number
text = '7' + 5; // string

js는 실행 과정에서 계속하여 동적으로 자료형을 바꾸는 모습을 보임. 따라서 동적으로 자료형을 바꾸지 못하도록 강제해버리는 TS가 등장했음! (물론 js를 먼저 가고 ts로 넘어가는 것이 정석이니까 그렇게 갑시다)

#4. operator, if, for loop

연산자

  • + 연산자 : 문자열이 등장하는 경우 문자열을 이어붙여줌
  • +, -, *, /, %, ** 연산자 : 숫자에 대하여 연산 실행. (**의 경우 거듭제곱)
  • ++, -- 연산자 : 증감 연산자
  • = 연산자 : 대입
  • >, < 연산자 : 비교
  • 논리 연산자 : &&(and), ||(or), !(not)
    (단축 연산의 개념을 익히고 가자. true || true || complexFn() 코드와 같이 무거운 연산일수록 뒤에 배치하여 실행할 경우를 최소화하는 것이 좋다)
  • ==, === : 느슨한 비교, 엄격한 비교 연산자
    (타입이 다른 경우 자동으로 형변환하여 비교해주는 느슨한 비교 연산자 / 자료형까지 비교해주는 엄걱한 비교 연산자 2가지가 존재. 엄격한 비교 연산자를 애용합시다!)

분기

if (조건) { } else if (조건) { } else { }
(조건) ? (조건이 참일때의 값) : (조건이 거짓일때의 값) // 삼항연산자
switch(변수) { case: ... break; case: ... break; default: ...}

반복문

while(조건) { } // 조건이 참인 경우 계속하여 실행
do { } while(조건) // 한번이라도 먼저 실행하고 싶을 때
for(초기식; 조건식; 증감식) { } // 반복횟수를 명시적으로 적어주면서 반복하는 반복문

반복문 안에 반복문을 넣는 중첩 반복문은 시간복잡도에 제곱 영향을 미치므로 지양하자😥

#5. arrow function

function : sub-program! 여러번 재사용이 가능하다는 장점 😀
(js에서 함수는 object니 주의)

function 이름 (매개변수1, 매개변수2, ...) { ... return; }
const 이름 = function() { }
() => { } 
const 이름 = () => { }

es6에서 매개변수의 defualt 값을 생성할 수 있는 문법 & 가변적 매개변수(rest parameter) 등장

function 이름 (매개변수1, 매개변수2=기본값) { }
function 이름 (매개변수1, ...옵션매개변수) { } 

scope 개념을 한번 더 짚고 넘어가자면, 함수 안과 밖은 완전히 별개의 공간이라고 생각할 것
그렇다면 함수에서 연산한 값을 밖으로 내보내려면? return을 사용하여 함수의 실행결과로 값을 넘겨주면 된다!

early return을 잘 사용하여 함수를 먼저 종료해도 되는 경우 조건에 적절한 return문을 선언할 것

callback function : 함수의 이름을 전달하여 함수를 실행할 권한을 넘겨주자. 함수 안에서 자기 자신을 호출할 수도 있음(재귀함수)

IIFE (Immediately Invoked Function Expression) : 함수를 즉시 실행하는 방법 (잘 사용하지는 않으나 알아는 두자)

(function hi() {console.log("hi");})();

#6. class vs object

class : template, no data in, declare once (설계도)
object : instance of a class, created many times, data in (실제 사물)

js는 프로토타입을 기본으로 하고 있으나 es6에서 클래스가 추가되어 보다 다른 객체지향 언어와 비슷한 양상을 띄게 됨.

클래스는 다음의 포맷으로 설계한다.

class Person{
  //constructor
  constructor(name, age) {
    // fields
    this.name = name;
    this.age = age;
  }
  // methods
  speak() {
    console.log(`${this.name} : hello!`);
  }
}

해당 클래스로 객체를 만들어보자.

const yj = new Person('yj', 27);

다만 직접적으로 fields에 접근하는 것은 객체지향의 캡슐화 개념에 부합하지 않기 때문에 getter, setter를 사용하는 것을 권장한다.

get age() {
  return this._age;
}
set age() {
  this._age = age > 0 ? age : 1;
}
  • 주의할 점은 getter, setter은 내부에서 값을 할당해줄 때 호출되므로 변수명을 동일하게 사용하게 되면 무한정 호출이 일어나 call stack이 꽉 차버리는 상황이 발생할 수 있다. 따라서 변수명을 다르게 설정해줘야 한다. (위의 코드에서는 언더바_를 붙임)

최근에 접근제어 지시자 public, private , 객체를 만들지 않고 클래스만으로 사용 가능하도록 해주는 옵션 static이 추가되었다.

상속, 다형성

클래스에서 다른 클래스를 사용할 수 있도록 해주는 extends
공통된 부분을 하나의 클래스에 정의해둔 뒤 상속받는다. 그리고 추가적으로 기능을 추가하고 싶은 경우 동일한 이름의 함수를 새롭게 정의하면 override를 해주며 다형성을 보장한다.
(동일한 이름의 함수를 재정의하면 부모의 요소를 부르는 것이 아니라 자식의 함수를 호출)

instanceof

어떤 object가 특정 object에 해당되는지 체크할 수 있는 방법

#7. object란?

const obj1 = {};
const obj2 = new Object();

const obj = { key1 : value1 };
obj.key2 = value2; // 동적으로 키 생성
delete obj.key2; // 동적으로 키 삭제

console.log(obj.key1); 
console.log(obj['key1']; // computed property. 동적으로 key의 값을 넣어줘야할 때 사용하자

property value shorthand & constructor function

function newPerson(name, age){
  return {
    name, // name : name 에서 값을 넣어주는 부분을 생략할 수 있다
    age
  }
}

function Person(name, age){
  // this = {}; // 생략. js 엔진이 알아서 처리
  this.name = name;
  this.age = age;
  // return this; // 생략. js 엔진이 알아서 처리
}

const yj = Person("yj", 24); // constructor 처럼 사용 가능!

in operator

key가 object 안에 존재하는지 체크

for... in / for ...of

for (key in obj){
  console.log(key);
}

// 순차적으로 데이터에 접근할 때 for of 사용
const arr = [1,2,3];
for(value of arr){
  console.log(value);
}

Fun cloning

= 방식으로 단순히 객체를 할당해주면 동일한 주소지를 갖고 있으므로 객체 값이 복사가 되지 않는다. 따라서 객체를 제대로 복사해주는(deep copy)해주는 방법은 다음과 같다.

// old way
const user = { name: "yj", age: '20' };

const user2 = {};
for(key in user){
  user2[key] = user[key];
}

// Object.assign 사용
const user3 = Object.assign({}, user); // 함수의 원형은 MDN 공식문서 참고하기

만약 Object.assign에 여러개의 객체를 전달하게 되면 뒤에 등장한 객체일수록 우선순위가 높다(값이 덮어씌워질 때 뒤의 값으로 덮어씌워짐)

#8. Array, API

연속된 데이터를 저장할 수 있는 자료구조 array는 js에서 object 안에 정의되어 있다.

const fruits = ['apple', 'banana'];
console.log(fruits.length);
console.log(fruits.[0]); // 인덱스 값으로 접근

배열 내 요소를 반복문을 통해 접근할 수 있는 다양한 방법들에 대해 알아보자. forEach가 개인적으로 제일 편하다 😄

// for문
for(let i=0; i<fruits.length; i++){
  console.log(fruits[i]);
}

// for of 문
for(let item of fruits){
  console.log(item);
}

// forEach문. callback Fn을 전달받아 배열 내 각 요소마다 실행
fruits.forEach((item) => console.log(item);); 

배열에 데이터를 넣고 빼고 조작하는 방법 : push, pop, unshift, shift, splice, concat, indexOf, includes

fruits.push(item) // 맨 뒤에 데이터 추가
fruits.pop() // 맨 뒤의 데이터 뿅
fruits.unshift(item) // 맨 앞에 데이터 추가 
fruits.shift() // 맨 앞의 데이터 뿅 -> shift, unshift는 배열의 길이에 속도 영향받음!

fruits.splice(1, 1); // 인덱스를 지정하여 데이터 제거. 시작 인덱스, 끝 인덱스
fruits.splice(1, 1, 'melon'); // 지워진 자리에 새로운 데이터 삽입!

const newFruits = fruits.concat(['watermelon']); // 전달받은 데이터와 합쳐 새로운 데이터를 반환

fruits.indexOf('melon'); // 인덱스 반환
fruits.includes('kiwi'); // 값의 유무 반환

#9. 유용한 배열 함수들

  • join : 구분자를 갖고 배열 내의 요소를 하나의 문자열로 반환. 구분자 생략 시 콤마로 자동 삽입
    fruits.join("");
  • string.split() : 문자열 -> 배열로 변환. string 객체 내의 내장 메서드
  • reverse : 원본 배열 뒤집은 뒤 그 결과 반환 (원본을 건드림!)
  • slice(start, end) : start부터 end 이전까지의 요소를 갖는 새로운 배열 반환
  • find(()=>{}) : 각각의 요소에 콜백함수를 실행하여 리턴값이 참인 첫번째 요소를 반환
  • filter(()=>{}): 콜백함수의 리턴값이 참인 요소만 갖는 배열 반환
  • map(()=>{}) : 콜백함수를 호출하며 각각의 요소에 콜백함수를 실행한 결과 기반의 새로운 배열 만들어 반환
  • some(()=>{}) : 콜백함수를 실행하여 참인 요소가 있는지 없는지 반환 (boolean) 배열 안에 조건을 만족하는 값이 있는지 체크할 때 유용!
  • every(()=>{}) : 콜백함수의 실행결과가 모두 참인지 그 여부를 반환 (boolean)
  • reduce((accum, curr)=>{}, init) : reduce 함수에는 두개의 파라미터를 전달. 첫번째 파라미터는 accumulator 와 current 를 파라미터로 가져와서 결과를 반환하는 콜백함수이며, 두번째 파라미터(init)는 reduce 함수에서 사용 할 초깃값. 여기서 accumulator 는 누적된 값을 의미.
    • reduce는 잘 사용하면 굉장히 유용한 함수이나 설명만 보면 약간 어렵다... 직접 써봐야 익숙해지니 자주 사용해보자.

#10. JSON. 서버 통신의 시작

브라우저와 서버가 통신을 하는 과정에서 데이터를 주고받을 때, 그 데이터의 형식이 바로 json이다. key-value 형태로 전달되며 프로그래밍 언어, 브라우저에 제한을 받지 않는다는 강력한 특징이 있다!

js 내에서 object <-> json 변환을 어떻게 하는지 알아보자.

// object -> json : JSON.stringify()
let json = JSON.stringify(true);
json = JSON.stringify({name: 'yj'});
json = JSON.stringify(['yj', '20']); 

오버로딩이 많이 되어 있으니 사용하기 전에 한번 더 stringify 공식문서를 참고하자!
object 전달 시 함수는 변환되지 않는다.

// json -> object : JSON.parse()
const obj = JSON.parse(json);

변환되는 데이터는 모두 문자열이므로 변환 과정에서 자료형을 변환해야 하는 경우 콜백함수를 사용하자,

유용한 사이트들
https://jsondiff.com/
https://jsonbeautifier.org/
https://jsonparser.org/
https://tools.learningcontainer.com/json-validator/

#11. 비동기 처리의 시작. callback hell🤮

JS는 기본적으로 동기적인 언어다. 즉 정해준 순서에 맞게 코드가 나타나는 순서대로 실행이 된다. 비동기를 이해하려면 setTimeout 예제가 제일 좋다고 생각한다.

setTimeout(()=> console.log("hi"), 1000);

1초 뒤에 hi라는 문자열이 출력된다. 브라우저에게 1초 뒤에 콜백함수를 출력하라고 전달하는 전형적인 비동기 로직이 들어간 코드다. 비동기 처리 과정에서 콜백함수가 중첩되고 호출되는 지옥의 상황이 은근 흔하다.... 물론 이런 지옥의 과정에서 구원해줄 async & await 구원자가 es8에서 등장하긴 했다!

#12. Promise란?

Promise 객체 만들기

비동기 상황을 값으로 표현한다. (pending / fulfilled / rejected 상황 중 하나)

  • pending : reject, resolve 되지 않고 여전히 promise 생성자 로직이 실행중인 상황
  • fulfilled : resolve가 호출되어 값을 넘길 수 있는 상태. then 사용이 가능
  • rejected : reject가 호출되어 값을 넘길 수 있는 상태. Error 객체를 넘겨주는 경우가 많다. catch 사용이 가능
const promise = new Promise(resolve, reject) => {
  // .. 실행할 코드
  // 성공한 경우
  resolve(value);
  // 실패한 경우
  reject(new Error("에러 발생!"));
}

결과 처리하기

Promise의 결과를 가지고 처리하는 방법은 3가지다. then, catch, finally를 사용할 수 있다.

  • then : 가장 기본으로 2가지의 함수를 넘길 수 있다. 먼저 넘겨주는 함수는 성공 시 실행하며 두번째 함수는 실패 시 실행한다. 콜백함수 1개만 넘겨주는 경우 성공 시 실행하는 함수만 넘겨받는 것! 또한 then의 반환 결과는 Promise 객체이다. 따라서 chaining 이 가능하다.

    promise.then( (resolve)=> {}, (reject)=>{});
  • catch : 에러가 발생한 경우만 다루고 싶을 때. then(null, callbackFn)과 동일

    promise.catch( (reject)=> {});
  • finally : 결과가 어떻든 마무리가 필요할 때. 성공 실패 여부에 관심이 없으므로 전달받는 요소가 없다. 자동으로 다음 핸들러에 결과, 에러를 전달.

    promise.finally(()=>{}).then(......);

체이닝

then 함수가 Promise 객체를 반환하기 때문에 계속하여 then 메서드를 호출할 수 있다.

promise
  .then((resolve)=> {})
  .catch((reject)=> {})
  .then((resolve)=> {})
  .then((resolve)=> {})
  .catch((reject)=> {});

중간에 에러 핸들링이 필요한 경우에는 적절한 위치에 catch 메서드를 넣어주자!

#13. Async & Await

비동기 처리의 꽃✨

Async

  • async 키워드를 함수 앞에 붙여주면 자동으로 Promise 객체를 반환하도록 설정이 된다.
async function fetchUser() {
  // ... 뭔가 서버로부터 받아오는 로직 코드 ...
  return 'yj';
}

Await

  • await 키워드는 async 함수 안에서만 사용이 가능하다. 해당 Promise 객체가 종료될 때까지 기다리도록 강제하는 키워드.
function delay(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple(){
  await delay(2000);
  return 'apple';
}

에러 핸들링해줄 때도 try catch문을 사용하여 해줄 수 있다.

Promise 객체가 실행하는 과정에서 서로 영향을 미치지 않아 병렬로 수행해도 되는 경우에는 Promise.all() API를 사용하자. all 키워드는 배열로 Promise 객체를 전달받아 한꺼번에 실행할 수 있다.

Promise.all([promise1, promise2]).then([r1, r2]) => {...});

spread, rest 문법

ES6 에서 도입된 spread 와 rest 문법은 외형이 동일하나 동작이 다르다. 이번 기회에 정리해둬야지.

spread

const slime = {
  name: '슬라임'
};

const cuteSlime = {
  name: '슬라임',
  attribute: 'cute'
};

const purpleCuteSlime = {
  name: '슬라임',
  attribute: 'cute',
  color: 'purple'
};

// spread 문법 사용
const slime = {
  name: '슬라임'
};

const cuteSlime = {
  ...slime,
  attribute: 'cute'
};

const purpleCuteSlime = {
  ...cuteSlime,
  color: 'purple'
};

// 배열에서의 spread 문법 사용
const animals = ['개', '고양이', '참새'];
const anotherAnimals = [...animals, '비둘기'];

...(spread 문법)은 기존의 것을 건드리 않고, 새로운 객체 및 배열을 만들 수 있다.

rest

rest는 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능하다.

// 객체에서 rest 사용
const purpleCuteSlime = {
  name: '슬라임',
  attribute: 'cute',
  color: 'purple'
};

const { color, ...rest } = purpleCuteSlime;

// 배열에서 rest 사용
const numbers = [0, 1, 2, 3, 4, 5, 6];

const [one, ...rest] = numbers;

// 함수 파라미터에서 rest 사용
function sum(...rest) {
  return rest;
}

함수의 경우 arguments 객체(유사 배열 객체)를 사용했던 것과 비슷하다. 다만 필수적으로 전달해야되는 매개변수가 존재하고 그 외의 요소를 옵션으로 전달받아야 되는 상황이라면 다음과 같이 rest parameter를 쓰는 것이 더 좋겠다.

function sum(operator, ...rest) {
  // ...
  return result;
}

operator 매개변수는 반드시 전달해줘야 하는 문자열이고 그 외의 숫자나 데이터는 옵션으로 전달받는 상황이다. 위와 같이 코드를 작성해주면 명료하게 함수의 매개변수를 통해 작동 방향을 암시할 수 있다.

단축 평가

매우 유용한 기능이어 추가로 정리해보려고 한다. 논리 연산자를 보다 똑똑하게 사용하는 방법이다. 논리 연산자를 사용 할 때에는 무조건 true 혹은 false 값을 사용해야 되는 것은 아니다. 문자열이나 숫자, 객체를 사용 할 수도 있고, 해당 값이 Truthy 하냐 Falsy 하냐에 따라 결과가 달라지는 것을 이용하여 코드를 짧게 단축시킬 수 있다.

function render() {
  return condition1 && condition2 && `<div>Hello</div>`;
}

위의 코드에서 true 자리에 렌더링하는 조건이 부여되고 condition1, condition2의 결과가 참이라고 가정하자. 그러면 두 조건이 모두 참이기 때문에 가장 마지막에 위치한 문자열이 리턴된다. 위와 같이 논리 연산자를 사용하지 않았다면 swtichif를 사용하여 코드가 길어졌을 것이다.

&&||연산자를 사용한 단축 평가를 통해 코드를 보다 간결하게 작성하자 😎

profile
함께 일하고 싶은 개발자가 되기 위해 노력합니다. 코딩테스트 관련 공부 및 이야기는 티스토리에도 업로드되어 있습니다.

0개의 댓글