제대로 파는 자바스크립트(JavaScript) - by 얄코
(https://www.inflearn.com/course/제대로-파는-자바스크립트/dashboard)
목차
12-1. 렉시컬과 클로저
12-2. this의 동적 바인딩
12-3. this의 정적 바인딩
12-4. 중간점검 퀴즈
변수나 상수가 코드상 어디에서 지정되었는가에 따라 그 사용 범위를 결정
함수가 코드상 어디에서 정의되었는가에 따라 그 상위 스코프를 결정
호출한 곳을 기준으로 하는 동적 스코프(dynamic scope)와 상반되는 개념
정의된 블록을 기준으로 상위 스코프의 값이 사용됨
(호출 기준이 아님!!)
- func2을 호출한 블록에서의 y 값은 2
- func2가 정의된 블록에서의 y 값은 1
func2를 func1 안으로 옮기면
전체 문서, 함수, 블록을 실행하기 전 만들어지는 내부 객체
각 스코프의 고유 값들과 외부 스코프에 대한 참조를 포함
구성 요소
환경 레코드 environment record: 해당 스코프의 데이터들
외부 렉시컬 환경에 대한 참조 outer lexical environment reference
(참고) 3-1강. 블록문과 스코프 (https://siwon-swu18.tistory.com/77)
내부 함수에서 외부 함수의 값에 접근할 수 있다는 개념 (함수 중첩 시)
logHello에는 func1 안의 함수인 func2가 반환되어 지정됨
func1의 실행이 끝났음에도 불구하고, 해당 스코프 내의 값(word = 'Hello')이 살아있음
func2와 func2가 선언된 환경(fun1의 스코프)의 조합: 클로저
단지 값을 복사해서 갖는 것이 아니라, 해당 값이 저장되는 외부 환경 자체가 유지됨
private field 흉내내기
(참고) this vs 지역 변수 정리
- this: 공개 상태 관리 (외부 접근 가능)
- 지역 변수: 은닉 상태 관리 (외부 접근 불가)
기본적으로 자신이 속한 곳을 가리킴: 문맥 context
함수의 호출 방식에 따라 가리키는 바가 달라짐 (자바스크립트 특성)
1. 전역에서의 this
브라우저 콘솔창: window 객체 (globalThis와 같음)
Node.js의 REPL: global 객체 (globalThis와 같음)
.js 문서로 작성 후 실행: 빈 객체 (Node.js에서 각 .js 문서들은 모듈로서 실행되기 때문)
2. 함수 안에서의 this
느슨한 모드와 엄격 모드에서 다르게 동작
객체에 속하지 않은 함수에서는 this 사용이 의미 없음
3. 객체 안에서의 this (일단 화살표 함수 제외)
a. 객체 리터럴: 해당 객체를 가리킴
b. 생성자 함수: 생성될 인스턴스를 가리킴
c. 클래스 선언: 생성될 인스턴스를 가리킴
자바스크립트의 독특한 동작 방식
this가 가리키는 대상이 함수의 호출 주체 또는 그 방식에 따라 달라짐
함수가 누가, 어떻게 호출되었는가에 따라 this가 가리키는 대상이 달라짐
해결 방법
1. call을 사용한 함수 호출
this의 대상과 인자들을 나열
2. apply를 사용한 함수 호출
this의 대상과 인자들을 담은 배열을 넘김
3. bind를 사용한 this 대상 고정
this의 대상이 동적으로 변하지 않는 함수를 반환
4. 바인딩된 함수를 내보내는 함수
5. 생성자 함수일 경우: 함수 자체를 미리 인스턴스에 바인딩하기
call, apply, bind의 다른 활용
배열 메서드의 thisArg
콜백으로 주어진 함수 내에서 this가 가리킬 대상
보통 콜백함수 다음 인자로 넣음
(참고) 메서드 체이닝 과정
0. 객체 선언: peter로 확인1. map: idx 속성 추가
2. filter: sex는 (this.sex 또는 'U')이고, size는 this.size인 요소 필터링 3. forEach: 필터링된 각 요소를 돌며 console.log 출력 (++idx: 인덱스 +1해서 출력)
객체의 메서드 종류별 비교
로그로 출력된 형태 비교
생성자 역할 수행 여부 비교
function 선언 함수만 생성자로 활용 가능 (더 많은 기능이 있고 무겁다는 의미)
ES6 함수 구분
생성자 역할 | 프로토타입 | arguments | super | |
function 선언 함수 | O | O | O | X |
메서드 | X | X | O | O |
화살표 함수 | X | X | X | X |
화살표 함수와 this
function 함수나 메서드의 동적 바인딩과 다르게 동작
함수가 어디서 선언되었는가를 따름 (가장 근접한 상위 스코프에 바인딩 고정)
this를 정적으로 바인딩함
함수 종류 this 결정 기준 일반 함수 함수를 호출한 객체 (call site) 화살표 함수 정의된 위치의 this (lexical)
( 자신을 감싸고 있는 가장 가까운 일반 함수(function)의 this를 가져온다. )
1. 객체 리터럴에서
객체 리터럴의 화살표 함수는 가리키는 기본 스코프가 나머지 둘과 다름
2. 생성자 함수와 클래스에서
기본적으로는 가리키는 대상이 동일(해당 인스턴스)
동적으로 바인딩하는 타 방식과의 차이 확인
생성자 함수
클래스
class 문법에서도 화살표 함수의 this는 선언 시점에 고정된다.
class 자체도 결국은 함수이므로 내부 동작은 function 기반 생성자와 크게 다르지 않다.
(class는 문법적 설탕(syntactic sugar)일 뿐이며, 실제 동작은 함수 기반 생성자와 거의 같다.)
화살표 함수는 call, apply, bind의 this 인자가 무시됨
이미 this가 정적으로 바인딩되어 있기 때문
Node.js 파일 실행 시 빈 객체가 출력된 이유
Node.js는 각 파일을 모듈로 만들어 실행
파일 내 모든 코드는 모듈의 메서드 안으로 들어가 실행됨. 즉, 객체 내 함수의 코드가 됨
(참고) 위의 코드에서 this 결정 기준
함수 유형 선언 위치 기준 호출 방식 기준 결과 this 최상단 코드 O X 모듈 객체 ({}) 일반 함수 X O 전역 객체 (strict면 undefined) 화살표 함수 O X 상위 스코프의 this 캡처
(참고) Node.js에서는 각 파일이 내부적으로 다음과 같이 감싸져 실행된다.
(function(exports, require, module, __filename, __dirname) { // 작성한 코드가 여기에 위치 })();
1. 클로저를 활용해서, 함수 생성시 인자로 주어진 수를 함수 실행시 인자로 주어진 수와 곱한 결과를 반환하는 코드를 작성해보세요.
// 사용 예시
const multiplyByTwo = multiplyBy(2);
const multiplyByFive = multiplyBy(5);
console.log(multiplyByTwo(3)); // 6
console.log(multiplyByFive(3)); // 15
예상 답:
function multiplyBy(a) {
return function(b) {
return a * b;
};
}
정답:
function multiplyBy(firstNumber) {
return function(secondNumber) {
return firstNumber * secondNumber;
};
}
2. width 와 height 프로퍼티를 갖고 있는 객체들이 각자의 넓이를 출력할 때 사용할 수 있는 외부 함수를 만들고, 이를 사용하는 코드를 작성해보세요.
const rectangle1 = {
width: 10,
height: 5
};
const rectangle2 = {
width: 4,
height: 3
};
예상 답:
function getArea() {
this.area = this.width * this.height;
console.log(this.area);
}
rectangle1.getArea = getArea;
rectangle2.getArea = getArea;
rectangle1.getArea();
rectangle2.getArea();
정답:
// 방법 1. 메서드로 할당
function printArea() {
const area = this.width * this.height;
console.log(`Area: ${area}`);
}
const rectangle1 = {
width: 10,
height: 5,
printArea: printArea // 메서드로 할당
};
const rectangle2 = {
width: 4,
height: 3,
printArea: printArea // 메서드로 할당
};
rectangle1.printArea(); // Area: 50
rectangle2.printArea(); // Area: 12
// 방법 2. call() 메서드 이용
function printArea() {
const area = this.width * this.height;
console.log(`Area: ${area}`);
}
const rectangle1 = {
width: 10,
height: 5,
};
printArea.call(rectangle1); // Area: 50
// 방법 3. 메서드 추가
function printArea() {
const area = this.width * this.height;
console.log(`Area: ${area}`);
}
const rectangle1 = {
width: 10,
height: 5,
};
rectangle1.printArea = printArea;
rectangle1.printArea(); // Area: 50
3. 어떤 값이 출력될까요?
class Obj1 {
constructor (name) {
this.name = name;
this.arrowFunc = () => this.name;
}
normFunc () { return this.name }
}
class Obj2 {
constructor (name) {
this.name = name;
}
}
const obj1 = new Obj1('Apple');
const obj2 = new Obj2('Banana');
obj2.arrowFunc = obj1.arrowFunc;
obj2.normFunc = obj1.normFunc;
console.log(
obj2.arrowFunc(),
obj2.normFunc()
);
예상 답: Apple Banana
정답: Apple Banana (일반 함수나 메소드의 this는 동적으로, 화살표 함수의 this는 정적으로 바인딩됩니다.)
FIN.
[자바스크립트] 섹션 11. 문제들에 대비하기 (0) | 2025.04.13 |
---|---|
[자바스크립트] 섹션 10. 이터러블과 제너레이터 (0) | 2025.04.13 |
[자바스크립트] 섹션 9. 추가 자료형들 (0) | 2025.04.08 |
[자바스크립트] 섹션 8. 객체 깊게 다루기 (0) | 2025.04.08 |
[자바스크립트] 섹션 7. 배열 (0) | 2025.04.02 |
댓글 영역