Skip to content

[week3_HAEUN] 22장, 24장 퀴즈#8

Open
sonnnnhe wants to merge 1 commit intomainfrom
week3_HAEUN
Open

[week3_HAEUN] 22장, 24장 퀴즈#8
sonnnnhe wants to merge 1 commit intomainfrom
week3_HAEUN

Conversation

@sonnnnhe
Copy link
Collaborator

@sonnnnhe sonnnnhe commented Nov 10, 2025

Q1. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.


var name = 'SOHYUN';

const person = {
  name: 'JISUNG',
  printName() {
    console.log(this);
    setTimeout(function() {
        console.log("callback's this.name: ", this.name);
    }, 100);
  }
};

const anotherPerson = {
  name: 'NAEUN'
};
anotherPerson.printName = person.printName;

console.log(person.printName());
console.log(anotherPerson.printName());

(a) 코드의 실행 결과와 그 이유를 간략히 서술하시오.
(b) 위 코드가 문맥에 맞게 동작할 수 있도록 (= 콜백 함수가 헬퍼 함수의 역할을 제대로 수행할 수 있도록) 수정하시오.


Q2. OX 문제


(1) 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 일반적으로 모든 함수를 클로저라고 칭한다. (O/X)
(2) 자바스크립트 객체의 모든 프로퍼티와 메서드는 기본적으로 public하다. (O/X)


Q3. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.


function createAgeTracker(ageModifier) {
  let age = 23;
    
  return function () {
    age = ageModifier(age);
    return age;
  };
}
function eatTteokguk(curAge) {
    return ++curAge;
}
function undoEatingTteokguk(curAge) {
    return --curAge;
}

const getOlder = createAgeTracker(eatTteokguk);
const getYounger = createAgeTracker(undoEatingTteokguk);

console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getYounger());
console.log(getYounger());
console.log(getYounger());  // 하은의 기대: 24

하은이는 먹은 떡국 그릇 수만큼 나이를 먹는다는 말에 속아, 코드 상에서라도 자신의 나이를 되돌리기 위해 떡국을 먹기 전으로 돌아갈 수 있는 프로그램을 만들고 싶었다.
그러나 하은의 기대와 달리 나이의 증감이 연동되지 않는 문제점이 발생하였다. 우울해하는 하은을 위해 문제점이 발생한 이유와 해결 방법을 서술하시오.


Q4. 코드를 바탕으로 이어지는 질문에 대답하시오.


function Button(props) {
    const clickHandler = () => {
        console.log(props.id);
    };
    
    return <button onClick={clickHandler}>클릭</button>;
}

위 코드에서 clickHandler 함수가 props.id 값을 정상적으로 참조할 수 있는 이유를 컴포넌트 렌더링 시점과 이벤트 발생 시점으로 나누어 상세히 설명하시오.

@mimizae
Copy link
Collaborator

mimizae commented Nov 11, 2025

Q1. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

(a) 코드의 실행 결과와 그 이유를 간략히 서술하시오.

실행 결과는 다음과 같다.

{ name: 'JISUNG', printName: f }
callback's this.name:  SOHYUN
{ name: 'NAEUN', printName: f }
callback's this.name:  SOHYUN

먼저 코드를 보면, 전역 변수 name은 'SOHYUN'이고, person 객체 안에는 name: 'JISUNG'과 printName 메서드가 정의되어 있다.
printName 메서드는 자신을 호출한 객체를 가리키는 this를 console.log(this)로 출력하고, 이어서 setTimeout 안의 콜백 함수에서 this.name을 출력한다.

여기서 핵심!!!!!!은 콜백 함수 내부의 this가 무엇을 가리키는가이다.
자바스크립트에서 setTimeout의 콜백은 별도의 일반 함수로 호출되기 때문에, 그 내부의 this는 기본적으로 전역 객체를 가리킨다.
따라서, 전역 스코프에 있는 var name = 'SOHYUN'이 window.name으로 등록되어 있기 때문에, 콜백 내부에서는 this.name이 'SOHYUN'으로 출력된다.

요 과정을 순서대로 보자면

  1. person.printName() 호출 시,
  • this는 person 객체를 가리킨다.
  • 따라서 첫 번째 console.log(this)에서는 person 객체 전체가 출력된다.
  • 그러나 setTimeout 안의 익명 함수에서 this는 전역 객체를 가리키므로, "callback's this.name: SOHYUN"이 출력된다.
  1. anotherPerson.printName()을 호출하면,
  • 이번엔 this가 anotherPerson 객체를 가리킨다.
  • 그래서 첫 번째 로그는 anotherPerson 객체가 출력된다.
  • 하지만 똑같이 콜백 내부의 this는 전역 객체로 바인딩되므로, 두 번째 로그는 역시 "callback's this.name: SOHYUN"이 출력된다.

(b) 위 코드가 문맥에 맞게 동작할 수 있도록 (= 콜백 함수가 헬퍼 함수의 역할을 제대로 수행할 수 있도록) 수정하시오.

콜백 함수가 헬퍼 함수의 역할을 제대로 수행할 수 있도록 수정하려면, setTimeout 내부의 콜백 함수가 printName 메서드의 문맥(this)을 그대로 유지해서, person이든 anotherPerson이든 자신이 속한 객체의 name을 출력하게 만들어야 한다!!

이는 화살표 함수를 사용하면 해결 된다.

printName() {
  console.log(this);
  setTimeout(() => {
    console.log("callback's this.name:", this.name);
  }, 100);
}

화살표 함수는 자신만의 this를 가지지 않고, 바깥 스코프(printName의 this)를 그대로 사용하므로 올바르게 동작할 것이다.

Q2. OX 문제

(1) 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 일반적으로 모든 함수를 클로저라고 칭한다. (X)

  • 중첩 함수가 상위 함수보다 더 오래 유지되긴 하지만 상위 스코프의 어떤 식별자도 참조하지 않을 시, 모던 브라우저는 최적화를 통해 상위 스코프를 기억하지 않으므로 클로저라고 하지 않는다.
  • 중첩 함수가 상위 스코프의 식별자를 참조하고 있다면? 클로저라고 할 수 있겠지만 외부 함수 foo의 밖으로 중첩 함수가 반환되지 않는다면 이 역시 클로저라고 보기 어렵다. (외부 함수보다 생명 주기가 짧다)
  • 외부 함수보다 중첩 함수가 더 오래 살아남고, 상위 스코프의 식별자 x를 참조하고 있는 경우만을 클로저로 한정하는 것이 일반적이다.

(2) 자바스크립트 객체의 모든 프로퍼티와 메서드는 기본적으로 public하다. (O)

Q3. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

(떡국 제가 만들어드릴게요;; 멍청한 렉시컬 환경!!)

하은이가 기대한 대로 나이의 증감이 서로 연동되지 않는 이유는, getOlder와 getYounger가 서로 다른 렉시컬 환경을 가지고 있기 때문이다.

함수 createAgeTracker는 호출될 때마다 새로운 실행 컨텍스트와 렉시컬 환경을 생성한다.
이 내부에는 let age = 23이라는 지역 변수가 존재하고, createAgeTracker에서 반환되는 함수는 이 변수를 클로저로서 참조한다.

그런데 문제는, createAgeTracker가 두 번!! 호출되고 있다는 점이다!!!!

const getOlder = createAgeTracker(eatTteokguk);
const getYounger = createAgeTracker(undoEatingTteokguk);

이렇게 하면 getOlder는 첫 번째 createAgeTracker 호출에서 만들어진 렉시컬 환경을, getYounger는 두 번째 호출에서 만들어진 완전히 별개의 렉시컬 환경을 참조하게 된다.

즉, 두 함수는 각각 자신만의 독립된 age 변수를 가진 클로저가 되는 것이다.

결과적으로 getOlder를 여러 번 호출해 나이가 증가하더라도, getYounger가 참조하는 age는 그와는 전혀 다른, 초기값 23을 갖는 또 다른 변수를 바라보기 때문에 두 함수 간의 나이 변화가 연동되지 않는다.

이 문제를 해결하려면, 두 함수가 하나의 동일한 age 변수를 공유하도록 해야 한다.

createAgeTracker가 두 번 호출될 때마다 새로운 렉시컬 환경을 만들지 않도록 하나의 공통된 클로저 환경을 가지게 만들어야 한다.

이를 위해 즉시 실행 함수를 이용할 수 있다.
즉시 실행 함수를 통해 한 번만 age 변수를 생성하고, 그 안에서 getOlder와 getYounger 두 함수를 함께 정의하여 둘 다 동일한 age 변수를 참조하도록 하면 된다.

Q4. 코드를 바탕으로 이어지는 질문에 대답하시오.

위 코드에서 clickHandler 함수가 props.id 값을 정상적으로 참조할 수 있는 이유를 컴포넌트 렌더링 시점과 이벤트 발생 시점으로 나누어 상세히 설명하시오.

결론부터 말하자면 클로저 덕분에 렌더링 시점의 변수 값이 이벤트 발생 시점에도 유지되어 접근 가능한 것이다.

먼저, 렌더링 시점에 React는 Button 컴포넌트를 호출하여 JSX를 해석하고, 그 과정에서 함수 내부의 모든 변수와 함수 선언이 평가된다.

이때 props는 부모 컴포넌트로부터 전달된 인자 값으로, clickHandler 함수는 props 변수를 자신의 상위 스코프에서 참조하는 클로저로서 정의된다.

즉, clickHandler는 만들어질 때 이미 자신이 참조해야 할 props가 포함된 렉시컬 환경을 기억하게 된다!!

그다음, 이벤트 발생 시점에 사용자가 버튼을 클릭하면, React는 이전에 등록된 clickHandler를 호출한다.

이때 clickHandler 함수는 새로 정의되는 것이 아니라, 렌더링 시점에 생성되어 기억하고 있던 그 동일한 함수!!!!!다.
따라서 함수 내부에서 props.id를 접근할 때, 자신이 생성될 당시의 렉시컬 환경 안에 저장되어 있던 props를 그대로 참조할 수 있다.

결국 clickHandler가 별도로 props를 전달받지 않았음에도 정상적으로 props.id 값을 출력할 수 있는 이유는, 이 함수가 컴포넌트 렌더링 시점의 스코프를 클로저로서 기억하고 있기 때문이다.

즉, 렌더링 시점에는 props가 함수의 상위 스코프에 존재하고, 이벤트 발생 시점에는 clickHandler가 그 렉시컬 환경을 통해 props.id를 안정적으로 참조하게 된다.

@Tnalxmsk
Copy link
Collaborator

Tnalxmsk commented Nov 11, 2025

Q1. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

(a)

1. { name: 'JISUNG', printName: [Function: printName] }
2. undefined
3. { name: 'NAEUN', printName: [Function: printName] }
4. undefined
5. callback's this.name:  strict 혹은 node 환경이면 undefined 브라우저 환경이라면 SOHYON
6. callback's this.name:  strict 혹은 node 환경이면 undefined 브라우저 환경이라면 SOHYON
  1. printName의 첫 번째 console.log(this)는 객체 자신을 가리키는 this
    setTimeout의 this.name은 실행 환경에 따라 this가 다르게 바인딩됨
  2. person.printName()은 return 하지 않으므로 console.log(person.printName())은 undefined 로그를 찍음
  3. anotherPerson을 가리키는 this
  4. 2번과 동일하게 anotherPerson.printName()의 호출 반환값은 undefined
  5. setTimeout 내부의 콜백은 일반 함수로 호출되므로 this 바인딩이 사라짐. 브라우저의 일반 스크립트에서는 this === window가 되어 window.name은 'SOHYUN' 이 찍히지만,
    ES 모듈이나 Node.js 환경에서는 전역 변수 name이 전역 객체에 등록되지 않아 this.name이 undefined가 됨
  6. 6번은 5 동일

추가적으로 setTimeout 의 콜백은 태스크 큐에 들어가고 이벤트 루프에 의해 실행되어 콜 스택이 비워진 후 마지막에 처리되어 5, 6 제일 마지막에 처리되어 log 출력

(b) 위 코드가 문맥에 맞게 동작할 수 있도록 (= 콜백 함수가 헬퍼 함수의 역할을 제대로 수행할 수 있도록) 수정하시오.

...
// bind를 활용한다.
const person = {
  name: 'JISUNG',
  printName() {
    console.log(this);
    setTimeout(function () {
      console.log("callback's this.name: ", this.name);
    }.bind(this), 100); // bind를 통한 this 바인딩
  },
};

// 2. 화살표 함수를 활용한다.
const person = {
  name: 'JISUNG',
  printName() {
    console.log(this);
    setTimeout(() => {
      console.log("callback's this.name: ", this.name);
    }, 100);
  },
};

...

// 불필요한 console.log 제거
person.printName();
anotherPerson.printName();

Q2. OX 문제

  1. X
  2. O

Q3. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

function createAgeTracker(ageModifier) {
  let age = 23;
    
  return function () {
    age = ageModifier(age);
    return age;
  };
}
function eatTteokguk(curAge) {
    return ++curAge;
}
function undoEatingTteokguk(curAge) {
    return --curAge;
}

const getOlder = createAgeTracker(eatTteokguk);
const getYounger = createAgeTracker(undoEatingTteokguk);

console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getYounger());
console.log(getYounger());
console.log(getYounger());  // 하은의 기대: 24

이유
createAgeTracker 각각 호출하여 콜백 함수를 전달하고 있음
현재는 각각 다른 렉시컬 환경을 생성하여 참조값이 다른 객체가 됨

해결방법
핵심은 클로저가 하나의 변수를 공유하도록 구현하는 것

  • 즉시 실행 함수를 활용하고 객체의 함수로 등록한다.
const counter = (function createAgeTracker(ageModifier) {
  let age = 23;

  return {
    eatTteokguk: function () {
      return ++age;
    },
    undoEatingTteokguk: function () {
      return --age;
    },
  };
})();

console.log(counter.eatTteokguk());
console.log(counter.eatTteokguk());
console.log(counter.eatTteokguk());
console.log(counter.eatTteokguk());
console.log(counter.undoEatingTteokguk());
console.log(counter.undoEatingTteokguk());
console.log(counter.undoEatingTteokguk());  // 하은의 기대: 24

클래스 static 활용도 가능

  • static을 활용하면 생성자 없이 유틸성으로 활용이 가능하다.
class Counter {
  static #age = 23;

  static eatTteokguk() {
    return ++this.#age;
  }

  static undoEatingTteokguk() {
    return --this.#age;
  }
}

console.log(Counter.eatTteokguk());
console.log(Counter.eatTteokguk());
console.log(Counter.eatTteokguk());
console.log(Counter.eatTteokguk());
console.log(Counter.undoEatingTteokguk());
console.log(Counter.undoEatingTteokguk());
console.log(Counter.undoEatingTteokguk());

Q4. 코드를 바탕으로 이어지는 질문에 대답하시오.

function Button(props) {
    const clickHandler = () => {
        console.log(props.id);
    };
    
    return <button onClick={clickHandler}>클릭</button>;
}

위 코드에서 clickHandler 함수가 props.id 값을 정상적으로 참조할 수 있는 이유를 컴포넌트 렌더링 시점과 이벤트 발생 시점으로 나누어 상세히 설명하시오.

@Sohyunnnn
Copy link
Collaborator

Q1. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.


var name = 'SOHYUN';

const person = {
  name: 'JISUNG',
  printName() {
    console.log(this);
    setTimeout(function() {
        console.log("callback's this.name: ", this.name);
    }, 100);
  }
};

const anotherPerson = {
  name: 'NAEUN'
};
anotherPerson.printName = person.printName;

console.log(person.printName());
console.log(anotherPerson.printName());

(a) 코드의 실행 결과와 그 이유를 간략히 서술하시오.

{ name: 'JISUNG', printName: [Function: printName] }
callback's this.name:  SOHYUN
{ name: 'NAEUN', printName: [Function: printName] }
callback's this.name:  SOHYUN

person.printName() 실행 시 this는 person 객체를 가리킴(jisung)

하지만 setTimeout 내부의 콜백은 일반 함수로 호출되므로, this 전역 객체를 가리킴(sohyun)

anotherPerson.printName() 실행 시 this는 anotherPerson 객체를 가리킴 (naeun)

그러나 setTimeout 콜백은 전역에서 실행되어 sohyun

(b) 위 코드가 문맥에 맞게 동작할 수 있도록 (= 콜백 함수가 헬퍼 함수의 역할을 제대로 수행할 수 있도록) 수정하시오.

setTimeout(() => {
  console.log("callback's this.name:", this.name);
}, 100);

Q2. OX 문제


(1) 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 일반적으로 모든 함수를 클로저라고 칭한다. (X)

(2) 자바스크립트 객체의 모든 프로퍼티와 메서드는 기본적으로 public하다. (O)

Q3. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.


function createAgeTracker(ageModifier) {
  let age = 23;

  return function () {
    age = ageModifier(age);
    return age;
  };
}
function eatTteokguk(curAge) {
    return ++curAge;
}
function undoEatingTteokguk(curAge) {
    return --curAge;
}

const getOlder = createAgeTracker(eatTteokguk);
const getYounger = createAgeTracker(undoEatingTteokguk);

console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getOlder());
console.log(getYounger());
console.log(getYounger());
console.log(getYounger());  // 하은의 기대: 24

하은이는 먹은 떡국 그릇 수만큼 나이를 먹는다는 말에 속아, 코드 상에서라도 자신의 나이를 되돌리기 위해 떡국을 먹기 전으로 돌아갈 수 있는 프로그램을 만들고 싶었다.

그러나 하은의 기대와 달리 나이의 증감이 연동되지 않는 문제점이 발생하였다. 우울해하는 하은을 위해 문제점이 발생한 이유와 해결 방법을 서술하시오.

실행결과

24
25
26
27
22
21
20

getOlder와 getYounger은 각각 독립된 실행 컨텍스트에서 생성된 별개의 클로저
즉, 두 함수가 서로 다른 렉시컬 환경을 캡쳐하고 있기 때문에 내부의 age 값 공유 되지 않음.
getOlder의 age는 23-27로 증가하지만, getYounger는 X

해결 방법
같은 age 변수 클로저를 공유해야함.

function createAgeTracker() {
  let age = 23;

  return {
    getOlder() {
      age++;
      return age;
    },
    getYounger() {
      age--;
      return age;
    }
  };
}

const tracker = createAgeTracker();
console.log(tracker.getOlder()); // 24
console.log(tracker.getOlder()); // 25
console.log(tracker.getYounger()); // 24

Q4. 코드를 바탕으로 이어지는 질문에 대답하시오.


function Button(props) {
    const clickHandler = () => {
        console.log(props.id);
    };

    return <button onClick={clickHandler}>클릭</button>;
}

위 코드에서 clickHandler 함수가 props.id 값을 정상적으로 참조할 수 있는 이유를 컴포넌트 렌더링 시점과 이벤트 발생 시점으로 나누어 상세히 설명하시오.

렌더링 시점
Button 컴포넌트가 렌더링될 때, clickHandler 함수는 함수 내부에서 정의
이 시점에서 clickHandler는 props 객체가 존재하는 렉시컬 환경을 캡쳐. 따라서 props.id를 기억할 수 있는 클로저가 만들어짐.

이벤트 발생 시점

사용자가 버튼을 클릭하면 clickHandler가 실행.
이때 button 컴포넌트의 스코프를 다시 만드는 게 아니라, 이미 존재하는 clickHandler 함수를 호출.
따라서 함수는 자신이 정의될 당시의 렉시컬 환경을 그대로 참조하여 props.id값을 정확히 읽어올 수 있음

@eunkr82
Copy link
Collaborator

eunkr82 commented Nov 11, 2025

Q1. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

(a)

{ name: 'JISUNG', printName: ƒ }
undefined
{ name: 'NAEUN', printName: ƒ }    
undefined
callback's this.name:  SOHYUN   // strict mode, Node.js에서는 undefined    
callback's this.name:  SOHYUN   // strict mode, Node.js에서는 undefined    
  • printName() 호출. 여기서의 this는 person이므로 person 객체가 찍힌다.
  • printName()은 값을 반환하지 않으므로 undefined 출력
  • console.log(anotherPerson.printName()); 호출. 위와 동일하게 여기서의 this는 anotherPerson이므로 anotherPerson 객체가 추력된다.
  • 콜백은 일반 함수 호출로 실행되므로, this는 전역 객체를 가리킨다. 따라서 브라우저에서는 SOHYUN이, strict mode 혹은 node.js 환경에서는 undefined가 출력된다.

(b)
콜백에서도 메서드와 같은 this를 유지해야 하므로, 화살표 함수를 사용하거나 bind를 사용할 수 있다.

setTimeout(() => {
  console.log("callback's this.name: ", this.name);
}, 100);

혹은

setTimeout(function() {
  console.log("callback's this.name: ", this.name);
}.bind(this), 100);

Q2. OX 문제

(1) X
JS의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저라 할 수 있지만, 일반적으로 외부 변수를 전혀 참조하지 않는 함수까지 클로저라고 하지는 않는다.

(2) O

Q3. 다음 코드를 바탕으로 이어지는 질문에 대답하시오.

getOlder와 getYounger가 서로 다른 렉시컬 환경을 가지기 때문에, 같은 age를 공유하지 않아 기대하는 값인 24가 출력되지 않는다.

즉시 실행 함수를 사용해 age 변수를 한 번만 생성해 동일한 age를 공유하도록 하는 방식으로 해결할 수 있다.

Q4.코드를 바탕으로 이어지는 질문에 대답하시오.

  • 함수 컴포넌트가 렌더링될 때, clickHandler 함수 객체가 새로 생성되고, 이 함수는 현재 렌더의 props를 클로저로 캡처한다. (해당 렌더의 props를 기억한다.)
  • 클릭 이벤트가 발생하면, react는 clickHandler를 호출한다. clickHandler는 자신이 생성되었을 때의 렌더에서 props를 꺼내 props.id를 출력한다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants