Modern JS Deep Dive 22-26

MM·2022년 11월 2일

JSDeepDive

목록 보기
3/6
post-thumbnail

📃 22장. this

🔹 객체

🔸 객체란 말입니다

상태를 나타내는 프로퍼티동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조

🔸 메서드

메서드는 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이다!
프로퍼티가 함수 객체를 가리키고 있을 뿐!

동작을 나타내는 메서드는 자신이 속한 객체의 프로퍼티를 참조하고 변경할 수 있어야 한다.
👉 이때 자신이 속한 객체를 가리키는 식별자를 참조해야 한다!
👉 이것을 위해 this가 필요하다!

🔹 this

자신이 속한 객체 or 자신이 생성할 인스턴스를 가리키는 자기 참조 변수!
js엔진에 의해 암묵적으로 생성된다

🔸 생성자 함수에서의 this

function Circle(radius){
	this.radius=raiuds; //이때의 this는 생성될 인스턴스를 가리킴
}

🔸 메서드 내부에서의 this

const circle={
	radius: 0,
  getRadius*({
		console.log(this); //이때의 this는 메서드를 호출한 객체를 가리킴
	}
}

🔹 this 바인딩

  • 함수 호출 방식에 따라 동적으로 결정된다!
  • 함수 호출 시점에 결정된다!
const foo = function(){console.log(this);}

//일반 함수 호출에서의 this = 전역 객체 window
//strict mode에서는 undefined
foo(); //window

//메서드 호출에서의 this = 호출한 객체
const obj = {foo};
obj.foo(); //obj

//생성자 함수 호출에서의 this = 생성한 인스턴스
new foo(); //foo{}

🔸 콜백 함수의 this 바인딩

const persone={
	name: "홍길동", 
    foo(callback){setTeimout(callback, 100);}
    //객체 리터럴 선언시의 this는 자기 자신을 가리킨다.
}

person.foo(function(){conosle.log(this)})
//콜백함수가 일반함수로 들어가 여기서의 this는 전역객체가 된다.

😧 콜백으로 들어오는 일반함수의 this도 콜백을 가져온 함수의 this와 일치시키려면..?

bind 메서드를 이용하자!

const person={
    foo(callback){setTeimout(callback.bind(this), 100);}
}
person.foo(function(){conosle.log(this)}) //person



📃 23장. 실행 컨텍스트

실행 컨텍스트는 소스코드를 실행하는 데 필요한 환경을 제공하고
소스코드의 실행 결과를 실제로 관리한다!

🔹 실행 컨텍스트 스택

각 실행 컨텍스트들이 해당 스택에 순서대로 쌓여 코드 실행 순서를 관리한다!

🔸 실행중인 실행 컨텍스트

실행 컨텍스트 가장 상위에 존재하는 컨텍스트!


🔹 렉시컬 환경

스코프를 구분하여 식별자를 등록하고 관리한다!

🔸 구성 컴포넌트

환경 레코드

스코프에 포함된 식별자를 등록하고 바인딩된 값을 관리한다.
소스코드 타입에 따라 관리내용에 차이 존재

외부 렉시컬 환경에 대한 참조

상위 스코프(상위코드 렉시컬 환경)를 가리켜 스코프 체인 구현

this 바인딩property

전역 객체의 경우에는 [[GlobalThisValue]]가 되고, window를 참조한다!


🔹 실행 컨텍스트 생성과 식별자 검색과정

🔸 1. 전역 코드 평가 시작

전역 객체전역 실행 컨텍스트가 생성되고,
실행 컨텍스트 스택에 전역 실행 컨텍스트가 푸시된다!


🔸 2. 전역 렉시컬 환경 생성

전역 렉시컬 환경 객체가 생성되고,
전역 실행 컨텍스트의 렉시컬 환경에 전역 렉시컬 환경을 바인딩한다!


🔸 3. 전역 환경 레코드 생성

객체 환경 레코드선언적 환경 레코드가 생성되고, 전역 환경 레코드에 바인딩된다!

🥐 객체 환경 레코드

var 키워드로 선언된 것들 관리!
전역 객체와 연결되어 있어 객체 환경 레코드에 정의된 전역 변수와 함수들은 전역 객체의 프로퍼티와 메소드가 된다!

🥨 선언적 환경 레코드

let, const 키워드로 선언된 것들 관리!
따라서 let과 const는 전역이지만!ㅠ전역 객체 프로퍼티가 되지 않고 개념적인 블록 내에 존재하게 된다!


🔸 4. this 바인딩 결정

전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩된다!
여기서는 전역 객체이므로 전역 객체에 바인딩되었다!


🔸 5. 외부 렉시컬 환경에 대한 참조 결정

전역(최상위)이므로 외부 렉시컬 환경에 대한 참조(상위 스코프)는 null!


🔸 6. 전역 코드 실행

변수들에 값이 할당되고 함수가 호출된다!

🚦 식별자 결정

동일한 이름의 식별자가 다른 스코프에 존재한다면 어떤 것을 참조해야 할지 결정하는 것!
식별자를 검색할 때는 실행중인 실행 컨텍스트에서부터 식별자 검색을 실행한다!


🔸 7. 함수 호출 및 평가

함수가 호출되면 코드 실행을 일시중단하고 함수 내부로 진입하여 함수 코드를 평가한다!

함수 코드 평가 과정은 전역 코드 평가 과정과 마찬가지로..

    1. 함수 실행 컨텍스트를 생성하여 컨텍스트 스택에 푸시한다!
    2. 함수 렉시컬 환경(환경레코드, 외부 렉시컬환경 참조)을 생성한다
      • 2.1. 함수 환경 레코드를 생성한다
        👉 이때 매개변수와 지역변수가 실행 컨텍스트가 관리하는 지역 스코프에 등록된다!
        2.2. this 바인딩
        2.3. 외부 렉시컬환경 참조 결정
        👉 여기서는 상위인 전역 렉시컬 참조가 할당되어 체인이 형성된다!
    3. 런타임이 시작되어 함수 소스코드가 순차적으로 실행된다!
      • 3.1. 매개변수와 변수에 인수과 값이 할당된다!
        3.2. 식별자 결정을 위해 실행 중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색한다!
        👉 만약 없다면, 외부 렉시컬 확경에 대한 참조가 가리키는 렉시컬 환경(상위 스코프)로 이동하여 식별자를 검색한다!
    4. 함수 코드 실행 종료
      function foo(){
      	function bar(){...}
          //bar 함수 실행 컨텍스트가 실행컨텍스트스택에서 pop된다!
          //foo 함수 실행 컨텍스트가 실행중인 실행컨텍스트가 된다!
      }

😏 함수 실행 컨텍스트가 제거되어도....

bar 함수 렉시컬 환경 객체는 남아 있다! 두개는 별도의 객체니까~

😳 가비지 컬렉터가 없애지 않나요?

가비지 컬렉터는 누구에게도 참조되지 않는 값 혹은 객체의 메모리 공간을 해제한다!
따라서, foo함수 렉시컬 환경이 누군가에게 참조되어지고 있다면 소멸하지 않는다~~

👉 이것을 응용한 것이 바로 클로저고, 클로저를 응용한 것이 useState !

😚 잊지 않았죠? 렉시컬(정적) 스코프는...

함수를 어디서 호출했는지가 아니라, 어디에 정의했는지에 따라 상위 스코프를 결정합니다!
js 엔진은 이렇게 결정한 상위 스코프를 함수 객체의 내부 슬롯 [[Environment]]에 저장해요~


🔹 실행 컨텍스트와 블록 레벨 스코프

🔸 if문 코드 블록이 실행되면...

새로운 렉시컬 환경 객체가 생성되어 기존 렉시컬 환경 객체를 교체한다!
참고로.. 기존 렉시컬 환경 객체도 아직 남아있는 상태.

😨 그럼 설마 for문은..?

그렇다!! 한번 돌아갈 때마다 렉시컬 환경을 생성한다!!




📃 24장. 클로저

🔹 클로저란?

클로저는 JS고유 개념이 아니다!

함수와 그 함수가 선언된 렉시컬 환경의 조합!
함수를 일급객체로 사용하는 함수형 프로그래밍 언어에서 사용되는 특성이다.

😤 몇 번이고 말하는데요, 함수 호출 위치는...

함수의 상위 스코프에 아무런 영향을 주지 못합니다!


🔹 클로저와 렉시컬 환경

🔸 함수 실행컨텍스트가 컨텍스트 스택에서 사라져도..

내부함수의 [[Environment]]가 참조중인 외부함수 렉시컬 환경 객체는 남아 있다!

function outer(){
  const x=10;
  const inner=function(){console.log(x)};
  return inner;
}

const innerFunc=outer(); //inner를 반환하고 outer는 스택에서 pop!
innerFunc(); //inner를 실행하는데.. x가 살아 있다!

외부함수보다 중첩함수가 더 오래 유지되는 경우, 중첩함수는 이미 생명주기가 종료된 외부 함수의 변수를 참조할 수 있다!
👉 여기서 중첩함수(inner)가 바로 클로저!

🛹 자유 변수

위 예시에서 x가 바로 자유 변수!
클로저란 "자유 변수에 대해 닫혀있는 함수", 혹은 "자유 변수에 묶여있는 함수"로 표현할 수 있다.


🔹 클로저의 활용

상태를 안전하게 변경하기 위해 사용한다!

🔸 상태 은닉

특정 함수에게만 상태 변경을 허용한다!

const increase=( function(){
	let num=0;
    return function(){return ++num;} //즉시실행함수
})

increase(); //increase가 pop되어도 return된 내부함수가 실행되므로, num은 살아있다!
increase(); //즉시실행함수는 한 번만 실행되므로, 리터럴 환경 객체를 새로 생성x! 계속 같은 num!

🔗 암묵적 결합

전역 변수는 누구나 접근할 수 있고, 누구나 변경할 수 있다.

🔸 함수형 프로그래밍에서 클로저를 활용하기

function makeCount(aux){
	let count=0;
  
  return function(){ //즉시실행함수
  	counter=aux(counter);
    return counter;
  }
}

function increase(n){return ++n;}  //보조 함수
function decrease(n){return --n;}

//둘은 서로 독립된 렉시컬 환경을 가지므로, count를 공유하지 않음!
const increaser=makeCount(increase);
const decrease=makeCount(increase);

//하나의 렉시컬 환경, 하나의 자유변수를 공유하게 해야 한다
counter(increase);
counter(decrease);

👉 객체로 정의된 외부함수 수만큼 렉시컬 환경이 생성되고, 공유되지 않는다!

🔸 js에서 제한자 쓰기

const Person = (function(){
	let age = 0; //private
  function Person(propname, propage){ //생성자 함수
  	this.name=propname; //public
    age=propage;
  }
  
  Person.prototype.sayHi=function(){ //단 한번 생성되는 클로저!
  	console.log(`내 이름은 ${name}이고 ${age}살입니다");
  }
  
  return Person; //생성자함수를 반환
}

const me = new Person("홍길동", 20);
const you = new Person("김길동", 30);
me.sayHi(); // 내이름은 홍길동이고 30살입니다

👉 sayHi가 단 한번만 정의되기 때문에, 렉시컬 환경 객체도 한번만 생성되어 모든 객체가 공유한다!
👉 그냥 private를 씁시다. 이제는 JS도 private를 지원한다.




📃 25장. 클래스

🔹 클래스란

js에서 클래스는 값처럼 사용할 수 있는 일급 객체인 함수를 의미한다!
클래스 내 모든 코드는 암묵적으로 strict mode가 지정되어 해제가 불가능함!


🔹 클래스 호이스팅

🔸 클래스 선언문도 호이스팅이 된다!

다만, let/const처럼 TDZ(템퍼럴 데드 존)이 존재하여 호이스팅이 되지 않는 것처럼 보인다!


🔹 인스턴스 생성

🔸 클래스는 이름이 아닌 식별자로 접근한다

const Person = class MyClass{};
const me = new Person();    //오류x
const you = new MyClass();  //오류o

🔹 메서드

🔸 construct

  • 별도의 반환문 없이 암묵적으로 this(생성된 인스턴스)를 반환한다
  • 명시적인 객체 반환이 있다면 해당 return문이 우선된다
    • 원시값 반환은 무시된다네~

🔸 프로토타입 메서드

  • 인스턴스의 프로토타입에 존재하는 메서드.
    • 프로토타입 체인에 포함되어 상속이 가능하다
  • 인스턴스 프로퍼티 참조 가능 (this에 해당 인스턴스)

🔸 정적 메서드

  • 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
  • 클래스에 바인딩된 메서드로 프로토타입 체인상에 존재하지 않아 인스턴스가 상속받을 수 없음
  • 인스턴스 프로퍼티 참조 불가 (this 해당 클래스)

🔹 프로퍼티

class Person{
	constructor(name){
    	this.name=name; //인스턴스 프로퍼티
    }
  
  //접근자 프로퍼티
  get fullName(){ return name; } 
  set fullName(name){this.name=name;}
}

🔸 인스턴스 프로퍼티

  • this는 클래스의 constructor와 메서드 내에서만 유효하다!
  • 초기값이 할당되지 않으면 undefined를 갖는다
  • 기본적으로 public이다.
class Person{
  this.name=''; //에러!
}

😎 private 정의하기

private는 constructor가 아닌 클래스 몸체에 직접 정의해야한다!

class Person{
	#privateName=''; //해당 함수 내부에서만 접근 가능
    publicName='';
	constructor(privateName, publicName){
      	this.#privateName=privateName;
        this.publicName=publicName;
    }
}

🔹 상속

🔸 부모와의 관계

Person.prototype 객체는 Children.prototype 객체의 상위 프로토타입 체인([[Prototype]])에 존재한다.

🔸 동적 상속

생성자 함수를 상속받을 수도 있다!

class Children extends Person{}
const test = new Children(1);

🔸 super 키워드

상위클래스 prototype를 가리키는 키워드([[Prototype]]처럼!)

class Base{
	constructor(a){
    	this.a=a;
    }
  
  	sayHi(){
      return "hi!";
    }
}

class Derived extends Base{
 	constructor(a,b){
    	super(a);
      	this.b=b;
    } 
  
  sayHi(){
  	return super.sayHi();
  }
}

construct dervied=new Derived(1,2);

👉 서브클래스에서 constructor를 생략하지 않는다면, 서브클래스 constructor에서는 반드시 super를 호출해야 한다!

🙄 왜 constructor에서 super를 호출해야 하나요?

서브클래스로 인스턴스를 생성하면, 서브클래스가 직접 생성하는 것이 아니라 수퍼클래스에게 인스턴스 생성을 위임한다.
따라서 서브클래스에서 super로 수퍼클래스 constructor를 반드시 가져와주어야 한다!

🔸 서브클래스의 인스턴스 생성 단계

  1. 서브클래스에서 super를 호출한다
  2. 수퍼클래스에서 인스턴스를 생성하고 this를 바인딩한다
    👉 이때 수퍼클래스가 인스턴스를 생성했지만, this는 서브클래스에서 생성된 것처럼 서브클래스 인스턴스를 가리킨다!
  3. 수퍼클래스의 constructor가 실행되어 this에 바인딩된 인스턴스를 초기화한다.
    • 인스턴스 프로퍼티 추가
    • 전달받은 초기값으로 인스턴스 프로퍼티 초기화
  4. 서브클래스의 constructor로 복귀
    • super가 반환한 인스턴스가 this에 바인딩된다
    • 서브클래스는 해당 인스턴스를 this에 바인딩한다
      👉 즉, super가 호출되지 않으면 인스턴스가 생성되지 않고 this에도 아무것도 바인딩되지 않는다!
  5. 서브클래스의 constructor가 실행되어 this에 바인딩된 인스턴스를 초기화한다.
    • 인스턴스 프로퍼티 추가
    • 전달받은 초기값으로 인스턴스 프로퍼티 초기화
  6. 인스턴스가 바인딩된 this 반환



📃 26장. ES6함수

ES6에서는 함수를 아래 세 가지로 구분하였다.

🔹 메서드

메서드 축약 표현으로 정의된 함수만을 의미한다.

const obj={
  x:1,
  foo() {return this.x;},  //메서드
  bar: function() {return this.x;} //일반 함수
}

🔸 ES6에서 메서드는..

  • 인스턴스를 생성할 수 없는 non-constructor로, 생성자 함수로 호출할 수 없다.
  • 따라서 prototype 프로퍼티가 없고 프로토타입도 생성하지 않는다.
  • 대신 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]를 갖는다.
    👉 따라서 super는 사용할 수 있다! 이 슬롯이 상위 클래스를 가리키니까!

🔹 화살표 함수

🔸 화살표 함수 정의

const arrow1 = (x,y) => {..};
const arrow2 = x => x**2;
const arrow3 = () => {..};
const arrow4 = (x=>({sayHi(return x;)}))(x);
console.log(arrow4.sayHi("hi");); //hi

🔸 일반 함수와의 차이

  • 인스턴스를 생성할 수 없는 non-constructor로, 생성자 함수로 호출할 수 없다.
  • 따라서 prototype 프로퍼티가 없고 프로토타입도 생성하지 않는다.
  • 중복된 매개변수 이름을 선언할 수 없다.
  • 자체의 this, target, arguments, new.target 바인딩을 가지지 않는다.
    👉 스코프 체인을 통해 상위 스코프의 값을 참조해 온다!
    👉 따라서 this의 상속이 가능하여 콜백함수로 자주 사용된다!

🍜 렉시컬 this

렉시컬 스코프와 같이 화살표 함수 내 this는 함수가 정의된 위치에 의해 결정되는 것을 말한다!

🔸 class 내부에서 화살표 함수 사용하기

일반적으로 클래스 생성자의 내부에서 화살표 함수를 사용하면, this는 상위 스코프인 전역이 된다.
그러나 this.name으로 프로퍼티를 선언해 주면 this에 생성할 인스턴스가 바인딩되므로,
화살표 함수에서의 this는 생성할 인스턴스가 된다.

class Person{
	constructor(){
    	this.name="홍길동"this.sayHi=()=>console.log(this.name); //홍길동
    }
}

🔸 Rest 파라미터

화살표 함수에는 없는 arguments 대신 여러 인자를 받아올 때 사용하는 파라미터.
함수에 전달된 인수들의 목록을 배열로 전달받는다.
👉따라서, 유사배열객체인 arguments와 달리 배열 메서드를 사용할 수 있다!

  • rest 파라미터는 반드시 마지막 파라미터여야 한다.
  • rest 파라미터는 단 하나만 선언할 수 있다.
  • rest 파라미터에는 기본값을 지정할 수 없다.
function foo(...rest){
	console.log(rest);
}
profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글