TIL_19 / 20일차

minjun kim·2024년 10월 15일
0

3-6 부터 진행

실행 컨텍스트

환경 정보들을 모아놓은 객체

  1. 호이스팅 선언된 변수를 위로 끌어올림
  2. 외부 환경 정보를 구성
  3. this 값을 설정

실행 컨텍스트란?

콜 스택 : 바구니 (LIFO) -> 마지막에 들어간게 처음으로 나옴
큐 : 원통 (FIFO) -> 처음으로 들어간게 첫번째로 나감

콜 스텍

실행할 코드에 제공할 환경 정보를 모아놓은 객체
콜스텍에 쌓아올립니다. 다 실행되고 나면 없어짐

call stack 시작되자마자
전역 컨텍스트가 시작되고
outer()가 실행되고 중단이되고 쌓인다.
그 다음에 outer 함수 내부에 inner() 함수가 호출되는 걸 보면
outer 컨텍스트가 중단되고 inner 컨텍스트가 쌓인다.
다 실행되고 나면 innerout 빠지고 outer가 다 실행되면 out 되고
전역도 out 해서 순서대로 빠져나간다.
(코드 실행할 때 call stack을 한번씩 그려보기)


그럼 실행 컨텍스트에는 뭐가 들어 있나?

ariableEnvironment
현재 컨텐스트 내의 식별자 정보 = record)
var a = 3이라 할 때 var a를 의미
외부 환경 정부(= outer)

(생길 때 그 모습을 그대로 간직한다 (snapshot))

LexicalEnvironment

VE와 동일하지만 , 변경사항을 실시간으로 반영해요.
즉 실시간으로 반영되는 LE를 쓴다. (VE와 똑같다. 실시간이나 아니냐의 차이)

ThisBinding

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

VariableEnvironment , LexicalEnvironment

VE: 스냅샷을 유지한다 (실시간으로 바뀌지 않는다.)
LE: 스냅샷을 유지하지 않는다 (실시간으로 변경사항을 계속 반영한다.)
VE LE는 동일하다.

LexicalEnvironment - environmentRecord(=record)호이스팅 식별자 정보들이 저장된다.
수집 대상 정보: 함수에 지정된 매개변수, 식별자, 함수 자체 , var로 선언된 변수 식별자 등

(수집이 된다는건 호이스팅이 된다와 똑같다.)

컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집

순서대로 수집한다고 했지 코드가 실행한다고 하지는 않았다.

호이스팅 식별자 정보를 수집할 때 이 호이스팅 개념이 나오고
이건 가상 개념이다 그저 이해하기 쉽게 설명하려고 만든

호이스팅 규칙

호이스팅이 식별정보를 맨 위로 낚아 끌어 올리는걸 말함
식별자 정보만 위로 다 끌어올리는데 이게 레코드를 수집하는 과정을 의미한다.

법칙1. 매개변수나 변수는 선언부를 호이스팅 한다.
이 개념을 예제를 보면 예상한대로 값이 안나온다. (3주차 호이스팅)

function a () {
	console.log(b);
	var b = 'bbb';
	console.log(b);
	function b() { }
	console.log(b);
}

예시로 위에서부터 순서대로 읽으면
첫 콘솔은 underfined , 두번째는 bbb , 세번째는 function이 나와야 하는데

function a () {
	var b; // 변수 선언부 호이스팅
	function b() { } // 함수 선언은 전체를 호이스팅

	console.log(b);
	b = 'bbb'; // 변수의 할당부는 원래 자리에

	console.log(b);
	console.log(b);
}

실제로는 이렇게 호이스팅 과정을 거쳐 나온다는 것이다.
첫 콘솔은 function 두번째는 bbb , 세번째도 bbb

그러나 함수라고해서 모두 호이스팅 되지는 않는다.
JS 엔진이 그 함수 안에서 변수들을 수집하는 과정을 호이스팅이라 불렀고,
호이스팅 과정에서 변수의 선언부 함수는 위로 끌어올려졌다는 느낌.

조심해야되는 호이스팅

함수라고 해서 모두 호이스팅 되지 않는다.
함수 정의의 3가지 방식

정의부만 존재 , 할당 명령이 없는 경우가
함수 선언문이라 부른다.

별도의 변수에 함수를 할당하는게 함수 표현식이다
여기에 익명 함수 표현식과 기명 함수 표현식 두가지가 있따.
(기명 함수 표현식은 => 거의 활용이 없다.)

실행 컨텍스트는 실행할 코드에 제공할 환경 정보를 모아놓은 객체고
우리가 지금 하고 있는게 실행 컨텍스트 이다.

LErecordouter 두가지를 보유하고 있다.
우리가 지금 하고 있는게 recordhoisting 이다.

함수 선언문은 전체를 위로 끌어 올린다.
(그 자체로 선언이기 때문에 올라온다.)
그러나 함수 표현식은 변수 부분만 위로 끌어 올린다.

그러나 함수 선언문을 주의해야 하는 이유는?
함수 선언문으로 짜면 hoisting에 의해서 전체 영역에 쓰였는데,
나중에 신입이 5000 번째 줄에서 10군데 쓰일 수 있는
함수 선언문을 기존에 쓰인 똑같은 이름을 사용했는데
함수 선언문의 내용이 조금 달랐는데 이게 아래에 영향을 끼치지 않고
hoisiting에 의해 전체 코드에 영향을 미치게 시작했다.

그러나 함수 표현식이였다면?
함수 선언부만 위로 올라가기 때문에 밑에 내용이 영향을 주지 않는다.
그래서 함수 표현식을 활용하는 습관을 들이도록 합시다.
그냥 선언해야지 협업할 때 사고가 안난다고 생각하자

LexicalEnvironment - outerEnvironmentReference(=outer)

1. 스코프
식별자에 대한 유효범위를 의미
대부분 언어에서 존재한다. JS에서도 존재한다

2. 스코프 체인
outer는 현재 호출된 함수가 선언될 당시에 외부환경 정보를 가지고 있다.
(call stack에서 항상 처음에 저장되는건 전역 컨텍스트다.)

A가 만들어질 때 전역컨텍스트의 환경정보를 가지고 있고
여기서 B도 A와 같은 정보를 가지고 그 위에 C는 B의 정보를 가지기 때문에
동일한 LE에 대한 정보를 가지고 있다.

쉽게 설명하자면

var a = 1;
var outer = function() {
	var inner = function() {
		console.log(a); // 이 값은 뭐가 나올지 예상해보세요! 이유는 뭐죠? scope 관점에서!
		var a = 3;
	};
	inner();
	console.log(a); // 이 값은 또 뭐가 나올까요? 이유는요? scope 관점에서!
};
outer();
console.log(a); // 이 값은 뭐가 나올까요? 마찬가지로 이유도!

여기서 전역 컨텍스트 밑에 깔리고
outer(); 실행 되니까 2번째로 오고
inner(); 실행 되니까 3번째로 온다
근데 여기서 inner()가 실행되면서
비로소 맨 처음 실행되는 consoleinner() 안에 console이다
당연하게도 underfined가 찍힌다.

왜? 밑에 var a = 3;가 호이스팅 되면서
var a; -> console log(a) -> a = 3; 순서로 호이스팅 되기 때문에
consolea함수inner 함수 내부에서 해결함 (스코프 체인과 상관없음)
inner() 실행이 끝나면 사라지고
다음 실행되는게 console.log(a)인데
a는 inner 안의 a = 3을 찍을 수 없다
왜냐면 이미 inner()는 콜스텍에서 사라져버렸기 때문에...

여기서 참조한 a는 무조건 바깥 즉 전역 영역에서 가져올 수 있는
var a = 1; 밖에 가져올 수 없다.

outer();var a = 1 은 똑같은 전역에 존재하고 있고,
inner()outer() 안에 있기 때문에 전역의 값을 받지 못한다.

각각의 실행 컨텍스트는 LE 안에 recordouter를 가지고 있고,
outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니
scope chain에 의해 상위 컨텍스트의 record를 읽어 올 수 있다.

outer 입장에서는 전역 context가 된다.

호이스팅이랑 상관없이 변수는 자기 블럭 안을 먼저 보고
없으면 바깥으로 나가고 또 없으면 바깥으로 나가고 outer의 개념

var호이스팅 때문에 햇갈려서 더이상 안쓴다
letconst호이스팅이 있긴한데 에러를 비춰준다.


this

Javascript에서는 this가 독특함
어떤 상황에선 a이기도 하고 b이기도 하다.

상황에 따라 달라지는 this
(실행 컨텍스트를 다루고 있고, 실행할 환경정보를 모아놓은 객체)

이제 마지막과정인 ThisBindings를 배운다.

this는 함수를 호출할 때 결정된다.

함수 vs 메서드
(두 가지의 차이점은 독립성)
함수는 그 자체로 독집적인 기능으로 수행한다.

함수는 그 자체로 실행이 가능하고 (호출 주체없이 스스로 실행)

메서드는 자신을 호출한 대상 객체에 대한 동작을 수행하니
메서드는 종속된 친구라는 거다.(누군가 어떤 객체가 실행시켜줘야 함)

함수랑 메서드는 호출의 주체가
함수는 없고 메서드는 있기 때문에

함수는 this 가 전역 객체다
메서드는 this 가 호출의 객체다

function(1) 의 this는 node에선 global이 된다.

var obj = {
	method: func,
}
obj.method(2) 의 this는 {method: f} 가 된다.

즉 호출의 주체가 있냐 없냐로 구분하는데
이 구분하는 방법이 [대괄호] 혹은 . (점) 으로 구분할 수 있다.

var obj = {
	methodA: function () { console.log(this) },
	inner: {
		methodB: function() { console.log(this) },
	}
};
obj.methodA();             // this === obj
obj['methodA']();          // this === obj

obj.inner.methodB();       // this === obj.inner
obj.inner['methodB']();    // this === obj.inner
obj['inner'].methodB();    // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner

함수로서 호출할 때 그 함수 내부에서의 this (함수 자체로)
함수 내부에서의 this는 항상 전역객체를 가리킨다.

메서드는 호출주체가 O고 , 함수는 호출주체가 X
(node 커멘드 라인이 만들어졌으면 ctrl + C , ctrl + D)

function(); 이 형태로 호출되면 this는 전역객체
객체.fucntion(); 이 형태로 호출되면 this는 객체를 호출


this를 우회하는 방법 , 콜백 함수의 this , 생성자함수 this

this 대해서 이해는 하지만 이게 이해가 되나?
함수를 다 호출하다 보면 흐름이 있는건데
객체. 안했다고 this가 전역객체를 다 보지 않고

우회할 수 있지 않나? 라고 생각한 방법들

1. 변수를 활용한 방법 내부 스코프에 이미 존재하는 this를 별도의 변수에 할당하는 방법

var obj1 = {
	outer: function() {
		console.log(this); // (1) { outer: ƒ }

		// AS-IS
		var innerFunc1 = function() {
			console.log(this); // (2) 전역객체
		}
		innerFunc1();

		// TO-BE
		var self = this;
		var innerFunc2 = function() {
			console.log(self); // (3) { outer: ƒ }
		};
		innerFunc2();
	}
};

메서드 호출 부분
obj1.outer();

여기서 보면 TO-BE를 자세히 보면 되는데
let self = this; 하고 , console에도 self를 부르고 innerFunc2(); 함수로 바로 실행하면
obj1. 의 객체부분을 콘솔에 비춰준다 outer : f

번외 개발자로 일하면
AS-IS는 말은 이제 기존 것 , TO-BE는 이후 것 이라는 말입니다.

우회하는 두번째 방법 바로 화살표 함수

ES6 내부에서 this가 전역 객체를 바라보는 문제 때문에
즉 유실하는 문제 때문에 화살표 함수를 들여왔다고 해도 과언이 아니다.

this binding 여부가 가장 두드러진 차이라고 할 수 있다.
화살표 함수는this binding자체를 생략한다 아예 없는 것임

var obj = {
	outer: function() {
		console.log(this); // (1) obj
		var innerFunc = () => {
			console.log(this); // (2) obj
		};
		innerFunc();
	}
}

obj.outer();
실제로 this를 강제적으로 bind하는 방법은 명시적 바인딩이다.

다음은 콜백 함수도 함수다

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

별도 지정 없음 : 전역객체

[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});

addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
따라서 thisbutton을 의미함

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});

ThisBinding하면 모두 전역 객체가 된다
그래서 콜백함수는 무조건 기본적으로 전역 객체를 무조건 바라보게 되어있다.

단! 예외는 있다! 콜백 함수에 별도로 this를 지정한 경우
addEventListner 자신이 콜백 함수로 호출할 때 this를 상속하게 해놈
만든 사람들이 이렇게 설계하고 제작을 함.

여기서 말하는 thisaddEventListner 호출한 주체 즉 버튼을 바라보고 있다.
이런 예외 사항이 없다면 다 전역 객체를 바라보게 되어 있습니다.

콜백 함수도 함수기 때문에 예외사항은 없다.

생성자 함수

구체적인 인스턴스 (어려우면 객체로 이해해도 됨)
이런걸 만들기 위한 생성자 constructor 거기 안에서의 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

생성자 함수는 function (name, age) 저 부분의 this
인스턴스를 지칭하는건

name은 초코 age는 7을 가지는 bark는 야옹이라는 인스턴스를 생성하는데
this 는 그 인스턴스를 지칭하고,

아래의 나비 와 5를 이용해서 생성하는 this는 다른 인스턴스를 생성한다.
즉 당장 이해하기 어려우면 객체로 이해하면 좋다.


명시적 this binding

var func = function (a, b, c) {
	console.log(this, a, b, c);
};

// no binding
func(1, 2, 3);

이렇게 하면 this가 글로벌 객체를 바라보고 있다.

// 명시적 this binding
// call, apply, bind
// call 
var func = function (a, b, c) {
	console.log(this, a, b, c);
};

// no binding
// func(1, 2, 3);

// 명시적 binding
func.call({ x: 1 }, 4, 5, 6);
func.apply({x: 1},[4,5,6]);

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

// method 함수 안의 this는 항상 obj!!
// obj.method(2, 3);

obj.method.call({ a: 4 }, 5, 6);

이렇게 callapply의 방식으로 명시적 binding이다.

이걸 잘 활용하면 유사배열객체(array-like-object)에 배열 메서드를 적요

배열은 index를 가지로 다음에 length를 가지고 있다.

유사배열의 조건
1. length가 반드시 필요하다 없으면 유사배열이라고 인식 안함
2. index번호0번부터 시작해서 1씩증가해야 한다.

call 과 apply즉시 실행 함수이기 때문에
즉시 실행하면서 this binding을 하는 this 자리에 해당 유사 배열 객체를 넣어줌으로써
해당 기능을 수행하게 하는것

그래서 this binding 앞에 유사 배열 객체를 넣어준다.

call과 apply는 원래 this를 위한 기능이다.
유사 배열 객체에서 사용하는 방법은 조금 벗어난 방법인데

// 유사배열
var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3
};
// 객체 -> 배열
var arr = Array.from(obj);

// 찍어보면 배열이 출력됩니다.
console.log(arr);

ES6으로 더 좋은 방법으로 슈아배열을 사용할 수 있다.


call과 apply의 세부적인 내용들 나머지 부분들은
튜터님 조언 메모장을 보면 좋다.

Math. 에서 펼치는게 중요한 이유는
... 이 배열의 형태로 담는걸 풀어줘야 하기 때문에 중요하다

bind에서 bound라는 수동태가 있다 (bind가 되었다.)
name 프로퍼티 => 함수의 이름을 추적

console.log(func.name);
console.log(bindFunc1.name);
console.log(bindFunc2.name);

이런 경우에 1. func , 2. bound func , 3. bound func
bind가 사용되면 bound로 추적할 수 있다.

이번엔 this를 내부함수나 콜백함수로 전달하는 방법
이건 상위 컨텍스트에 전달하기 위해 self라는 변수를 활용한
To-BE 방식을 사용했는데,

편하지만 꼭 변수를 추가해야하는 하드코딩이 되기 때문에 불편하다.
그래서 call을 쓰거나 bind(this) 방식으로 고정해서 사용한다.

우회 call apply bind보다 편리한 방법
그냥 화살표 함수 할당과정이 아예 생략이 되어서

this가 함수로써 호출되어도 전역객체로 세팅이 될 일이 없다.


3-1 과제 해석

var user = {
    name: "john",
    age: 20,
}

// 객체 만들어 프로퍼티 복사하기
var getAged = function (user, passedTime) {
    var result = {};
    for (var prop in user) {
        result[prop] = user[prop];
    }
    result.age += passedTime; 
    return result;
}

여기서 객체를 복사하기 위해 result이라는 빈 객체를 지정하고
for 문에서 prop === 객체의 key , value도 접근할 수 있다.
그러나 우린 객체를 복사하기 위해 key에 접근해
result[prop] = user[prop];
빈객체의 key에 user에 key를 user의 key 갯수만큼 접근해 하나씩 넣어준다.

그 다음 result의 나이가 passedTime 만큼 먹어야 하니까
+= 는 붙이는게 아니라 값을 할당하는 것이다
그리고 그 결과값을 return해서 돌려주면 끝이다.

0개의 댓글

관련 채용 정보