Javascript Koans

왕지호·2022년 11월 9일
1

오늘은 페어와 함께 Javascript Koans를 풀었다.
그 중 새로 알게 된 개념 위주로 정리해보자!

.equal()

  • equal은 두 값 타입이 엄격하게 같은지 검사(===)한다
 it("Matcher .equal 의 사용법을 학습합니다.", function () {
    let expectedValue = 2;
    expect(1 + 1).to.equal(expectedValue);
  });

==를 사용할까 ===를 사용할까?

  • 비교 연산자 ==는 느슨하게 검사를 한다. 그렇기 때문에 최대한 같은 타입끼리 연산을 하고, 즉 엄격한 동치 연산('===')을 사용하고, 조건문에 비교 연산을 명시하는 것이 훨씬 좋다.

Const

  • 변수 재할당 금지
  • 새로운 요소 추가, 삭제 가능
  • 객체의 경우 속성을 추가하거나 삭제 가능

const를 추천하는 이유?

  • 변수를 선언하는 시점에는 재할당이 필요할지 잘 모르고 객체의 경우 재할당을 하는 경우가 드물다. 따라서 변수를 선언할 때에는 일단 const 키워드를 사용하고 나중에 재할당을 해야하는 경우 let으로 바꾸자.

scope

  • 변수의 값(변수에 담긴 값)을 찾을 때 확인하는 곳

함수 호이스팅

  • 함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것
  • var 변수 선언과 함수선언문에서만 호이스팅이 일어남
  • var 변수/함수의 선언만 위로 끌어 올려지며, 할당은 끌어 올려지지 않음
  • let/const 변수 선언과 함수표현식에서는 호이스팅이 발생하지 않음

함수선언문과 함수표현식의 차이

  • 함수선언문은 호이스팅에 영향을 받지만, 함수표현식은 호이스팅에 영향을 받지 않음
  • 함수선언문은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석할 때 맨 위로 끌어 올려짐
  • 함수표현식은 함수선언문과 달리 선언과 호출 순서에 따라서 정상적으로 함수가 실행되지 않을 수 있음
/* 오류 */
function printName(firstname) { // 함수선언문
    console.log(inner); // "undefined": 선언은 되어 있지만 값이 할당되어있지 않은 경우
    var result = inner(); // ERROR!!
    console.log("name is " + result);

    var inner = function() { // 함수표현식 
        return "inner value";
    }
}
printName(); // TypeError: inner is not a function

/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
/* 오류 */
function printName(firstname) { // 함수선언문
    var inner; // Hoisting - 변수값을 끌어올린다. (선언은 되어 있지만 값이 할당되어있지 않은 경우)
    console.log(inner); // "undefined"
    var result = inner(); // ERROR!!
    console.log("name is " + result);

    inner = function() { // 함수표현식 
        return "inner value";
    }
}
printName(); // TypeError: inner is not a function

Q. “printName이 is not defined” 이라고 오류가 나오지 않고, function이 아니라는 TypeError가 나오는 이유?
A. printName이 실행되는 순간 (Hoisting에 의해) inner는 ‘undefined’으로 지정되기 때문
inner가 undefined라는 것은 즉, 아직은 함수로 인식이 되지 않고 있다는 것을 의미함

Reference:https://gmlwjd9405.github.io/2019/04/20/function-declaration-vs-function-expression.html
https://gmlwjd9405.github.io/2019/04/22/javascript-hoisting.html

lexical scope

  • 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정되는 것

실행 컨텍스트

  • 변수 저장장소

클로저
mdn 정의

  • "함수와 함수가 선언된 어휘적(lexical) 환경의 조합을 말합니다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다."

- 반환하고 있는 내부함수가 외부함수의 변수를 참조 or 접근하고 있으면 반환되는 내부함수는 바로 클로저

let seenYet = function() {
  let archive = {};
  return function(val) {
    if (archive[val]) {
      return true;
    } 
    archive[val] = true;
    return false;
  }
}

답은 'seenYet 함수가 리턴하고 있는 익명 함수'
seenYet이 반환하는 익명 함수는 외부 함수 seenYet의 스코프에 선언된 변수 archive에 접근할 수 있기 때문에 클로저임

클로저는 내부(inner) 함수가 외부(outer) 함수의 지역 변수에 접근할 수 있음.

여기서의 키워드는 "함수가 선언"된 "어휘적(lexical) 환경". 특이하게도 자바스크립트는 함수가 호출되는 환경과 별개로, 기존에 선언되어 있던 환경 - 어휘적 환경 - 을 기준으로 변수를 조회하려고 함. "외부 함수의 변수에 접근할 수 있는 내부 함수"를 클로저 함수로 부르는 이유.

클로저 함수: 클로저는 외부 함수의 컨텍스트에 접근할 수 있는 내부 함수를 뜻함. 외부 함수의 실행이 종료된 후에도, 클로저 함수는 외부 함수의 스코프, 즉, 함수가 선언된 어휘적 환경에 접근할 수 있음.

클로저 사용 예시: 클로저를 통해 커링(currying, 함수 하나가 n개의 인자를 받는 대신 n개의 함수를 만들어 각각 인자를 받게 하는 방법), 클로저 모듈(변수를 외부 함수 스코프 안쪽에 감추어, 변수가 함수 밖에서 노출되는 것을 막는 방법) 등의 패턴을 구현할 수 있음.

클로저의 단점: 일반 함수였다면 함수 실행 종료 후 가비지 컬렉션(참고 자료: MDN '자바스크립트의 메모리 관리') 대상이 되었을 객체가, 클로저 패턴에서는 메모리 상에 남아 있게 됨. 외부 함수 스코프가 내부 함수에 의해 언제든지 참조될 수 있기 때문. 따라서 클로저를 남발할 경우 퍼포먼스 저하가 발생할 수도 있음.

자바스크립트는 가비지 컬렉션을 통해 메모리 관리를 함. 객체가 참조 대상이 아닐 때, 가비지 컬렉션에 의해 자동으로 메모리 할당이 해제됨.


화살표 함수

  • function 키워드를 생략하고 화살표 => 를 붙임
it("화살표 함수를 이용해 클로저를 표현합니다", function () {
    const adder = (x) => {
      return (y) => {
        return x + y;
      };
    };

    expect(adder(50)(10)).to.eql(60);

    const subtractor = (x) => (y) => {
      return x - y;
    };

    expect(subtractor(50)(10)).to.eql(40);

    const htmlMaker = (tag) => (textContent) =>
      `<${tag}>${textContent}</${tag}>`;
    expect(htmlMaker("div")("code states")).to.eql("<div>code states</div>");

    const liMaker = htmlMaker("li");
    expect(liMaker("1st item")).to.eql("<li>1st item</li>");
    expect(liMaker("2nd item")).to.eql("<li>2nd item</li>");
  });

this.

  • 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this
  • obj이라는 객체 안에 foo라는 메서드를 선언하고, this를 반환한다고 했을 때 ( 예: let obj = {foo: function() {return this}}; )
  • this는 함수의 호출에 따라서 값이 달라지기도함

화살표 함수는 자신의 this가 없음
화살표 함수에서의 this는 자신을 감싼 정적 범위(lexical context)(전역에서는 전역 객체를 가리킴)

  • 일반 변수 조회 규칙(normal variable lookup rules)을 따르기 때문에, 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수 바로 바깥 범위에서 this를 찾음.

얕은 복사 & 깊은 복사

얕은 복사(Shallow Copy)

  • 객체를 복사할 때 원래값과 복사된 값이 같은 참조를 가리키고있는 것을 말함
  • 객체안에 객체가 있을 경우 한개의 객체라도 원본 객체를 참조하고 있다면 얕은 복사

'Object.assign'

  • 'Object.assign'을 통한 복사는 reference variable은 주소만 복사
const obj = {
      mastermind: "Joker",
      henchwoman: "Harley",
      relations: ["Anarky", "Duela Dent", "Lucy"],
      twins: {
        "Jared Leto": "Suicide Squad",
        "Joaquin Phoenix": "Joker",
        "Heath Ledger": "The Dark Knight",
        "Jack Nicholson": "Tim Burton Batman",
      },
    };
    
    const copiedObj = Object.assign({}, obj);
    copiedObj.mastermind = "James Wood";
    expect(obj.mastermind).to.equal("Joker");

깊은 복사(Deep Copy)

  • 객체는 객체안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체

    JSON.parse(JSON.stringify(object))
let obj = { 
  a: 1,
  b: { 
    c: 2,
  },
}

let newObj = JSON.parse(JSON.stringify(obj));

obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)

전개 문법(spread syntax)

  • 빈 배열에 전개 문법을 사용할 경우, 아무것도 전달되지 않음
  • 여러 개의 배열을 이어붙일 수 있음
  • 여러 개의 객체를 병합할 수 있음
it("여러 개의 객체를 병합할 수 있습니다.", function () {
    const fullPre = {
      cohort: 7,
      duration: 4,
      mentor: "hongsik",
    };

    const me = {
      time: "0156",
      status: "sleepy",
      todos: ["coplit", "koans"],
    };

    const merged = { ...fullPre, ...me };
    // 변수 'merged'에 할당된 것은 'obj1'과 'obj2'의 value일까요, reference일까요? -> value
    // 만약 값(value, 데이터)이 복사된 것이라면, shallow copy일까요, deep copy일까요? ->얕은 복사

    expect(merged).to.deep.equal({
      cohort: 7,
      duration: 4,
      mentor: "hongsik",
      time: "0156",
      status: "sleepy",
      todos: ["coplit", "koans"],
    });
  });

구조 분해 할당(Destructing Assignment)

  • 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식
  • 할당하기 전 왼쪽에는, rest 문법 이후에 쉼표가 올 수 없음(const [first, ...middle, last] = array)
it("rest/spread 문법을 객체 분해에 적용할 수 있습니다 #3", () => {
    const user = {
      name: "김코딩",
      company: {
        name: "Code States",
        department: "Development",
        role: {
          name: "Software Engineer",
        },
      },
      age: 35,
    };

    const changedUser = {
      ...user,
      name: "박해커",
      age: 20,
    };

    const overwriteChanges = {
      name: "박해커",
      age: 20,
      ...user,
    };

    const changedDepartment = {
      ...user,
      company: {
        ...user.company,
        department: "Marketing",
      },
    };

    expect(changedUser).to.eql({
      name: "박해커",
      company: {
        name: "Code States",
        department: "Development",
        role: {
          name: "Software Engineer",
        },
      },
      age: 20,
    });

    expect(overwriteChanges).to.eql({
      name: "김코딩",
      company: {
        name: "Code States",
        department: "Development",
        role: {
          name: "Software Engineer",
        },
      },
      age: 35,
    });

    expect(changedDepartment).to.eql({
      name: "김코딩",
      company: {
        name: "Code States",
        department: "Marketing",
        role: {
          name: "Software Engineer",
        },
      },
      age: 35,
    });
  });
profile
개발 공부하는 코린이!

0개의 댓글