[2021.08.16] 핵심 자바스크립트 개념2

이다은·2021년 8월 18일
1
post-thumbnail

📌 실행 컨텍스트?

JavaScript 코드가 실행되고 있는 컨텍스트(환경)

  • 실행컨텍스트는 실행할 코드에 제공할 환경 정보들을(변수 정보들) 모아놓는다.
    함수를 실행할 때마다, 그 함수에 대한 새로운 실행 컨텍스트를 생성하여 자신만의 고유한 컨텍스트에서 실행된다.

  • 어떤 정보냐면..
    → 코드 실행하기 위한 여러가지 정보
    → ex) 어떤 변수가 있는지, 어떤 변수를 hoisting 할지, scope는 어떻고 scope chain은 어떻고, this는 어디에 어떻게 binding 되고 등등..


📌 호이스팅?

💡 변수의 호이스팅

  • 호이스팅은 변수의 선언을 끌어올리는 것을 말한다.
  • 선언부를 끌어올리고, 할당은 코드가 실행되는 시점에 진행된다.

왜 y 변수에
Uncaught ReferenceError: y is not defined 에러가 아닌, undefined가 찍힐까?

var x = 1; 
console.log(x + " " + y); // ✅  '1 undefined'
var y = 2;

var x = 1; 
var y; // ✅ Declare y : 호이스팅이 발생하였으므로, undefined가 된다
console.log(x + " " + y); // '1 undefined'
y = 2; // Initialize y

💡 var / let / const의 차이점

  1. var는 함수 레벨 스코프
    let, const는 블록 레벨 스코프

  2. var로 선언한 변수는 선언 전에 사용해도 에러가 나지 않지만,
    let, const는 에러가 발생한다.

  3. var는 이미 선언되어있는 이름과 같은 이름으로 변수를 또 선언해도 에러가 나지 않지만,
    let, const는 이미 존재하는 변수와 같은 이름의 변수를 또 선언하면 에러가 발생한다.

  4. var, let은 변수 선언시 초기 값을 주지 않아도 되지만,
    const는 반드시 초기값을 할당해야 한다.

  5. var, let은 값을 다시 할당할 수 있지만,
    const는 한 번 할당한 값은 변경할 수 없다.
    (단, 객체 안에 프로퍼티가 변경되는 것까지 막지는 못합니다)


✨ 2. 내용을 보면, 그럼 letconst는 호이스팅이 되지 않는걸까?
         할 수 있지만 사실 var, let, const는 다 호이스팅 된다.

다만 var는 호이스팅 되면서 초기 값이 없으면 자동으로 undefined를 할당하고,
let, const는 자동으로 초기값이 할당되지 않는다.
let, const는 변수가 선언되고 변수에 값이 할당 되기 전 TDZ(Temporal Dead Zone)에 있게 된다.

Q1. TDZ 문제 > 출력될 결과 값은?

let letValue = 'out Scope';

function hello() {
  console.log('letValue', letValue);
  let letValue = 'inner scope';
};

hello();

✅ 답 : Uncaught ReferenceError: Cannot access 'letValue' before initialization 에러가 발생한다.

  1. JS엔진은 letValue변수와 hello 함수를 메모리에 등록한다.
    ➡ (GlobalContextLexical Enviroment에 등록)
  2. letValue를 'out Scope'로 초기화한다.
  3. hello() 함수를 호출한다.
  4. hello() 함수를 호출하면 실행 컨텍스트가 만들어지고 letValue를 메모리에 등록
    ➡ (helloContextLexical Enviroment에 등록)
    *let은 블록레벨 스코프를 가진다.
  5. console.log 실행
    letValue는 메모리에 초기화 되지 않은 채로 등록되어 있기 때문에 TDZ 상태에 있다
    ➡ ReferenceError 발생!!!

💡 함수의 호이스팅

✅ 함수 선언문(function declaration)

function a(){
  //로직
}

➡ function 정의만 존재하고 별도의 할당 명령이 없다.
➡ 함수 자체가 호이스팅이 된다.

catName("Chloe"); // "My cat's name is Chloe"

function catName(name) {
  console.log("My cat's name is " + name);
}

✅ 함수 표현식(function expression)

const a = function(){
  //로직
}

➡ function 키워드로 정의한 함수를 변수에 할당하는 것을 말한다.
➡ 변수 const notHoisted; 선언만 호이스팅 되었고, 함수 할당 부분이 없으므로 에러 발생!

console.log(notHoisted) // undefined
notHoisted(); // TypeError: notHoisted is not a function

const notHoisted = function() {
   console.log('bar');
};

📌 스코프?

  • 변수가 유효한(살아있는) 범위

💡 Global Scope

  • 코드 어디에서든지 참조 가능
  • var로 선언한 변수는 전역 객체에 속하게 된다.(프로퍼티가 된다)
    • client(브라우저)의 전역 객체는 window, Node.js에서는 global

💡 Local Scope

  • JavaScipt는 다른 언어와 달리 scope의 범위가 함수 블록 내이다.
  • 함수에 의해서만 scope가 생성된다. ({} 블록과 상관이 없다!!!)
  • JavaScipt에서 let, const로 변수를 선언할 때의 scope은 블록({}) 단위이다.
    if (true) {
      var x = 5;
    }

    console.log(x);  // 5 → if 문 안에 있더라도, 일반 블록이므로 x는 전역변수가 된다.

💡 Scope Chain

  • 변수가 해당 scope에서 유효하지 않을 때, 안에서부터 바깥으로만 차례로 검색해 나가는 것

Q1. Scope Chain

function sum() {
  var a = 3;
  var b = 5;

  function inner() {
	var b = 7;
	var c = 11;
	a = a + b + c;  // ✅ a = 3, b = 7, c = 11
	console.log(a);  // 21

  };
  console.log(a);  // 3
  inner();
  console.log(a);  // 21
};

sum();
console.log(a); // ✅ Uncaught ReferenceError: a is not defined

Q2. Scope Chain

var a = 1;
var outer = function () {
    var inner = function () {
        console.log(a);
        var a = 3; // ✅ 호이스팅으로 undefined 출력됨.
    }
    inner();
    console.log(a);  // ✅ a의 값이 outer에 없으면 **바깥으로만** 차례로 검색해 나간다 ➡ 1 출력
}
outer();
console.log(a);  // ✅ 1 출력

📌 클로져?

scope가 끝난 외부 함수의 변수를 참조할 수 있다.

  • 예제
    어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우,
    A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
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를 참조하고 계속 값이 증가함
  • 메모리 관리
    • JavaScript는 원래 메모리 관리에 신경쓰지 않아도 된다.
      사용하지 않는 변수는 알아서 Garbage Collector에 수집
    • 그런데 closure에서는 의도적으로 변수를 사용하므로 메모리가 계속 소모됨
      ➡ 만약 GC에 수집되게 하려면 null이나 undefined를 할당하면 됨

📌 this?

  • this는 실행컨텍스트가 생성될 때 결정된다.

  • 실행컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다.

  • this가 무엇이냐고 한다면 → this가 바라보고 있는 객체인데, 상황에 따라 대상이 달라진다.


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

  • client(브라우저)에서는 window
  • Node.js에서는 global

💡 2. 암시적 binding : 메서드로 호출될 때 this가 바라보는 대상

  • 객체의 프로퍼티에 할당된 함수를 호출하면, this는 해당 객체를 바라본다
  • 물론 객체가 메서드로 호출해야 함

Q1. 암시적 binding

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();  // ✅ this는 user, "kim"
user.child.underTwenty();  // ✅ this는 user.child, "15"

user.parentUnderTwenty = user.child.underTwenty;
user.parentUnderTwenty(); // ✅ this는 user, "50"

Q2. 암시적 binding

var name = 'lee';
var user = {
  name: 'kim',
  getName: function() {
    console.log(this.name); // ✅ "kim"

    var inner = function() {  // inner 함수에서 this는? -> 전역 !!!
      console.log(this.name); // ✅ "lee"
    }
    inner();
  }
}

user.getName(); //✅ this는 user

💡 3. 명시적 binding : 원하는 대상으로 this binding

  • call

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

  • apply

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

  • bind

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

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

user.child.underTwenty.call(user); //✅ this는 user로 명시적 binding

💡 arrow function

  • this의 대상이 어떤 객체가 호출했느냐로 결정되지 않는다.
  • 함수 내부에 this는 없으며, scope chain의 가장 가까운 this로 대상 결정!

💡 함수와 메서드 차이

  • 함수와 메서드는 모두 function 키워드로 함수를 정의한 것을 의미
  • 메서드 : 객체의 프로퍼티로 함수가 정의되어야 한다. (=객체가 함수를 호출해야 메서드!)
    let user = {
    	name: 'kim',
    	underTwenty: function(age) {
    		return age < 20;
    	}
    }

    user.underTwenty(30); // ✅ "메서드"!
    const under20 = user.underTwenty;
    under20(15); // ✅ 객체 안에 정의된 함수라도, 이것은 메서드가 아닌 "함수"!
profile
단단_프로트엔드개발자!

0개의 댓글