자바스크립트(Koans)

전예훈·2023년 3월 6일
0

Koans

Koans는 불교에서 유래된 단어로, 결론을 내리기 전에 이게 왜 맞는지 깊게 고민한다는 의미를 가지고 있습니다. 답이 미리 제시되어 있긴 때문에 고민 없이 풀면, 큰 어려움 없이 전부 다 풀 수 있습니다. 하지만 그게 왜 정답인지 깊게 고민해 보는 시간을 갖지 않는다면 충분이 성장하기 어렵기 때문에 배운 내용들을 복습하는 느낌으로 공부하는 시간을 가져보겠습니다.!

학습목표!

  • JavaScript의 타입
  • 변수 선언 키워드 let과 const
  • 화살표 함수
  • 스코프
  • 배열
  • 객체
  • spread
  • 구조분해 할당

아래에 코드를 보면서 주석에 설명하면서 작성하도록 하겠습니다.!

본격적으로 공부하기전 expect, equal 공부하기!


describe('expect에 대해서 학습합니다.', function () {
  /*
  코드스테이츠의 JavaScriptKoans에 오신 것을 환영합니다.
  01_Introduction.js 에서는 유닛 테스트(unit test)를 작성하는 방법을 학습합니다.

  ---
  
  연습문제(coplit)를 풀다보면 여러분이 제출한 코드를 테스트 케이스 별로 검사하여 전체 결과를 보여줬던 것을 기억하시나요? 
  여러분이 작성한 함수가 주어진 입력값에 대해서 리턴하는 값이 기대하는 값과 같은지를 비교하는 것입니다.
  
  이때 테스트하는 값과 기대값을 비교하기 위해 expect 함수를 사용합니다.
  expect의 사용법은 아래와 같습니다.
  
    expect(테스트하는값).기대하는조건
    expect(isEven(3)).to.be.true => 'isEven(3)'의 결과값은 참(true)이어야 한다'
    expect(1 + 2).to.equal(3) => 'sum(1, 2)의 결과값은 3과 같아야(equal) 한다'
  
  '기대하는조건'에 해당하는 함수를 matcher라고 합니다.
    '참인 것이어야 한다' => to.be.true
    '3과 같아야 한다' => to.equal(3)

  mocha, chai framework에는 다양한 matcher가 있지만, 이번 과제에서는 그 일부가 사용됩니다. 
  궁금하시면 아래 링크를 통해 확인하시기 바랍니다. (어떤 것들이 있는지만 확인하는 수준이면 충분합니다.)
    https://www.chaijs.com/api/bdd/
  
  여러분들은 expect를 활용하여 모든 Koans 과제를 완성하시면 됩니다.
  즉, 각 테스트 케이스가 테스트를 통과하도록 테스트 코드를 적절하게 변형하여야 합니다. 
  단순히 테스트를 통과하는 것을 넘어, '왜 통과하는지'를 고민하는 시간이 되었으면 합니다.
  */

  it('테스트하는 값(expect의 전달인자)이 true인지의 여부를 검사합니다.', function () {
    /* 
    첫 문제는 가볍게 풀어봅시다.
    expect 함수의 전달인자를 아래와 같이 false 대신 true로 변경하여 테스트를 통과하세요.
      expect(true).to.be.true;
    */
    // TODO: 테스트가 통과될 수 있도록(테스트하는 값이 true가 되도록) expect의 첫 번째 전달인자를 수정합니다.
    expect(true).to.be.true;
  });

  it('테스트하는 값(expect의 전달인자)이 falsy 여부를 검사합니다.', function () {
    // 반대의 경우에는 어떻게 해야할까요?
    // TODO: 테스트가 통과될 수 있도록(테스트하는 값이 false가 되도록) expect의 첫 번째 전달인자를 수정합니다.
    expect(false).to.be.false;
  });

  /*
  2가지 matcher(.true, .false)를 통해 '기대하는 값'이 true인지 false인지 확인하는 방법을 학습했습니다.
  이 때, '기대하는 값'은 표현식(expression)이거나 함수의 실제 실행 결과입니다.
    1) 표현식: true || false, 1 + 1, 10 * 3
    2) 함수의 실행: isEven(3), sum(1, 2)
  
  보시면 알 수 있듯이, '기대하는 값'은 true, false 뿐만 아니라 어떤 구체적인 값인 경우가 있습니다. 
  이 경우 '기대하는 값'이 '특정 값'과 같은지를 비교해서 테스트를 진행하면 됩니다. 
  가장 간단한 방법은 비교 연산자 === 을 사용하는 방법이 있습니다. 
  '기대하는 값과 특정 값을 비교한 결과'가 true인지 확인하면 됩니다.
    expect('기대하는 값과 특정 값을 비교한 결과').to.be.true;
  */
  it("'테스트하는 값'을 '기대하는 값'과 비교한 결과가 참 인지 확인합니다.", function () {
    // '테스트하는 값'은 우리가 작성한 어떤 코드의 실제 실행 결과 값이므로 '실제 값'이라고 불러도 됩니다.
    let actualValue = 1 + 1;
    let expectedValue = 2; // TODO: 'FILL_ME_IN'을 변경하여 테스트 케이스를 완성합니다.
    expect(actualValue === expectedValue).to.be.true;
  });

  /*
  이처럼 to.be.true, to.be.false 만을 가지고도 많은 테스트 케이스를 작성할 수 있습니다. 
  하지만 이는 직관적이지 않고 다소 불편합니다. 
  두 값 A와 B를 '비교한 결과'가 참인지를 확인하는 대신에 직접 A가 B와 같은지 확인하는 matcher가 없을까요?
  .equal이 바로 그런 역할을 합니다. 아래 테스트 코드는 '테스트하는값'이 '기대하는값'과 같은지 직접 확인합니다.
    expect('테스트하는값').to.equal('기대하는값');
  
  이제 'FILL_ME_IN'을 적절한 값으로 변경하여 테스트 케이스를 완성하시면 됩니다. 
  이후에도 같은 방식으로 'FILL_ME_IN' 변경하면 됩니다.
  */
  it('Matcher .equal 의 사용법을 학습합니다.', function () {
    let expectedValue = 2; // TODO
    // .equal은 두 값이 타입까지 엄격하게 같은지 검사(strict equality, ===)합니다.
    expect(1 + 1).to.equal(expectedValue);
  });

  it('Matcher .equal의 사용법을 학습합니다.', function () {
    let actualValue = (1 + 1).toString();
    expect(actualValue).to.equal(actualValue); // TODO
  });
});



describe('type에 대해서 학습합니다.', function () {
  it("비교연산자 '=='는 두 값의 일치 여부를 느슨하게 검사(loose equality)합니다.", function () {
    let actualValue = 1 + 1;
    let expectedValue = '2';
    expect(actualValue == expectedValue).to.be.true;

    /*
    혹시 'FILL_ME_IN'을 '2'(문자열 '2')로 변경해도 테스트가 통과된다는 사실을 알고 계십니까?
    '=='의 실행 중 타입 변환(type coercion)이 일어나기 때문입니다. 
    이는 다소 복잡하고, 명확한 규칙이 존재하지 않습니다. 
    느슨한 동치 연산자 '=='의 느슨함을 보여주는 예시가 아래에 있습니다.
    아래 테스트 코드들의 주석을 제거해도 이 테스트는 통과합니다.
    */

    // expect(0 == false).to.be.true;
    // expect('' == false).to.be.true;
    // expect([] == false).to.be.true;
    // expect(![] == false).to.be.true;
    // expect([] == ![]).to.be.true;
    // expect([] == '').to.be.true;
    // expect([] == 0).to.be.true;
    // expect([''] == '').to.be.true;
    // expect([''] == 0).to.be.true;
    // expect([0] == 0).to.be.true;
  });

  /*
  사실 느슨한 동치 연산(loose equality)는 프로그램의 작동을 예측하기 다소 어렵게 만듭니다.
  '=='의 특성을 정확하게 외워서 모든 상황에 대응하겠다는 자세는 접어두시기 바랍니다.
  매우 비효율적일뿐더러 일반적인 컴퓨터 프로그래밍 언어의 관습(convention)과도 거리가 먼 자세입니다.
  지금부터는 엄격한 동치 연산(strict equality) '==='을 사용하시기 바랍니다.
  */

  it("비교연산자 '==='는 두 값의 일치 여부를 엄격하게 검사(strict equality)합니다.", function () {
    let actualValue = 1 + 1;
    let expectedValue = 2;
    expect(actualValue === expectedValue).to.be.true;
    // 이제 'FILL_ME_IN'을 대신할 수 있는 건 number 타입의 2뿐입니다.
    // 문자열 '2'는 테스트를 통과하지 못합니다.
  });

  it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
    expect(1 + '1').to.equal('11');
  });

  it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
    expect(123 - '1').to.equal(122);
  });

  it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
    expect(1 + true).to.equal(2);
  });

  it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
    expect('1' + true).to.equal('1true');
  });

  /*
  지금까지 본 것처럼 자바스크립트에는 다소 이해하기 힘든 부분들이 존재합니다. 
  아래처럼 자바스크립트의 별난(quirky) 부분들을 따로 모아둔 저장소도 있을 정도입니다.
    https://github.com/denysdovhan/wtfjs
  
  여기서도 동치 연산을 학습하면서 당부한 자세가 요구됩니다.
  이런 별난 특성들을 전부 외워서 모든 상황에 대응하려고 하지 말고, 올바른 코딩 습관을 기르시기 바랍니다.
  대표적으로 최대한 같은 타입끼리 연산을 하고, 즉 엄격한 동치 연산('===')을 사용하고, 조건문에 비교 연산을 명시하는 것이 훨씬 좋습니다.
  */
});

변수 var let const 에 대해 공부하기


describe("'const'에 대해서 학습합니다.", function () {
  it("'const'로 선언된 변수에는 재할당(reassignment)이 금지됩니다.", function () {
    // 아래 코드에서 문제가 되는 부분을 삭제합니다.
    const constNum = 0;
    expect(constNum).to.equal(0);
    const constString = 'I am a const';
    expect(constString).to.equal('I am a const');
  });

  it("'const'로 선언된 배열의 경우 새로운 요소를 추가하거나 삭제할 수 있습니다.", function () {
    const arr = [];
    const toBePushed = 42;
    arr.push(toBePushed);
    expect(arr[0]).to.equal(42);

    // 여전히 재할당은 금지됩니다.
    // arr = [1, 2, 3];
  });

  it("'const'로 선언된 객체의 경우, 속성을 추가하거나 삭제할 수 있습니다.", function () {
    const obj = { x: 1 };
    expect(obj.x).to.equal(1);

    delete obj.x;
    expect(obj.x).to.equal(undefined);

    // 여전히 재할당은 금지됩니다.
    // obj = { x: 123 };

    obj.occupation = 'SW Engineer';
    expect(obj['occupation']).to.equal('SW Engineer');
  });

  /*
  재할당도 안되는 'const' 키워드를 굳이 써야하는지 이해가 안 될수도 있습니다.
  'let' 키워드는 재할당이 가능하기 때문에 여러모로 편하고, 큰 문제도 없어 보이기 때문입니다.
  이에 대해서 잠시 고민하신 후, 'const'가 추천되는 이유에 대해 직접 찾아보시기 바랍니다.

  동기 부여를 위해 구글 자바스크립트 코딩 스타일 가이드를 소개해 드립니다.
  세계의 탑코더들이 있는 구글 스타일 가이드는 선언 키워드에 대해서 어떻게 안내하고 있을까요?
    https://google.github.io/styleguide/jsguide.html#features-use-const-and-let
  */
});


scope


describe('scope 대해서 학습합니다.', function () {
  //  scope는 변수의 값(변수에 담긴 값)을 찾을 때 확인하는 곳을 말합니다. 반드시 기억하시기 바랍니다.
  it('함수 선언식(declaration)과 함수 표현식(expression)의 차이를 확인합니다.', function () {
    let funcExpressed = 'to be a function';

    expect(typeof funcDeclared).to.equal("function");
    expect(typeof funcExpressed).to.equal("string");

    function funcDeclared() {
      return 'this is a function declaration';
    }

    funcExpressed = function () {
      return 'this is a function expression';
    };

    // 자바스크립트 함수 호이스팅(hoisting)에 대해서 검색해 봅니다.

    const funcContainer = { func: funcExpressed };
    expect(funcContainer.func()).to.equal('this is a function expression');

    funcContainer.func = funcDeclared;
    expect(funcContainer.func()).to.equal('this is a function declaration');
  });

  it('lexical scope에 대해서 확인합니다.', function () {
    let message = 'Outer';

    function getMessage() {
      return message;
    }

    function shadowGlobal() {
      let message = 'Inner';
      return message;
    }

    function shadowGlobal2(message) {
      return message;
    }

    function shadowParameter(message) {
      message = 'Do not use parameters like this!';
      return message;
    }

    expect(getMessage()).to.equal('Outer');
    expect(shadowGlobal()).to.equal('Inner');
    expect(shadowGlobal2('Parameter')).to.equal('Parameter');
    expect(shadowParameter('Parameter')).to.equal('Do not use parameters like this!');
    expect(message).to.equal('Outer');
  });

  it('default parameter에 대해 확인합니다.', function () {
    function defaultParameter(num = 5) {
      return num;
    }

    expect(defaultParameter()).to.equal(5);
    expect(defaultParameter(10)).to.equal(10);

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

    expect(pushNum(10)).to.deep.equal([10]);
    expect(pushNum(20)).to.deep.equal([20]);
    expect(pushNum(4, [1, 2, 3])).to.deep.equal([1,2,3,4]);
  });

  it('클로저(closure)에 대해 확인합니다.', function () {
    function increaseBy(increaseByAmount) {
      return function (numberToIncrease) {
        return numberToIncrease + increaseByAmount;
      };
    }

    const increaseBy3 = increaseBy(3);
    const increaseBy5 = increaseBy(5);

    expect(increaseBy3(10)).to.equal(13);
    expect(increaseBy5(10)).to.equal(15);
    expect(increaseBy(8)(6) + increaseBy(5)(9)).to.equal(28);

    /*
    mdn에 따르면 클로저의 정의는 다음과 같습니다. 반드시 기억하시기 바랍니다.
      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

      A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

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

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

    클로저는 내부(inner) 함수가 외부(outer) 함수의 지역 변수에 접근할 수 있습니다.
    이를 유념하시고 클로저의 유즈 케이스를 검색해 보시기 바랍니다. 아래 검색 키워드를 활용합니다.
      function factories
      namespacing private variables/functions
    */
  });

  it('lexical scope와 closure에 대해 다시 확인합니다.', function () {
    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();

      expect(age).to.equal(26);
      expect(name).to.equal('jimin');

      return innerFn;
    }

    const innerFn = outerFn();

    expect(age).to.equal(27);
    expect(name).to.equal('jimin');
    expect(innerFn()).to.equal(178);
  });
});


화살표 함수


describe('화살표 함수에 관해서', function () {
  it('함수 표현식 사용법을 복습합니다', function () {
    const add = function (x, y) {
      return x + y
    }

    expect(add(5, 8)).to.eql(13)
  })

  it('화살표 함수 사용법을 익힙니다', function () {
    // function 키워드를 생략하고 화살표 => 를 붙입니다
    const add = (x, y) => {
      return x + y
    }
    expect(add(10, 20)).to.eql(30)

    // 리턴을 생략할 수 있습니다
    const subtract = (x, y) => x - y
    expect(subtract(10, 20)).to.eql(-10)

    // 필요에 따라 소괄호를 붙일 수도 있습니다
    const multiply = (x, y) => (x * y)
    expect(multiply(10, 20)).to.eql(200)

    // 파라미터가 하나일 경우 소괄호 생략이 가능합니다
    const divideBy10 = x => x / 10
    expect(divideBy10(100)).to.eql(10)
  })

  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'}>`)
  })
})


배열(Array)

describe('Array에 대해서 학습합니다.', function () {
  it('Array의 기본을 확인합니다.', function () {
    const emptyArr = [];
    expect(typeof emptyArr === 'array').to.equal(false);
    expect(emptyArr.length).to.equal(0);

    const multiTypeArr = [
      0,
      1,
      'two',
      function () {
        return 3;
      },
      { value1: 4, value2: 5 },
      [6, 7],
    ];
    expect(multiTypeArr.length).to.equal(6);
    expect(multiTypeArr[0]).to.equal(0);
    expect(multiTypeArr[2]).to.equal('two');
    expect(multiTypeArr[3]()).to.equal(3);
    expect(multiTypeArr[4].value1).to.equal(4);
    expect(multiTypeArr[4].value2).to.equal(5);
    expect(multiTypeArr[5][1]).to.equal(7);
  });

  it('Array의 요소(element)를 다루는 방법을 확인합니다.', function () {
    const arr = [];
    expect(arr).to.deep.equal([]);

    arr[0] = 1;
    expect(arr).to.deep.equal([1]);

    arr[1] = 2;
    expect(arr).to.deep.equal([1, 2]);

    arr.push(3);
    expect(arr).to.deep.equal([1, 2, 3]);

    const poppedValue = arr.pop();
    expect(poppedValue).to.equal(3);
    expect(arr).to.deep.equal([1, 2]);
  });

  it('Array 메소드 slice를 확인합니다.', function () {
    const arr = ['peanut', 'butter', 'and', 'jelly'];

    expect(arr.slice(1)).to.deep.equal(['butter','and','jelly']);
    expect(arr.slice(0, 1)).to.deep.equal(['peanut']);
    expect(arr.slice(0, 2)).to.deep.equal(['peanut','butter']);
    expect(arr.slice(2, 2)).to.deep.equal([]);
    expect(arr.slice(2, 20)).to.deep.equal(['and','jelly']);
    expect(arr.slice(3, 0)).to.deep.equal([]);
    expect(arr.slice(3, 100)).to.deep.equal(['jelly']);
    expect(arr.slice(5, 1)).to.deep.equal([]);

    // arr.slice는 arr의 값을 복사하여 새로운 배열을 리턴합니다.
    // 아래의 코드는 arr 전체를 복사합니다. 자주 사용되니 기억하시기 바랍니다.
    expect(arr.slice(0)).to.deep.equal(['peanut', 'butter', 'and', 'jelly']);
  });

  it('Array를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
    // call(pass) by value와 call(pass) by reference의 차이에 대해서 학습합니다.
    const arr = ['zero', 'one', 'two', 'three', 'four', 'five'];

    function passedByReference(refArr) {
      refArr[1] = 'changed in function';
    }
    passedByReference(arr);
    expect(arr[1]).to.equal('changed in function');

    const assignedArr = arr;
    assignedArr[5] = 'changed in assignedArr';
    expect(arr[5]).to.equal('changed in assignedArr');

    const copiedArr = arr.slice();
    copiedArr[3] = 'changed in copiedArr';
    expect(arr[3]).to.equal('three');
  });

  it('Array 메소드 shift와 unshift를 확인합니다.', function () {
    const arr = [1, 2];

    arr.unshift(3);
    expect(arr).to.deep.equal([3, 1, 2]);

    const shiftedValue = arr.shift();
    expect(shiftedValue).to.deep.equal(3);
    expect(arr).to.deep.equal([1, 2]);
  });
});



객체(object)


describe('Object에 대해서 학습합니다.', function () {
  /*
    이번 과제에서는 객체의 기본적인 내용을 재확인합니다.
    이머시브 과정에서 객체를 보다 자세하게 학습하게 됩니다. (예. prototype)
  */
  it('Object의 기본을 확인합니다.', function () {
    const emptyObj = {};
    expect(typeof emptyObj === 'object').to.equal(true);
    expect(emptyObj.length).to.equal(undefined);

    const megalomaniac = {
      mastermind: 'Joker',
      henchwoman: 'Harley',
      getMembers: function () {
        return [this.mastermind, this.henchwoman];
      },
      relations: ['Anarky', 'Duela Dent', 'Lucy'],
      twins: {
        'Jared Leto': 'Suicide Squad',
        'Joaquin Phoenix': 'Joker',
        'Heath Ledger': 'The Dark Knight',
        'Jack Nicholson': 'Tim Burton Batman',
      },
    };

    expect(megalomaniac.length).to.equal(undefined);
    expect(megalomaniac.mastermind).to.equal('Joker');
    expect(megalomaniac.henchwoman).to.equal('Harley');
    expect(megalomaniac.henchWoman).to.equal(undefined);
    expect(megalomaniac.getMembers()).to.deep.equal(['Joker','Harley']);
    expect(megalomaniac.relations[2]).to.equal('Lucy');
    expect(megalomaniac.twins['Heath Ledger']).to.deep.equal('The Dark Knight');
  });

  it('Object의 속성(property)를 다루는 방법을 확인합니다.', function () {
    const megalomaniac = { mastermind: 'Agent Smith', henchman: 'Agent Smith' };

    expect('mastermind' in megalomaniac).to.equal(true);

    megalomaniac.mastermind = 'Neo';
    expect(megalomaniac['mastermind']).to.equal('Neo');

    expect('secretary' in megalomaniac).to.equal(false);

    megalomaniac.secretary = 'Agent Smith';
    expect('secretary' in megalomaniac).to.equal(true);

    delete megalomaniac.henchman;
    expect('henchman' in megalomaniac).to.equal(false);
  });

  it("'this'는 method를 호출하는 시점에 결정됩니다.", function () {
    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;
      },
    };

    expect(currentYear).to.equal(2023);
    expect(megalomaniac.calculateAge(currentYear)).to.equal(53);

    megalomaniac.birthYear = 2000;
    expect(megalomaniac.calculateAge(currentYear)).to.equal(23);

    megalomaniac.changeBirthYear(2010);
    expect(megalomaniac.calculateAge(currentYear)).to.equal(13);

  /**
   * !!Advanced [this.mastermind]? this.birthYear? this가 무엇일까요?
   * 
   * method는 '어떤 객체의 속성으로 정의된 함수'를 말합니다. 위의 megalomaniac 객체를 예로 든다면,
   * getMembers는 megalomaniac 객체의 속성으로 정의된 함수인 '메소드'라고 할 수 있습니다. megalomaniac.getMembers()와 같은 형태로 사용(호출)할 수 있죠.
   * 사실은, 전역 변수에 선언한 함수도 웹페이지에서 window 객체의 속성으로 정의된 함수라고 할 수 있습니다. 
   * window. 접두사 없이도 참조가 가능하기 때문에(window.foo()라고 사용해도 됩니다.), 생략하고 쓰는 것뿐입니다. 이렇듯, method는 항상 '어떤 객체'의 method입니다.
   * 따라서 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this입니다.
   * 예시로, obj이라는 객체 안에 foo라는 메서드를 선언하고, this를 반환한다고 했을 때 ( 예: let obj = {foo: function() {return this}}; )
   * obj.foo() === obj 이라는 코드에 true라고 반환할 것입니다.
   * this는 함수의 호출에 따라서 값이 달라지기도 합니다. (apply나 call, bind에 대해서는 하단의 학습자료를 통해 더 공부해 보세요.)
   * 
   * 그러나 화살표 함수는 다릅니다. 자신의 this가 없습니다.
   * 화살표 함수에서의 this는 자신을 감싼 정적 범위(lexical context)입니다. (전역에서는 전역 객체를 가리킵니다.)
   * 일반 변수 조회 규칙(normal variable lookup rules)을 따르기 때문에, 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수 바로 바깥 범위에서 this를 찾습니다.
   * 그렇기에 화살표 함수를 사용할 때, 이러한 특이점을 생각하고 사용해야 합니다.
   * 
   * 이와 관련하여, this에 대해서 더 깊이 학습하셔도 좋습니다.
   * 가이드가 될 만한 학습자료를 첨부합니다.
   * https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this
   */
  });

  it('객체의 method를 정의하는 방법을 확인합니다.', function () {
    const megalomaniac = {
      mastermind: 'Brain',
      henchman: 'Pinky',
      getFusion: function () {
        return this.henchman + this.mastermind;
      },
      battleCry(numOfBrains) {
        return `They are ${this.henchman} and the` + ` ${this.mastermind}`.repeat(numOfBrains);
      },
    };

    expect(megalomaniac.getFusion()).to.deep.equal('PinkyBrain');
    expect(megalomaniac.battleCry(3)).to.deep.equal('They are Pinky and the Brain Brain Brain');
  });

  it('Object를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
    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',
      },
    };

    function passedByReference(refObj) {
      refObj.henchwoman = 'Adam West';
    }
    passedByReference(obj);
    expect(obj.henchwoman).to.equal('Adam West');

    const assignedObj = obj;
    assignedObj['relations'] = [1, 2, 3];
    expect(obj['relations']).to.deep.equal([1, 2, 3]);

    const copiedObj = Object.assign({}, obj);
    copiedObj.mastermind = 'James Wood';
    expect(obj.mastermind).to.equal('Joker');

    obj.henchwoman = 'Harley';
    expect(copiedObj.henchwoman).to.equal('Adam West');

    delete obj.twins['Jared Leto'];
    expect('Jared Leto' in copiedObj.twins).to.equal(false);

    /*
    마지막 테스트 코드의 결과가 예상과는 달랐을 수도 있습니다.
    'Object.assign'을 통한 복사는 reference variable은 주소만 복사하기 때문입니다. 
    이와 관련하여 얕은 복사(shallow copy)와 깊은 복사(deep copy)에 대해서 학습하시기 바랍니다.
    가이드가 될 만한 학습자료를 첨부합니다.
      https://scotch.io/bar-talk/copying-objects-in-javascript
      https://medium.com/watcha/깊은-복사와-얕은-복사에-대한-심도있는-이야기-2f7d797e008a
    */
  });
});


전개문법(SpreadSyntax)

describe('Spread syntax에 대해 학습합니다.', function () {
  it('전개 문법(spread syntax)을 학습합니다.', function () {
    const spread = [1, 2, 3];
    // TODO: 전개 문법을 사용해 테스트 코드를 완성합니다. spread를 지우지 않고 해결할 수 있습니다.
    const arr = [0, ...spread, 4];
    expect(arr).to.deep.equal([0, 1, 2, 3, 4]);
  });

  it('빈 배열에 전개 문법을 사용할 경우, 아무것도 전달되지 않습니다.', function () {
    const spread = [];
    // TODO: 전개 문법을 사용해 테스트 코드를 완성합니다. spread를 지우지 않고 해결할 수 있습니다.
    const arr = [0, ...spread, 1];
    expect(arr).to.deep.equal([0, 1]);
  });

  it('여러 개의 배열을 이어붙일 수 있습니다.', function () {
    const arr1 = [0, 1, 2];
    const arr2 = [3, 4, 5];
    const concatenated = [...arr1, ...arr2];
    expect(concatenated).to.deep.equal([0, 1, 2, 3, 4, 5]);
    // 아래 코드도 같은 동작을 수행합니다.
    //  arr1.concat(arr2);
  });

  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, 데이터)이 복사된 것이라면, shallow copy일까요, deep copy일까요?

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

  it('Rest Parameter는 함수의 전달인자를 배열로 다룰 수 있게 합니다.', function () {
    // 자바스크립트는 (named parameter를 지원하지 않기 때문에) 함수 호출 시 전달인자의 순서가 중요합니다.
    function returnFirstArg(firstArg) {
      return firstArg;
    }
    expect(returnFirstArg('first', 'second', 'third')).to.equal('first');

    function returnSecondArg(firstArg, secondArg) {
      return secondArg;
    }
    expect(returnSecondArg('only give first arg')).to.equal(undefined);

    // rest parameter는 spread syntax를 통해 간단하게 구현됩니다.
    function getAllParamsByRestParameter(...args) {
      return args;
    }

    // arguments를 통해 '비슷하게' 함수의 전달인자들을 다룰 수 있습니다. (spread syntax 도입 이전)
    // arguments는 모든 함수의 실행 시 자동으로 생성되는 '객체'입니다.
    function getAllParamsByArgumentsObj() {
      return arguments;
    }

    const restParams = getAllParamsByRestParameter('first', 'second', 'third');
    const argumentsObj = getAllParamsByArgumentsObj('first', 'second', 'third');

    expect(restParams).to.deep.equal(['first','second','third']);
    expect(Object.keys(argumentsObj)).to.deep.equal(['0','1','2']);
    expect(Object.values(argumentsObj)).to.deep.equal(['first', 'second', 'third']);

    // arguments와 rest parameter를 통해 배열로 된 전달인자(args)의 차이를 확인하시기 바랍니다.
    expect(restParams === argumentsObj).to.deep.equal(false);
    expect(typeof restParams).to.deep.equal('object');
    expect(typeof argumentsObj).to.deep.equal('object');
    expect(Array.isArray(restParams)).to.deep.equal(true);
    expect(Array.isArray(argumentsObj)).to.deep.equal(false);

    const argsArr = Array.from(argumentsObj);
    expect(Array.isArray(argsArr)).to.deep.equal(true);
    expect(argsArr).to.deep.equal(['first', 'second', 'third']);
    expect(argsArr === restParams).to.deep.equal(false);
  });

  it('Rest Parameter는 전달인자의 수가 정해져 있지 않은 경우에도 유용하게 사용할 수 있습니다.', function () {
    function sum(...nums) {
      let sum = 0;
      for (let i = 0; i < nums.length; i++) {
        sum = sum + nums[i];
      }
      return sum;
    }
    expect(sum(1, 2, 3)).to.equal(6);
    expect(sum(1, 2, 3, 4)).to.equal(10);
  });

  it('Rest Parameter는 전달인자의 일부에만 적용할 수도 있습니다.', function () {
    // rest parameter는 항상 배열입니다.
    function getAllParams(required1, required2, ...args) {
      return [required1, required2, args];
    }
    expect(getAllParams(123)).to.deep.equal([123, undefined, []]);

    function makePizza(dough, name, ...toppings) {
      const order = `You ordered ${name} pizza with ${dough} dough and ${toppings.length} extra toppings!`;
      return order;
    }
    expect(makePizza('original')).to.equal(`You ordered undefined pizza with original dough and 0 extra toppings!`);
    expect(makePizza('thin', 'pepperoni')).to.equal(`You ordered pepperoni pizza with thin dough and 0 extra toppings!`);
    expect(makePizza('napoli', 'meat', 'extra cheese', 'onion', 'bacon')).to.equal(`You ordered meat pizza with napoli dough and 3 extra toppings!`);
  });
});



구조 분해 할당(Destructuring Assignment)

describe('구조 분해 할당(Destructuring Assignment)에 관해서', () => {
  it('배열을 분해합니다', () => {
    const array = ['code', 'states', 'im', 'course']

    const [first, second] = array
    expect(first).to.eql('code')
    expect(second).to.eql('states')

    const result = []
    function foo([first, second]) {
      result.push(second)
      result.push(first)
    }

    foo(array)
    expect(result).to.eql(['states', 'code'])
  })

  it('rest/spread 문법을 배열 분해에 적용할 수 있습니다', () => {
    const array = ['code', 'states', 'im', 'course']
    const [start, ...rest] = array
    expect(start).to.eql('code')
    expect(rest).to.eql(['states', 'im' , 'course'])

    // 다음과 같은 문법은 사용할 수 없습니다. 할당하기 전 왼쪽에는, rest 문법 이후에 쉼표가 올 수 없습니다
    // const [first, ...middle, last] = array
  })

  it('객체의 단축(shorthand) 문법을 익힙니다', () => {
    const name = '김코딩'
    const age = 28

    const person = {
      name,
      age,
      level: 'Junior',
    }
    expect(person).to.eql({name,
      age,
      level: 'Junior'})
  })

  it('객체를 분해합니다', () => {
    const student = { name: '박해커', major: '물리학과' }

    const { name } = student
    expect(name).to.eql('박해커')
  })

  it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #1', () => {
    const student = { name: '최초보', major: '물리학과' }
    const { name, ...args } = student

    expect(name).to.eql('최초보')
    expect(args).to.eql({major: '물리학과'})
  })

  it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #2', () => {
    const student = { name: '최초보', major: '물리학과', lesson: '양자역학', grade: 'B+' }

    function getSummary({ name, lesson: course, grade }) {
      return `${name}님은 ${grade}의 성적으로 ${course}을 수강했습니다`
    }

    expect(getSummary(student)).to.eql('최초보님은 B+의 성적으로 양자역학을 수강했습니다')
  })

  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
    })
  })
})



expect 함수

expect(테스트하는값).기대하는조건

expect(isEven(3)).to.be.true => 'isEven(3)'의 결과값은 참(true)이어야 한다'
expect(1 + 2).to.equal(3) => 'sum(1, 2)의 결과값은 3과 같아야(equal) 한다'

기대하는조건'에 해당하는 함수를 matcher라고 합니다.
참인 것이어야 한다' => to.be.true
3과 같아야 한다' => to.equal(3)


.equal 과 .deep.equal


얕은 복사 vs 깊은 복사

자바스크립트에서 값은 원시값 과 참조값 두가지 데이터 타입이 존재합니다.

  • 원시값은 기본 자료형(단순한 데이터)을 의미합니다. Number, String, Boolean, Null, Undefined 등이 해당합니다. 변수에 원시값을 저장하면 변수의 메모리 공간에 실제 데이터 값이 저장됩니다. 할당된 변수를 조작하려고 하면 저장된 실제 값이 조작됩니다.

  • 참조값은 여러 자료형으로 구성되는 메모리에 저장된 객체입니다. Object, Symbol 등이 해당합니다. 변수에 객체를 저장하면 독립적인 메모리 공간에 값을 저장하고, 변수에 저장된 메모리 공간의 참조(위치 값)를 저장하게 됩니다. 그래서 할당된 변수를 조작하는 것은 사실 객체 자체를 조작하는 것이 아닌, 해당 객체의 참조를 조작하는 것입니다

깊은 복사


const a = 'a';
let b = 'b';
 
b = 'c';
 
console.log(a); // 'a';
console.log(b); // 'c';
 
// 기존 값에 영향을 끼치지 않는다.

원시값을 복사할 때 그 값은 또 다른 독립적인 메모리 공간에 할당하기 때문에, 복사를 하고 값을 수정해도 기존 원시값을 저장한 변수에는 영향을 끼치지 않습니다. 이처럼 실제 값을 복사하는 것을 깊은 복사라고 합니다. 하지만 이것은 자료형을 깊은 복사한 것입니다.

얖은 복사

const a = {
  one: 1,
  two: 2,
};
let b = a;
 
b.one = 3;
 
console.log(a); // { one: 3, two: 2 } 출력
console.log(b); // { one: 3, two: 2 } 출력
 
// 기존 값에 영향을 끼친다.

참조값을 복사할 때는 변수가 객체의 참조를 가리키고 있기 때문에 복사된 변수 또한 객체가 저장된 메모리 공간의 참조를 가리키고 있습니다. 그래서 복사를 하고 객체를 수정하면 두 변수는 똑같은 참조를 가리키고 있기 때문에 기존 객체를 저장한 변수에 영향을 끼칩니다. 이처럼 객체의 참조값(주소값)을 복사하는 것을 얕은 복사라고 합니다.

얕은 복사 방법

Array.prototype.slice()

(start부터 end 인덱스까지 기존 배열에서 추출하여 새로운 배열을 리턴하는 메소드 입니다)

Object.assign(생성할 객체, 복사할 객체)

(메소드의 첫 번째 인자로 빈 객체를 넣어주고 두 번째 인자로 복사할 객체를 넣어주면 됩니다)

Spread 연산자 (전개 연산자)

깊은 복사 방법

JSON.parse && JSON.stringify

( JSON.stringify()는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 모두 끊어집니다. 객체를 json 문자열로 변환 후, JSON.parse()를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어줍니다. 이 방법이 가장 간단하고 쉽지만 다른 방법에 비해 느리다는 것과 객체가 function일 경우, undefined로 처리한다는 것이 단점입니다)

재귀 함수를 구현한 복사
(복잡하다는 것이 단점)

Lodash 라이브러리 사용

( 라이브러리를 사용하면 더 쉽고 안전하게 깊은 복사를 할 수 있습니다. 설치를 해야 한다는 점과 일반적인 개발에는 효율적이겠지만, 코딩 테스트에는 사용할 수 없다는 것이 단점입니다.)


this 정의

this 는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가르키는 자기 참조 변수이다. this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메소드를 참조할 수 있다.

-> 정의를 살펴 포는 '또는'이라는 표현이 사용된 것을 볼 수 있는데 이는 this 가 고정된 값에 바인딩 되지 않기 때문이다. this는 함수가 호출되는 방식에 따라 동적으로 결정된다.

여기서 바인딩이란?

바인딩(binding)이란 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위를 의미합니다. 예를 들어 함수를 호출하는 부분에서 실제 함수가 위치한 메모리를 연결하는 것도 바로 바인딩입니다.
this의 호출 방식에 따라 this가 특정 객체에 연결되는 것으로
this 바인딩은
1. 일반 함수 내부
2. 메서드 내부
3. 생성자 함수 내부
4. call, apply, bind를 통한 호출 방식


정리

이처럼 this는 함수 호출 방식에 따라 동적으로 결정되고 예시로 살펴본 것처럼 함수를 일반 함수로 호출할 경우 this는 전역 객체를, 메서드로 홀출할 경우 호출한 객체를, 생성자 함수를 호출할 경우 생성자 함수가 생성할 인스턴스를 가르키게 됩니다. call, apply, bind 메소드 사용시 메서드에 첫 번째 인수를 전달하는 객체에 바인딩 됩니다.


Array.from() 메서드

Array.from() 은 문자열 등 유사 배열(Array-like) 객체나 이터러블한 객체를 배열로 만들어주는 메서드입니다.

(※ 유사 배열 객체란, 키가 인덱스 값으로 되어있고 길이를 나타내는 length 속성을 갖는 객체를 의미합니다.)


// 1. 문자열을 배열로 만드는 예시
console.log(Array.from("Hello"));
// [ 'H', 'e', 'l', 'l', 'o' ]

// 2. 유사 배열 객체를 배열로 만드는 예시
console.log(Array.from({ 0: "찬민", 1: "희진", 2: "태인", length: 3 }));
// [ '찬민', '희진', '태인' ]

// 3. 함수의 매개변수들을 순서대로 배열로 만드는 예시
const funcA = (...arguments) => {
    return Array.from(arguments)
}

console.log(funcA(1,2,3,4,5));
// [ 1, 2, 3, 4, 5 ]

Array.from() 의 첫 번째 인자는 배열로 만들 이터러블한 객체가 되며, 두 번째 인자는 생성한 배열의 모든 원소에 대해 수행할 맵핑 함수입니다.

(Array.map() 이라고 생각하시면 됩니다.)

equal과 deep equal 차이

profile
더욱더 QA스럽게!

0개의 댓글