JavaScript - 핵심 개념과 주요 문법

Kim-DaHam·2023년 3월 3일
0

JavaScript

목록 보기
5/18
post-thumbnail

🔥 학습목표

  • 클로저에 대한 기초 개념을 학습한다.
  • ES6 신규 문법 중 스프레드 문법과 rest 문법에 대해 공부하고, 앞으로 스프레드 문법으로 대체할 수 있는 건 최대한 대체하면서 사용에 익숙해진다.
  • 자바스크립트 DeepDive 24장을 학습한다.
  • 과제 중 헷갈렸던 부분을 다시 정리하고 복습한다.



🟣 원시 자료형과 참조 자료형

⬜ 원시 자료형

  • 원시 자료형을 변수에 할당하면 메모리 공간에 값 자체가 저장 된다.

  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값 자체가 복사되어 전달 된다. 이를 값에 의한 전달이라 한다.

  • 원시 자료형은 변경 불가능한 값이다. 이때 변경 불가능하다는 것은 변수가 아니라 값에 대한 진술이다. (변수는 언제든지 재할당 할 수 있다.)

    • 원시 값을 할당한 변수에 새로운 원시 값을 재할당 하면 새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 변수는 새롭게 재할당한 원시 값의 메모리 공간 주소를 가리킨다.

    • 이는 변수에 할당 된 원시 값이 변경 불가능한 값이기 때문이다. 값을 직접 변경할 수 없다.

    • 이러한 특성을 불변성이라 한다.

    • Ex. 유사 배열 객체인 문자열 let str = 'string' 이 존재할 때, str[0] = 'S' 와 같이 변경하지 못하는 이유도 문자열이 원시 값이기 때문이다.

      ⬜ 참조 자료형

    • 참조 자료형을 변수에 할당하면 메모리 공간에 주솟값이 저장 된다. 생성 된 객체가 저장된 메모리 공간의 주소 그 자체다.

    • 참조 값을 갖는 변수를 다른 변수에 할당하면 주솟값이 복사되어 전달 된다.

    • 즉 여러 개의 식별자가 하나의 객체를 공유할 수 있다.

    🟧 얕은 복사와 깊은 복사

    얕은 복사

    <배열을 복사하는 방법>

    1. slice()

    let copy = arr.slice();
    : 새롭게 생성 된 배열은 원본 배열과 참조하는 주소가 다르다. 복사한 배열에 요소를 추가해도 원본 배열엔 추가되지 않는다.

    2. spread syntax (...)

    let copy = [...arr];
    새로운 배열 안에 원본 배열을 펼쳐서 전달한다. 마찬가지로 같은 요소를 가지고 있지만 각각 다르주소를 참조한다.


    <객체를 복사하는 방법>

    1. Object.assign()

    let copy = Object.assign({}, obj);
    새로운 객체는 원본 객체와 다른 주소값을 가진다.

    2. spread syntax

    let copy = {...obj};
    얕은 복사 까지만 이루어진다. 즉 한 단계까지만 복사하고, 객체에 중첩되어 있는 객체는 같은 주솟값을 참조하게 된다.


    깊은 복사

    외부 라이브러리 사용: lodash 또는 ramda

const _ = require('lodash');
const obj2 = _.cloneDeep(obj);



🟣 스코프

스코프란 식별자가 유효한 범위를 말한다. 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.

  • 전역 스코프: 코드의 가장 바깥 영역
  • 지역 스코프: 함수 몸체 내부

지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.

⬜ 스코프 체인

변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.

⬜ 함수 레벨 스코프

  • 블록 레벨 스코프: 함수 뿐만 아니라 모든 코드블록(if, for, while, try/catch 등)을 포함하여 지역 스코프를 만든다.

  • 함수 레벨 스코프: 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정.

🔴 화살표 함수는 블록 스코프로 취급한다.

⬜ 렉시컬 스코프

  • 동적 스코프(dynamic scope): 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다.

  • 정적 스코프(static scope) 또는 렉시컬 스코프(lexical scope): 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다. 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정. 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프다.

const x = 1;
function foo() {
  const x = 10;
  // 함수 호출 위치와 상위 스코프는 아무 관계 없다.
  bar();
}

// 함수 bar의 상위 스코프는 전역 객체 window 메서드다.
function bar() {
  console.log(x);
}



🟣 클로저

  • 함수와 그 함수가 접근할 수 있는 변수의 조합.

아래에서 중첩 함수 innerFunc는 자신의 상위 스코프인 외부함수 outerFunc의 x 변수에 접근할 수 있다.

const x = 1;

function outerFunc() {
  const x = 10;
  
  function innerFunc() {
    console.log(x); // 10
  }
  
  innerFunc();
}
outerFunc();

⬜ 함수 객체의 내부 슬롯 [[Enviroment]]

🔵 함수는 자신의 내부 슬롯 [[Enviroment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.

기존 함수 내부에서 새로운 함수를 리턴하면 클로저로서 활용할 수 있다.


아래 코드에서 outer 함수의 실행이 종료되면 outer 함수의 실행 컨텍스트는 스택에서 제거된다. outer 함수의 지역변수 x 또한 생명주기를 마감한다.

그러나 inner 함수 호출 시 x 변수에 계속해서 접근한다.

🔴 외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저(closure)라고 부른다.

const x = 1;

function outer(x) {
  let y = 2
  const inner = function (z) {
    return console.log(x * y * z);
  }
  return inner;
}

const multi = outer(10);
multi(2); // 40

위 코드에서는 inner가 클로저로서 outer의 매개변수와 지역변수인 x, y에 접근할 수 있다. 게다가 inner 함수 호출 시 계속 재사용 할 수 있다.

🔴 왜 그럴까?

outer 함수의 렉시컬 환경이[[Enviroment]]에 계속해서 "보존"되기 때문이다.

outer 함수가 실행 컨텍스트 스택에서 제거되었더라도 outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.

outer 함수의 렉시컬 환경은 inner 함수의 [[Enviroment]] 내부 슬롯에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 되지 않기 때문이다.


⬜ 커링

여러 전달인자를 가진 함수를 연속적으로 리턴하는 함수로 변경하는 행위.

전체 프로세스의 일정 부분까지만 실행하는 경우 유용하다.

예를들어, a + b + c 를 해야하는 경우

// 이렇게 한꺼번에 하는 게 아닌
function sum(a, b, c) {
  return a+b+c;
}

// 단계를 나누어 계산한다. 어쩌다 중간까지만 호출해도 되도록.
function currySum(a) {
  return function(b) {
    return function(c) {
      return a+b+c;

⬜ 클로저의 활용

🔵 클로저의 장점

  • 상태를 안전하게 은닉하고
  • 특정 함수에게만 상태 변경을 허용한다.
function makeCounter(aux) {
  // 외부에서 counter를 바꾸지 못하게 은닉한다. 
  let counter = 0;
  
  return function() {
    counter = aux(counter);
    return counter;
  };
}

// 보조 함수
function increase(n) {
  return ++n;
}
function decrease(n) {
  return --n;
}

// 함수로 함수를 생성한다. 보조 함수를 인수로 전달 받아 함수를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser()); // 1
const decreaser = makeCounter(decrease);
console.log(decreaser()); // 0



🟣 ES6 신규 문법

⬜ spread/rest 문법

spread 문법

...arr

배열을 풀어서 인자로 전달하거나, 각각의 요소로 넣을 때 사용한다.

  1. 배열 합치기
let arr1 = [0,1,2];
let arr2 = [3,4,5];
arr1 = [...arr1, ...arr2] // [0, 1, 2, 3, 4, 5]
  1. 객체 합치기
let obj1 = {foo: 'bar', x: 42}
let obj2 = {foo: 'baz', y: 20}

let mergeObj = {...obj1, ...obj2};
console.log(mergeObj); // { foo: 'baz', x: 42, y:20}

rest 문법

파라미터를 배열의 형태로 받아서 사용할 수 있다. 파라미터 개수가 가변적일 때 유용하다.

생김새는 spread와 비슷하나 역할이 다르다!

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

1. 객체에서 사용

others 안에 name을 제외한 값이 들어있다...!!

const personInfo = {
  name: '김다함',
  age: 22,
  major: 'Computer Engineering',
  position: 'SEB_FE_44'
}

const {name, ...others} = personInfo;
console.log(name); // '김다함'
console.log(others); // {age: 22, major: 'Computer Engineering, position: 'SEB_FE_44'};

const {age, ...rest} = personInfo;
console.log(rest); // {name:'김다함', major: 'Computer Engineering, position: 'SEB_FE_44'};

2. 배열에서 사용

const arr = [1, 2, 3, 4, 5];
const [one, ...rest] = numbers;
console.log(one); // 1
console.log(rest); // [2, 3, 4, 5]

3. 함수에서 사용

만약 파라미터의 개수가 몇 개가 될지 모르는 상황이라면...

rest 파라미터를 사용하면 파라미터를 배열의 형태로 받아들이기 때문에 2개 수를 더하든 5개 수를 더하든 함수를 작동하는 데 문제가 생기지 않는다.

function sum(...rest) {
  return rest.reduce((acc, current) => acc+current, 0);
}

const result = sum(1, 2, 3, 4, 5); // 21

⬜ 구조 분해 할당(destructing)

spread 문법을 이용하여 값을 해체한 후, 개별 값을 변수에 새로 할당하는 과정을 말한다.

위에서 보여준 배열, 객체 분해를 즉 구조 분해 할당이라고 한다...

  1. 배열을 분해한다.
const array = [1, 2, 3, 4, 5];
const [first, second] = array;

console.log(first); // 1;
console.log(second); // 2;
  1. rest/spread 문법을 배열 분해해 적용
const arr = [1, 2, 3, 4, 5];
const [one, ...rest] = numbers;
console.log(one); // 1
console.log(rest); // [2, 3, 4, 5]

⬜ 화살표 함수(arrow function)

🔴 화살표 함수를 이용해 클로저 표현

const adder = x => {
      return y => {
        return x + y
      }
    }

let result = adder(10)(20);
console.log(result); // 30

// 또는 아래와 같이 축약 가능

const adder = x => y => {
      return x + y
    }




📔 오늘의 후기

  1. spread와 rest 문법에 대해 잘 모르고 사용하지 않았었는데 이번에 똑바로 배우게 되었다. 다만 아직 개인적인 코딩 중에 제대로 활용한 적은 없어서... 그때그때 번뜩 잘 떠오를지 확신이 들진 않는다. 프로그래머스 문제 같은 걸 풀다보면 사용하게 되겠지?

  2. 클로저를 이해하고 나니 무척 재밌었다. 오늘 배운 것 중에 제일 개인적으로 활용해보고 싶은 부분이 클로저를 활용한 정보 은닉이다. 매번 ~이렇게 저렇게 하는 편이 더 좋다~ 라는 걸 보고서야 아차 수정하게 되는데, 앞으로는 내 스스로 더욱 안전하고 좋은 코드를 작성하는 법에 능숙해지면 좋겠다.

  3. 스코프 관련해서는... 멍하니 코드를 흐름에 따라 작성하면서 생각하면 스코프 에러는 잘 안 나는데, 이번 koans 과제처럼 곰곰히 과정을 추리하며 근거를 따지려들면 갑자기 머리가 복잡해진다. 이것도 내가 다 알고있다고 착각한 탓이겠지? 평소에도 꼼꼼하게 생각하면서 코딩하도록 계속 의식해야겠다.

profile
다 하자

0개의 댓글