개발일지

2023.10.23. TIL JavaScript 3주차 (2)

이경욱 2023. 10. 23. 20:43

실행 컨텍스트

실행할 코드의 환경 정보를 모아놓은 객체

 

 

1. 배경지식

(1) 콜스택이란?

Stack : Last in, first out (LIFO)

Queue : First in, first out (FIFO)

 

 

(2) 함수 정의방식

 

A. 함수 선언문

function a () { /* ... */ }
a(); // 실행 ok

B. 익명 함수 표현식

var b = function () { /* ... */ }
b(); // 실행 ok

C. 기명 함수 표현식

var c = function d () { /* ... */ } 
c(); // 실행 ok
d(); // 에러!

(3) 함수 vs 메서드

독립성을 기준으로 차이가 납니다.

a. 함수명();

b. 객체.메서드명();

구분 기준 : [] or .

 

(4) AS-IS, TO-BE

AI-IS는 기존,

TO-BE는 다음을 뜻한다.

 

 

2. 역할

1. 호이스팅 (선언된 변수를 위로 끌어올림)

2. 외부 환경 정보 구성

3. this 값 설정

 

 

3. 담기는 정보

(1) Variable Environment (VE)

현재 컨텍스트 내 식별자 정보 (ex. var a)

외부 환경 정보 (ex. outer)

선언 시점 Lexical Environment의 snapshot

 

(2) Lexical Environment (LE)

Variable Environment와 동일하지만, 변경사항을 실시간으로 반영

 

*VE와 LE는 완벽하게 동일하다. 그러나 스냅샷 유지 여부는 다르다.

VE는 유지 / LE는 실시간으로 변경사항 반영

 

LE는 다음 2가지 정보를 가지고 있다.

A. record (=environmentRecord)

이 record의 수집과정이 hoisting

 

호이스팅이란?

자바스크립트 엔진이 함수 안에서 변수들을 수집하는 과정

 

법칙1 : 매개변수 및 변수는 선언부를 호이스팅합니다.

법칙2 : 함수 선언은 전체, 변수는 선언부만 호이스팅합니다.

// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

// 변수는 선언부만 hoisting
var multiply; 

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { // 변수의 할당부는 원래 자리
	return a + b;
};

협업을 많이할수록 함수 표현식을 활용해서 코드를 사용한 시점의 아래부터 함수가 적용되도록 해야한다.

그래야 위에 다른 사람들이 작성된 코드에 함수가 영향을 미치지 않기 때문

 

B. outer (=outerEnvironmentReference)

outer는 함수가 선언될 시점의 LE를 참조한다.

 

스코프란?

식별자에 대한 유효범위

 

스코프 체인은?

식별자의 유효범위 (=스코프)를 안에서 밖으로 차례대로 찾아나가는 것

(위 배경지식 부분 콜스택 참조) 

 

스택이 쌓일수록 이전 LE 를 가지고 있던 정보까지 함께 포함되어

inner컨텍스트는 전역컨텍스트까지 record를 읽어올 수 있다.

(ex. 전역 : a  =>  outer : a / b  =>  inner : ab / c)

 

 

(3) This Binding

this 식별자가 바라봐야할 객체

전역환경에서 this는 => 노드(global 객체), 브라우저(window 객체)

 

함수의 this는 전역객체,

메서드의 this는 메서드가 호출의 주체가 된다.

 

함수의 호출로 나온 this는 스택이 쌓여있더라도(메서드 내부에 있더라도) 전역객체를 가리킨다.

따라서, 메서드 내부 함수에서의 this 우회 방법이 있다.

 

A. this 우회 방법

 

a. 변수 활용

var self = this;
var innerFunc2 = function() {
    console.log(self); // (3) outer
};
innerFunc2();

b. 화살표 함수

this를 바인딩하지 않는 함수이다.

그래서 this가 이전의 값(상위값)이 유지된다.

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제때문에 화살표 함수를 도입했다.

 

 

B. 콜백함수의 this

콜백함수란?

어떠한 함수, 메서드의 인자(매개변수)로 넘겨주는 함수

 

콜백함수도 함수이다.

따라서 this는 전역객체를 참조하지만,

넘겨받은 함수에서 별도로 this를 지정한 경우에

예외적으로 그 대상을 참조하게 되어있다.

 

콜백함수의 this는 따로 해야하는 게 아니고 그런 특징을 가진 함수가 정해져 있다.

 

예를 들어,

setTimeout 함수와 forEach 메서드는

콜백함수를 호출할 때 this를 지정하지 않으므로 전역객체를 바라보고,

addEventListner 메서드는 콜백함수 호출 시, 자산의 this를 상속하므로

button 태그가 된다.

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});

// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});

 

C. 생성자 함수의 this

생성자란?

구체적인 인스턴스를 만들기 위한 일종의 틀

var Cat = function (name, age) {
	this.bark = '야옹';
	this.name = name;
	this.age = age;
};

var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5);  //this : nabi

새로운 인스턴스를 만들 때마다 this는 달라진다.

 

D. 명시적 this 바인딩

자동으로 부여되는 상황별 this의 규칙을 깨고

별도의 값을 저장하는 방법

 

a. call 메서드

var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

 

b. apply 메서드

var func = function (a, b, c) {
	console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

 

c. all / apply 메서드

- 유사배열객체 (array-like-object)에 배열 메서드를 적용

유사배열객체?

인덱스와 길이까지 가지고 있지만, 배열처럼 push와 slice 같은 메서드는 사용할 수 없다.

하지만, call 또는 apply 메서드를 통해서 비슷한 기능을 사용할 수 있다.

즉시실행함수이기 때문에  this바인딩하는 자리에 해당 유사배열객체를 넣어주고

배열 메서드 기능을 수행하게한다 

 

 

- Array.form 메서드 (ES6)

Array.form() 에 배열을 넣어주기만 하면 되기때문에 아주 편리한 방법이다.

 

 

- 생성자 내부에서 다른 생성자를 호출 (공통된 내용의 반복제거)

function Person(name, gender) {
	this.name = name;
	this.gender = gender;
}
function Student(name, gender, school) {
	Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
	this.school = school;
}
function Employee(name, gender, company) {
	Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
	this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');

.call로 기존 틀을 불러와서 새로운 값을 더해줄 수 있다.

 

 

- 여러 인수를 묶어 하나의 배열로 전달

//효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);

// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);

 

 

d. bind 메서드

- 예시

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

- name 프로퍼티

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);

// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func