Frontend Study

[FE Bootcamp] 15일차_Javascript Koans

킹경후 2023. 3. 6. 11:38

오늘은 그동안 배웠던 자바스크립트의 문법에 대해 한번 쭉 복습하며 문제를 푸는 시간을 가졌다.

Koans는 한국말로 '간화선' 이라 하는데, 화두의 진의를 의심으로 궁구하며 살피는 선 수행 방법 이라고 한다.

쉽게 말하자면, 끝없는 의심을 통해 진리를 깨우치는 것이다.

나는 불교 학교를 나왔지만 처음 들어보는,.,,.불교 수업 열심히 들을걸

 

여튼, 한번 배웠던 내용이라도 다시 한번 생각해보며 이게 왜 답인지 고민해보는 과정을 통해 완벽히 이해하라는 의미로 주어진 문제인 듯 하였다.

약 50여개의 문제가 있었는데, 그중에서 헷갈렸거나 이건 몰랐네! 싶었던 문제들을 몇개 소개해 볼까 한다.

기존 문제는 복잡한 모듈들을 사용해 정답을 판별했기 때문에, 문제를 흔히 아는 방식으로 쉽게 바꾸어 적도록 하겠다.

 

1. Type

console.log(1+'1')
//ans = '11'
console.log(123-'1')
//ans = 123
console.log(1+true)
//ans = 2
console.log('1'+true)
//ans = '1true'

 

시작하자마자 만난 난관.  다른 타입간의 사칙연산이었다.

결론부터 말하자면, 덧셈은 string > number > boolean의 우선 순위를 가진다.

타입이 어떻든 string을 더하면 출력은 무조건 string이 된다는 것이다. 

반대로 뺄셈은 무조건 number로 출력된다. 

boolean의 경우 true = 1, false = 0이라는 값을 가진다. 하지만 문자열과 덧셈을 할 경우 문자 true, false로 변환된다는 것을 알아야 한다.

 

 

2. Scope

 function defaultParameter(num = 5) {
  return num;
}

console.log(defaultParameter())
//ans = 5
console.log(defaultParameter(10))
//ans = 10

 

디폴트 파라미터에 관한 문제였다.

매개변수가 정의되어 있을 때, 전달인자에 값이 들어간다면 어떻게 되는지 판단하는 문제였다.

나는 당연히 매개변수가 정의되어 있으므로, 전달인자의 값에 상관없이 정의된 매개변수의 값을 따라가는줄 알았으나,..,,.

 

디폴트 파라미터는 '매개변수는 필요하나 전달인자가 없을 때 사용할 기본 값'이라고 생각하면 될 것 같다.

쉽게 말해, 전달인자가 무조건 저 값으로 바뀌는 것이 아니라, 전달인자가 없을 때 대신 전달해주는 값인 것이다.

오오 아예 모르고 있던 개념이라 뭔가 신기하기도 하면서 갈 길의 거리를 뼈저리게 느낀,.,..,

 

function pushNum(num, arr = []) {
  arr.push(num);
  return arr;
}

console.log(pushNum(10))
//ans = [10]
console.log(pushNum(4, [1, 2, 3]))
//ans = [1,2,3,4]

 

위 문제도 마찬가지이다. 매개변수는 num, arr 두개지만 전달인자는 10 하나인 경우, arr는 디폴트 파라미터인 [ ]로 설정되는 것이다.

하지만 전달인자에 제대로 num과 arr가 전달 되었다면, 디폴트 파라미터와 관계없이 전달 인자를 사용한다.

 

let age = 27;
let name = 'jin';
let height = 179;

  function outerFn() {
    let age = 24;
    name = 'jimin';
    let height = 178;

    function innerFn() {
      age = 26;
      let name = 'suga';
      return height;
    }

    innerFn();

    console.log(age)
    //ans = 26
    console.log(name)
    //ans  = 'jimin'

    return innerFn;
  }

const innerFn = outerFn();
console.log(age)
//ans = 27
console.log(name)
//ans = 'jimin'
console.log(innerFn())
//ans = 178

 

만드신 분이 BTS의 열렬한 팬이신 듯 한 문제. 많은 사람들이 헷갈렸을 스코프 문제이다.

스코프에서는 'let'의 사용 여부를 먼저 판단해보자. 함수명이 같더라도, 내부 함수에서 let으로 선언된 값은 외부에 영향을 미치지 않는다.

  • let을 사용했다면 → 새로운 변수 선언
  • let을 사용하지 않았다면 → 기존 변수에 새로운 값 할당

 

let age = 27;
let name = 'jin';
let height = 179;
//age = 27
//name = 'jin'
//height = 179

  function outerFn() {
    let age = 24;
    name = 'jimin'; //기존 name의 값을 'jin' → 'jimin'으로 재할당
    let height = 178;
    //age = 24
    //name = 'jimin'
    //height = 178

    function innerFn() {
      age = 26; //기존 age의 값을 24 → 26으로 재할당
      let name = 'suga'; //내부 함수에서만 쓰이는 이름이기 때문에, 외부에서는 사용X
      return height;
    }

    innerFn();
    //age = 26
    //name = 'jimin'
    //height = 178

    return innerFn;
  }
  
  const innerFn = outerFn(); //outerFn이 한번 실행 된 상태
  
  //age = 27(외부 함수에서 let으로 선언되었기 때문에, 전역 변수에 영향X)
  //name = 'jimin'(외부 함수에서 name = 'jimin'으로 재할당)
  //height = 179
  
  //innerFn()의 결과
  //age = 26
  //name = 'jimin'
  //height = 178
  //innerFn()의 return값이 height이므로, innerFn() = 178

 

쉽게 말해, 현재 스코프의 바로 아래 스코프에서 let으로 name이라는 변수가 선언되었다면, 현재 스코프에서 name의 값은 선언한 그대로이다. 하지만 let을 사용하지 않고 바로 name에 다른 값을 할당했다면, 현재 스코프의 name 값도 바뀌게 된다는 것이다.

 

3. Array

 const multiTypeArr = [
   0,
   1,
   'two',
   function () {
     return 3;
   },
   { value1: 4, value2: 5 },
   [6, 7],
 ];
console.log(multiTypeArr[Fill_In][Fill_In])
//ans = 5

 

이번에는 답이 주어져 있고, Fill_In 부분을 채워 답에 맞는 코드를 만드는 것이었다.

답이 5였으므로, 4번 인덱스에 있는 객체를 사용하는 것은 알겠으나,.,.,.

아무리 생각해도 객체의 요소를 배열로 나타내는 것은 불가능 해 보였다.

 

일단 첫번째 괄호는 4번 인덱스니 4가 들어간다고 해도, 두번째 괄호에 1을 넣으면 undefined가 출력 되었다.

한참을 고민하다가, 페어 분께 여쭤보니 '객체의 value를 나타내는 방법 2가지'를 알려 주셨는데.,.,,.,.

 

2023.02.28 - [코드스테이츠] - [FE Bootcamp] 12일차_객체

 

[FE Bootcamp] 12일차_객체

오늘은 배열에 이어 객체에 대해 공부하였다. 나는 객체에 대한 이해도가 엄청나게 떨어졌던 지라 학교 시험이건 코딩 테스트 공부건 객체 부분에서 항상 막혔었는데, 이 참에 아예 제대로 공부

kinggh.tistory.com

 

객체의 value를 나타내는 방법은 2가지가 있었다. Dot Notation과 Bracket Notation.

그중 Bracket Notation은 객체명['key']로 value를 나타내는 것이었는데,.,.,..,,.

key가 어디 들어있는지 생각해보면,..,,..,,..,!!!

 

 const multiTypeArr = [
   0,
   1,
   'two',
   function () {
     return 3;
   },
   { value1: 4, value2: 5 },
   [6, 7],
 ];
console.log(multiTypeArr[4]['value2'])
//ans = 5

 

사람은 생각보다 망각이 빠르다는 것을 절실히 깨달았다,..,

 

const arr = ['peanut', 'butter', 'and', 'jelly'];

console.log(arr.slice(2, 2))
//ans = []
console.log(arr.slice(3, 0))
//ans = []
console.log(arr.slice(5, 1))
//ans = []

 

다음은 slice() 메서드 문제였다. slice() 메서드를 사용하면서, ( ) 안에 아무것도 없거나, 0인 경우 혹은 끝 인덱스가 배열의 범위보다 한참 큰 경우는 봤지만, 시작 인덱스보다 끝 인덱스가 작은 경우는 처음 본 것 같다.

물론, slice(0, -3) 처럼 음수로 넘어가는 경우는 봤지만, -가 붙을 경우 뒤에서부터 센다는 약속이 있기에 그렇게 헷갈리진 않았는데, 0이나 양수가 나와버리니 어떻게 되는지 알 길이 없었다.

 

또 결론부터 말하자면, 시작 인덱스 >= 끝 인덱스인 경우 '빈 배열을 리턴한다'. 

slice() 메서드의 메커니즘은 '시작 인덱스부터 (끝 - 1) 인덱스까지를 복사'하는 것이다. 

인덱스는 좌 → 우로 갈수록 커지기 때문에 시작 인덱스는 자신의 오른쪽으로 가면서 끝 인덱스를 찾을 것이다.

 

하지만 시작 인덱스가 끝 인덱스보다 큰 경우(= 우측에 있는 경우), 복사를 시작하긴 했지만 끝을 찾을 수 없어 결국 빈 배열을 리턴하는 것이다.

 

시작 인덱스와 끝 인덱스가 같은 경우도 마찬가지. 결국 시작 인덱스부터 시작 인덱스 - 1 까지를 복사하므로, 끝이 자신보다 좌측으로 가게 된다.

 

끝 인덱스가 0인 경우에는 끝 인덱스 - 1 = -1이 되어 맨 마지막 인덱스까지 복사할 줄 알았으나,..,,.,.

0인 경우에는 자동으로 -1이 되는 것이 아니라 그냥 0인 상태로 남는 것 같다. 때문에 똑같이 빈 배열을 리턴.

 

4. Object

const currentYear = new Date().getFullYear();
const megalomaniac = {
  mastermind: 'James Wood',
  henchman: 'Adam West',
  birthYear: 1970,
  calculateAge: function (currentYear) {
    return currentYear - this.birthYear;
  },
  changeBirthYear: function (newYear) {
    this.birthYear = newYear;
  },
};

console.log(currentYear)
//ans = 2023
console.log(megalomaniac.calculateAge(currentYear))
//ans = 53

 

사실 문제는 그리 어렵지 않았으나, 새로 등장한 'this'라는 것에 대해 알아보고자 가져왔다.

메서드는 '객체의 프로퍼티로 정의된 함수'를 나타내고, this는 메서드가 호출될 때 해당 메서드가 포함된 객체를 가리키는 접두사이다.

 

위 코드에서, megalomaniac이라는 객체 안에는 calulateAge, changeBirthYear이라는 두개의 함수(=메서드)가 존재한다. 

두 메서드에는 this.birthYear이라는 변수가 있는데, this는 메서드가 포함된 객체이므로, megalomaniac을 나타낸다.

쉽게 말해, this.birthYear = megalomaniac.birthYear이라는 것이다. 

 

그럼 this는 왜 사용하는 것일까?

const currentYear = new Date().getFullYear();
let birthYear = 1960
const megalomaniac = {
  mastermind: 'James Wood',
  henchman: 'Adam West',
  birthYear: 1970,
  calculateAge: function (currentYear) {
    return currentYear - birthYear;
  },
  changeBirthYear: function (newYear) {
    birthYear = newYear;
  },
};

console.log(currentYear)
//ans = 2023
console.log(megalomaniac.calculateAge(currentYear))
//ans = 53

 

전역 변수로 birthYear를 선언해 주었다. 

그렇게 되면, megalomaniac 객체 내의 birthYear은 단순히 '객체 내의 프로퍼티'에 지나지 않는다.

당연히 메서드를 호출 할 때, birthYear에 할당된 값은 전역 변수로 선언된 값일 것이고, 객체 내의 birthYear은 사용하지 않는 것이다.

 

반면 this를 사용한다면, 메서드 호출 시 this.birthYear은 megalomaniac.birthYear를 가리키게 된다. 

객체의 value를 가져와 사용하게 되는 것이다.

 

5. SpreadSyntax

function getAllParams(required1, required2, ...args) {
  return [required1, required2, args];
}
console.log(getAllParams(123))
//ans = [123, undefined, []]

 

rest 문법을 제대로 이해하지 못했던 문제였다.

매개변수는 3개, 전달인자는 1개인 상황. 함수의 리턴 형태는 배열이다.

매개변수가 전달인자보다 많을 경우, 남는 자리는 전부 undefined로 채워진다고 배웠다.

 

하지만 이는 어디까지나 전달인자가 '원시 자료형'일 경우였고,.,., 

rest 문법으로 표현된 ...args는 어떤 형태로 리턴되는지 몰랐다.

 

먼저, spread 연산자를 사용하면 자료형은 '정수의 집합'의 형태로 바뀐다고 알고 있었으나,..,,.,.,.

 

지가 써놓고도 모르는 사람

 

아하! rest 문법을 사용하면 '배열의 형태'로 전달되는 것이었다,.,.,.!!

전달인자가 없다면,.,.배열은 undefined가 없으니 빈 배열을 리턴 할 것이었다.

함수의 return 형태는 배열, 매개변수는 (원시, 원시, 배열)의 3개 전달인자는 1개이므로, 

[전달인자, undefined, []]이라는 결과가 나오는 것이다!

 

6. Destructing

const user = {
  name: '김코딩',
  company: {
    name: 'Code States',
    department: 'Development',
    role: {
      name: 'Software Engineer'
    }
  },
  age: 35
}

const changedUser = {
  ...user,
  name: '박해커',
  age: 20
}
console.log(changedUser)
//ans = {
//name: '박해커',
//company: {
//name: 'Code States',
//department: 'Development',
//role: {
//  name: 'Software Engineer'
//}
//},
//age: 20
//}

 

마지막 문제는 객체를 전개한 뒤 다른 객체 안에 넣을 때 프로퍼티가 어떻게 변하는지에 대한 문제였다.

지난 게시글에서도 설명했듯, 같은 key를 가진 프로퍼티라면 나중에 선언된 value로 덮어쓰기 된다.

 

changedUser 객체의 경우, ...user를 통해 user 객체의 프로퍼티들이 먼저 선언되고, name:'박해커', age:20이라는 프로퍼티가 나중에 선언된다.

이때 name과 age는 이미 존재하는 key이므로, 새로 선언된 value들로 값이 바뀌게 된다.

  • name : '김코딩' → '박해커'
  • age : 35 20

주의할 점은, key의 위치는 그대로이고, value의 값만 덮어쓰기 된다는 것.

name이 마지막에 다시 선언된다고 해도, 위치는 최초에 선언된 곳에 그대로 있게 된다.

 

 

수많은 문제들을 풀며 내가 몰랐던 것들, 헷갈렸던 것들, 그리고 두루뭉술하게 알고 있던 것들을 확실히 짚고 넘어갈 수 있었다.

특히 this 같은 경우에는 코딩을 처음 배울 때 부터 지금까지 제대로 된 개념이 잡혀있지 않았는데, 이 Koans를 통해 확실히 알고 가게 되어 뭔가 답답한 것이 싹 내려간 것 같다.

아직 자바스크립트 기초중의 기초인데도 이렇게 틀리는 것이 많다는 것에서 또 한번 마음을 다잡아야겠다는 생각이 들었다,..,

이번주가 지나면 딱 1달차가 된다. 앞으로 할 날이 더 많으니 좀 더 파이팅 해서 가보도록 하겠다.