# 4 주차 - 실행 컨텍스트

simoniful·2021년 9월 2일
0

wanted_onboarding

목록 보기
9/9
post-thumbnail

관련 Post

예전에 JS 관련해서 포스팅한 내용과 세션 내용을 기반으로 간단하게 정리하고자 합니다. 세부적인 사항이 궁금하신 분들은 Post를 참조해 주세요.


실행 컨텍스트

  • JavaScript 코드가 실행되고 있는 컨텍스트(환경)
  • 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을(변수 정보들) 모아놓는다.
  • 함수를 실행할 때마다, 그 함수에 대한 새로운 실행 컨텍스트를 생성하여 자신만의 고유한 컨텍스트에서 실행된다.
  • ex) 어떤 변수가 있는지, 어떤 변수를 hoisting 할지, scope와 scope chain은 어떻고, this는 어떻게 binding 되고 등

Call Stack

실행할 코드 : 전역 / 함수 / (eval)

하나의 자바스크립트 파일을 실행할 때, 필요한 환경 정보를 모아 실행 컨텍스트를 구성합니다. 그리고, call stack에 쌓아 올려지게 되고, 가장 위의 컨텍스트와 관련있는 코드를 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다. call stack 이란 현재 실행 중인 작업 관한 정보를 저장하는 Stack 자료구조(LIFO,후입 선출)라 이해하면 좋습니다.


컨텍스트에 담기는 정보

VariableEnvironment

  • LexicalEnvironment의 스냅샷

LexicalEnvironment

  • 변수, 매개변수, 인자, 함수 선언 등의 정보

  • Environment record와 Outer로 구성되어 있음

    1. Environment record

    • 변수와 함수 선언등을 저장
    • 코드가 실행되기 전에 정보를 모두 수집 = 자바스크립트 엔진은 실행되기 전에 코드의 변수명을 모두 알고 있다는 것 → hoisting
    • 해당 변수가 어떤 값을 할당했는지는 관심 없다

    2. Outer

    • 다른 LexicalEnvironment를 참조하기 위함. → scope 탐색!

실행 컨텍스트 형태 예시

function book(){
  var point = 123;
  function show(){
    var title = "JS책";
  };
  function getPoint() {
    return point;
  };
  show();
};
book();
show 실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC): {
    환경 레코드(ER): {
      선언적 환경 레코드(DER): {
        title: "JS책"
      },
      오브젝트 환경 레코드(OER): {}
    },
    외부 렉시컬 환경 참조(OLER): {
      point: 123,
      getPoint: function(){}
    }
  },
  변수 환경 컴포넌트(VEC): {},
  this 바인딩 컴포넌트(TBC): {
    글로벌 오브젝트(window)
  }
}
  1. book()함수가 호출되면 show Function 오브젝트 생성, show의 [[scope]]에 스코프 설정(show가 속한 범위)
  2. show() 함수가 호출되면 EC 생성
    함수 실행을 위한 Context 환경 구축(LEC, VEC, TBC)
    렉시컬 환경 컴포넌트 내 환경 레코드에 show() 안의 변수, 함수 기록
    렉시컬 환경 컴포넌트 내 외부 렉시컬 환경 참조에 show() 밖의 변수, 함수 기록
  3. this 바인딩 컴포넌트에 this 참조 설정
    show() 앞에 작성한 object를 설정

즉, 실행 콘텍스트 안에 함수에서 구할 수 있는 값의 덩어리(정적 환경)를 만들어두면서, 함수가 메모리에 올라가 실행 될 때 메모리를 빠져나가거나 이동하거나 하지않고(scope 고정) 안에서 처리할 수 있게 됩니다. 따라서 환경에 맞춰서 코딩을 하게 되었을 경우 효율성이 올라갑니다.


호이스팅

함수 호이스팅

자바스크립트는 함수 선언문 해석 → 변수 초기화 → 코드 실행 순으로 함수 코드를 해석합니다. 따라서 선언부는 끌어올리고, 할당은 코드가 실행되는 시점에 진행됩니다.

함수 선언문(function declaration)

function a() {
  // 로직
}

→ function 정의만 존재하고 별도의 할당 명령이 없다.

함수 표현식(function expression)

표현식(expression)이란 자바스크립트 인터프리터가 계산하여 값을 구할 수 있는 자바스크립트 구절을 말한다. 이러한 값을 표현하는 것을 리터럴이라고 한다.

var a = function() {
  // 로직
}
var b = function success() {
  // 로직
}

→ function 키워드로 정의한 함수를 변수에 할당하는 것을 말한다.

예제

function book(){
  var getBook = function(){
    return "책1";
  };
  console.log(getBook());
  function getBook(){
    return "책2";
  };
};
book();
catName("Chloe");
function catName(name) {
  console.log("My cat's name is " + name);
}

변수 호이스팅

var의 경우 변수의 선언이 끌어올려 집니다. ES6에서 등장한 let과 const의 경우와는 차이가 있습니다. let, const 역시 마찬가지로 LexicalEnvironment에 변수 정보를 미리 수집합니다(hoisting의 개념으로 알고 있는 동작). 하지만, let, const는 실행되기 전까지 액세스 할 수 없고, 사이 공간을 TDZ(Temporal Dead Zone)라고 함

// var를 사용한 호이스팅의 예시, 선언에 대하여 할당은 되지 않고 읽혀진다.
var x = 1; 
console.log(x + " " + y); // '1 undefined'
var y = 2;
// let을 사용한 호이스팅의 예시, 선언에 대하여 실행 콘텍스트에 수집은 되지만, 실행 이전까지 TDZ으로 에러가 발생한다.
x = 3; // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 1;

두 가지 모두 LexicalEnvironment에 변수 정보를 미리 수집은 되나 동작에서 차이가 있다.


스코프

  • 모든 프로그래밍 언어에 있어서 변수가 유효한(살아있는) 범위를 제한하여 식별자를 해결하는 것이 목적입니다.
  • 스코프가 없다면 코드 전체에 절대 충돌하지 않는 변수(식별자)명을 딱 하나만 써야 합니다.
  • 이름을 등록하고 검색한다는 두 가지 목적
var x = 'global';

function foo () {
  var x = 'function scope';
  console.log(x);  // function scope
}

foo();
console.log(x);   // global

Global Scope

  • 코드 어디에서든지 참조 가능 - 되도록 지양
  • var로 선언한 변수는 전역 객체에 속하게 된다.(프로퍼티가 된다)
    • client(브라우저)의 전역 객체는 window, Node.js에서는 global
var value = 100;
function book() {
  return value;
};
book()

Local Scope

  • JavaScipt는 다른 언어와 달리 scope의 범위가 함수 블록 내이다. ({} 블록과 상관이 없다)
    = 함수에 의해서만 scope가 생성된다(function 오브젝트를 만날 때 스코프가 결정)

  • if (true) {
      var x = 5;
    }
    
    console.log(x);  // 5

    → if 문 안에 있더라도, 일반 블록이므로 x는 전역변수가 된다.

    (function () {
      var x = 5;
    })();
    
    console.log(x);  // Uncaught ReferenceError: x is not defined

    → 오로지 함수만 scope가 생성!
    function 오브젝트를 만날 때 스코프가 결정

  • ⚠️ JavaScipt에서 let, const로 변수를 선언할 때의 scope은 블록({}) 단위이다.

    {
      let x = 1; 
    }
    
    console.log(x); //Uncaught ReferenceError: x is not defined

    var vs let vs const


Scope Binding

프로퍼티 이름을 구조적으로 결속된 상태로 만드는 것 입니다. 스코프를 설정하여 식별자 해결하는 것을 목적으로 하며, 바인딩 시점에 따라 정적 바인딩과 동적바인딩으로 구분합니다.

function book() { 
  var point = 100;
  function add(param) {
    point += param; 
  };
  var get = function() {
    return point;
  };
  add(200);
  console.log(get());
};
book();
// 300

<식별자 해결>

  1. 마지막 줄에서 book() 함수 호출
    • 초기화 단계에서 함수와 변수 이름을 선언적 환경 레코드에 바인딩
  2. function add(param) {...}
    • function 오브젝트 생성
    • add 함수가 속한 스코프를 add 오브젝트의 [[Scope]]에 설정
    • add 이름을 레코드에 바인딩
  3. var point = 100;
    • point 이름을 레코드에 바인딩
  4. var get = function() {...}
    • get 이름을 레코드에 바인딩
  5. 바인딩으로 함수와 변수의 식별자 해결 완료

<코드실행>

  1. var point = 100;
    • point 변수에 100 할당
  2. var get = function() {...}
    • function 오브젝트 생성, get에 할당
    • get 함수가 속한 스코프를 get 오브젝트의 [[Scope]]에 설정

<add() 함수 호출>

  1. add(200) 함수를 호출
  2. point += param;
    • 먼저 선언적 환경 레코드에서 point 이름 검색
    • point가 없기에 add 오브젝트의 [[Scope]]를 스코프로 사용하여 다시 검색
    • book 오브젝트가 스코프이며 point가 존재하므로 값을 산술

<get() 함수 호출>

  1. get() 함수를 호출
  2. return point;
    • 선언적 환경 레코드에서 point 이름 검색
    • point가 없기에 get 오브젝트의 [[Scope]]를 스코프로 사용하여 다시 검색
    • book 오브젝트가 스코프이며 point가 존재하므로 값을 반환

클로저

  • "함수가 선언됐을 때" 만들어진 scope가 사라진 후에도 호출할 수 있는 함수
  • scope가 끝난 외부 함수의 변수를 참조할 수 있습니다.
  • function 오브젝트를 생성할 때 함수가 속한 스코프를 [[Scope]]에 설정하고, 함수가 호출되었을 때 [[Scope]]의 프로퍼티를 사용하는 메커니즘입니다.
  • [[Scope]]의 설정과 사용방법을 이해하면 클로저는 단지 논리적인 설명입니다.
실행 콘텍스트: {
  렉시컬 환경 컴포넌트: {
    환경 레코드: {
      선언적 환경 레코드: {},
      오브젝트 환경 레코드: {}
    },
    외부 렉시컬 환경 참조: {}
  }
}

→ 외부 렉시컬 환경 참조에 선언된 변수, 함수를 내 것처럼 사용하는 것

var A = function() {
  var a = 1;
  var B = function() {
    return ++a;
  };
  return B;
};

var outer = A();
console.log(outer());  // 2
console.log(outer());  // 3
  1. **var outer = A()** 에서 함수 B자체를 반환
    1. A() 실행했으므로 A의 실행컨텍스트는 종료됨
    2. outer 변수는 이제 B함수를 바라보고 있음
  2. outer() outer를 호출하면, 즉! B를 호출. a값은 계속 증가함
    1. B함수에는 a의 유효범위가 아니므로, 한 번 더 바깥(A)의 scope를 참조하고 계속 값이 증가함

:: 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우, A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상


클로저와 무명 함수

  • 무명 함수(즉시 실행함수) 안에 작성한 값, 함수는 무명 함수가 끝나면 지워집니다. 그렇기에 다시 사용하려면 저장이 필요합니다.
  • 한편, 무명 함수는 저장하지 않으려는 의도로 사용합니다.

클로저 활용

  • 클로저는 함수 밖 스코프의 변수와 함수를 사용할 수 있습니다.
  • 변수는 외부에서 직접 접근할 수 없으므로 정보 보호
  • 무명 함수 안에서 클로저의 변수를 가진 함수를 반환하면 함수의 재사용과 정보 보호를 할 수 있습니다.

▶ 클로저를 통한 캡슐화와 은닉화


This

함수와 메서드

  • 함수와 메서드는 모두 function 키워드로 함수를 정의한 것을 의미합니다.
  • 그 중에서도 메서드는 객체프로퍼티로 함수가 정의되어야 합니다.
    • 여기서 중요한건 객체가 함수를 호출해야 메서드!
    • obj.name()형태로 호출한 함수(메소드)에서 this로 인스턴스(오브젝트)를 참조 합니다.
    • 실행 콘텍스트의 this 바인딩 컴포넌트에 바인딩됩니다.
let user = {
  name: 'kim',
  underTwenty: function(age) {
    return age < 20;
  }
}

user.underTwenty(30); // 메서드

const under20 = user.underTwenty;
under20(15); // 객체 안에 정의된 함수라도, 이것은 메서드가 아닌 함수

this의 결정

  • this는 실행컨텍스트가 생성될 때 결정된다. 실행컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정됩니다.
  • this가 무엇이냐고 한다면 → this가 바라보고 있는 객체인데, 상황에 따라 대상이 달라집니다.

this의 동작 방식

전역 공간에서 this가 바라보는 대상

  • client(브라우저)에서는 window
  • Node.js에서는 global
  • window는 JS에서 만든 것이 아니고, 글로벌 오브젝트의 스코프도 아닙니다. 하지만, window와 글로벌 오브젝트를 같은 선상에서 사용합니다.
  • window 오브젝트와 같이 다른 오브젝트를 마치 내것 처럼 사용하는 개념 즉,Host 오브젝트 개념을 적용하기 때문입니다. (DOM 오브젝트도 Host 오브젝트입니다)
var a = 1;
console.log(a);
console.log(window.a);
console.log(this.a);

암시적 binding

  • 메서드로 호출 시 this가 바라보는 대상
  • 객체의 프로퍼티에 할당된 함수를 호출하면, this는 해당 객체를 바라봅니다
var name = 'lee';

var user = {
	name: 'kim',
	getName: function() {
		console.log(this.name);
	},
	age: 50,
	child: {
		age: 15,
		underTwenty: function() {
			console.log(this.age);
			return this.age < 20
		}
	}
}

user.getName();  // kim | getName 메서드는 user 객체를 바라봄
user.child.underTwenty();  // 15 | underTwenty 메서드는 child 객체를 바라봄

user.parentUnderTwenty = user.child.underTwenty;
user.parentUnderTwenty(); // 50 | parentUnderTwenty 메서드는 user 객체를 바라봄

this와 인스턴스 활용

  • 인스턴스의 목적
    인스턴스마다 고유 값을 유지

  • 인스턴스에서 this의 목적
    this로 인스턴스를 참조하여 this.name형태로 프로퍼티에 접근할 수 있습니다.

  • __proto__ 프로퍼티 접근

    • new 연산자를 사용해 인스턴스를 생성하면 prototype에 연결된 프로퍼티가 인스턴스의 __proto__에 첨부됩니다.
    • this.method() 형태로 __proto__에 첨부된 method()를 호출할 수 있습니다.
    • prototype에 연결된 메소드는 모든 인스턴스에 공유

    ⇒ 일관된 환경에서 값만 다르게 가져가는 데이터 중심의 처리

var book = {};
book.Point = function(point) {
  this.point = point;
};
book.Point.prototype.getPoint = function() {
  console.log(this.point);
};
var obj = new book.Point(100);
obj.getPoint();
// 100
  1. var obj = new book.Point(100);
    • book.Point 인스턴스를 생성합니다.
  2. this.point = point;
    • this가 생성한 인스턴스를 참조하므로 point는 인스턴스 프로퍼티가 됩니다.
    • 이 논리로 인스턴스마다 프로퍼티 이름과 값을 유지할 수 있습니다.
  3. obj.getPoint();
    • obj 인스턴스의 getPoint() 메소드를 호출
  4. console.log(this.point);
    • obj.getPoint()로 호출, this가 obj참조
    • obj는 book.point의 인스턴스
    • 인스턴스의 point값 출력

명시적 binding

call

  • 함수를 호출할 때, 원하는 대상의 객체를 인자로 넘겨준다.

    var user = {
      name: 'kim',
      getName: function() {
        console.log(this.name);
      },
      age: 50,
      child: {
        age: 15,
        underTwenty: function() {
          console.log(this.age);
          return this.age < 20
        }
      }
    }
    
    user.child.underTwenty.call(user);

apply

  • call 메서드와 완전히 같은 기능이나, 호출할 함수에 인자를 배열로 넘기느냐 or not

bind

  • call과 비슷하지만, 바로 호출하는 것이 아니라 대상을 묶어놓기(binding)만 하는 것

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button **onClick={this.handleClick}**>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }
  • event listener는 내부적으로 this를 이벤트가 일어난 엘리먼트로 대상을 잡아놓기 때문에

  • handleClick 내부의 this가 Class를 가르키는 것이 아닌, 엘리먼트를 가르치게 됨


Arrow function(ES6)

  • this의 대상이 어떤 객체가 호출했느냐로 결정되지 않는다!
  • 함수 내부에 this는 없으며, scope chain의 가장 가까운 this로 대상 결정!
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
  }

  handleClick = () => {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
profile
소신있게 정진합니다.

0개의 댓글