본문 바로가기

Javascript/Javascript(2022 version)

Javascript 복습 - 스코프, 호이스팅, function 키워드, this binding

728x90
반응형

1) Javascript 복습

   1-1) 스코프

   1-2) 호이스팅

   1-3) function 키워드

   1-4) this binding(this가 어려운 이유)

 

 

 

 

1) Javascript 복습

Javascript 기본문법 - 공부 순서
1. 함수 선언과 호출
2. 콜스택
3. 스코프(전역변수, 지역변수)
4. 호이스팅("실행 컨텍스트"를 배우기 위한 사전 작업)
5. function 키워드
6. this
7. 클로저
8. 함수형 프로그래밍 : map, forEach, filter 등

 

 

1-1) 스코프

var, let의 차이는 호이스팅의 발생유무(var는 호이스팅이 일어나는 반면 let은 호이스팅이 일어나지 않는다!!)이다!!

  • 블록 레벨 스코프
  • 함수 레벨 스코프
  • 렉시컬 스코프
{
  var a = 10;
}
console.log(a); // 10

{
  let b = 10;
}
console.log(b); // ReferenceError: b is not defined


중괄호 유형

  • for () {}
  • if () {}
  • function () {}

블록 레벨 스코프는 모든 "{}(중괄호)"를 가리킴!!
함수 레벨 스코프는 "함수 선언부의 {}(중괄호)"만 가리킴!!

"var"는 함수 레벨 스코프를 따르는 반면, "let과 const"는 블록 레벨 스코프를 따름!!

 

for (let i = 0; i < 10; i++) {}
console.log(i); // undefined

for (var i = 0; i < 10; i++) {}
console.log(i); // 10

 

function main() {
  for (var i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, 1000 * i);
  }
}

main();

// 2가지 사실(1.var는 함수 레벨 스코프를 따른다. / 2.setTimeout은 background로 넘어가 event loop를 통해 실행된다.)을
// 명심한 상태에서 위의 코드를 살펴보면
// setTimeout()이 실행되기 전에 이미 for 문은 다 돈 상태가 되기 때문에
// 최종적으로 "5"가 콘솔에 setTimeout에서 지정한 시간 간격으로 5번 찍힌다!

 

 

렉시컬 스코프

const x = "x";
function c() {
  const y = "y";
  console.log("c", y);
}

function a() {
  const x = "xx";
  console.log("a", x);
  function b() {
    const z = "z";
    console.log("b", z);
    c();
  }
  b();
}

a();
b();

 

 

 

1-2) 호이스팅

호이스팅 : 실행 컨텍스트 생성 시 렉시컬 스코프 내의 선언이 끌어 올려지는 것

var x = "sangbeom";

function inner() {
  var x = "hello";
  console.log(x);  // hello
}

inner();

 

var x = "sangbeom";

function inner() {
  console.log(y); // undefined(var 호이스팅이 발생하여 var 선언부가 해당 실행 컨텍스트 내의 최상단으로 끌어올려지기 때문!!)
  var y = "hello";
}

inner();


해설
이전 블로그 글에서 정리했던 "call stack"에 대해서 다시 한 번 생각을 해 보면
"heap("heap"이 "실행 컨텍스트"와 동일하다고 보면 됨!!)"이라는 공간과 "call stack"이라는 공간이 있다.
이 Javascript가 실행될 때,
"Anonymous 함수"가 실행됨(익명함수 실행)

익명함수가 실행되면서 선언 부분만 실행시킴!
이때 "heap"이라는 부분이 "실행 컨텍스트"이다!!

 

익명함수 실행
(1) "x" 변수 선언

var x = "sangbeom"; // "x"라는 변수가 "sangbeom"이라는 값을 가지고 있음!


(2) inner 함수 선언

function inner() {
  var x = "hello";
  console.log(x);
}


(3) inner 함수 호출

inner();



콜스택

콜스택에 쌓이는 순서(익명함수가 먼저 실행되어 쌓이고, 그 위로 inner 함수를 호출함으로써 실행시킨 inner 함수 호출문이 새로운 실행 컨텍스트-이 실행 컨텍스트 안에 inner 함수 내부의 x 변수, console.log() 구문이 들어감!!-로 위에 쌓임!!)
inner()
Anonymous()  <-- 익명함수를 실행시키는 것을 의미함!!

 

{
  var x = 'hello';
  console.log(x);
}



실행 컨텍스트
- 여기서 0001, 0002, 0003은 실행 컨텍스트를 이해하기 위해 가정한 저장공간(메모리)이라 보면 됨!
0001 x sangbeom
0002 inner {}
0003 x hello

 

- 아래와 같이 참조 데이터 형식으로 실행 컨택스트를 이해하면 좋음!
0002 inner {
0001 x hello
}
0001 x sangbeom

 

 

let x = "sangbeom";

function inner() {
  // let은 var와 달리 호이스팅이 발생하지 않아 변수 y를 찾을 수 없다는 참조에러가 발생함!!
  console.log(y); // Uncaught ReferenceError: Cannot access 'y' before initialization
  let y = "hello";
}

inner();


(중요!!) var, function 키워드만 호이스팅이 발생한다!!

inner();
function inner() {
  console.log('정상적으로 작동');
}

// 이 경우, function 키워드는 호이스팅이 발생하기 때문에
// 이를 통해 선언된 inner 함수 선언부가 최상단으로 끌어 올려지고
// 먼저 처음 실행된 anonymous 함수에 해당 함수 선언부가 담기고,
// 이어서 inner 함수 호출문이 기존 실행 컨텍스트 위에 새로운 실행 컨텍스트를 생성하고
// 여기에 inner 함수의 console.log() 구문이 담기고 콜스택에서 실행됨과 동시에 빠져나감!!



(문제1)

function inner() {
  console.log(x); // sangbeom
}

var x = 'sangbeom';
inner();

 

풀이(문제1)
var가 호이스팅이 발생하여 해당 실행 컨텍스트 내의 최상단으로 끌어 올려지고, inner 함수 호출 전에 x 변수에 "sangbeom"이라는 값이 할당되어 콘솔을 찍었을 시 "sangbeom"이 잘 찍힘!!

(문제2)

inner();
function inner() {
  console.log(x);
  let x = 'hello';
}

var x = 'sangbeom';

 

풀이(문제2) --> Temporal Dead Zone(TDZ)
위의 경우에는 콘솔을 찍었을 시 "Uncaught ReferenceError: Cannot access 'x' before initialization" 에러가 찍힘!!

이는 let의 경우, 변수의 선언과 초기화가 분리되어 그 사이에 TDZ가 생성되기 때문에

만일 변수 선언 이전에 TDZ가 생성되어 접근한다면 위와 같이 Reference Error가 발생함!!



(문제3)

inner();
function inner() {
  x = 'hello';
  console.log(x); // hello
}

console.log(x); // hello
var x = 'sangbeom';

 

풀이(문제3)
var와 function 키워드는 각각 호이스팅이 발생하므로 콘솔에 "hello"가 2번 찍힘!!

추가

inner();
function inner() {
  x = 'hello';
  console.log(x); // hello
}

console.log(x); // hello

 

(중요!!) 변수 앞에 변수 선언 키워드(var, let, const)를 생략할 시 Javascript는 자동적으로 실행 컨텍스트 최상단에 var 키워드를 만들어 주기 때문에 위와 같은 결과가 나옴!!

(문제4)

inner();
function inner() {
  x = 'hello';
  console.log(x); // hello
}

var x = 'sangbeom';
console.log(x); // sangbeom

 

풀이(문제4)
var와 function 키워드는 각각 호이스팅이 발생하므로 첫 번째 콘솔에는 "hello"가, 두 번째 콘솔에는 "sangbeom"이 찍힘!!

 

 

 

1-3) function 키워드
function 키워드는 하는 역할이 너무 많기 때문에 어렵다!!

 

(1) 일반함수로 사용하는 경우

// 일반함수 --> 일반함수를 쓰고 싶은 경우에는 arrow function(화살표 함수)을 사용하는 것이 좋음!!
// 일반함수의 경우, "this"를 찍었을 시 "window(전역객체)"가 찍힘!!
function Foo(a, b) {
  console.log(this); // window(전역객체)
  return [a, b];
}

const a = Foo(1, 2);
console.log(a); // [1, 2]

 

(2) 생성자 함수로 사용하는 경우

// 생성자 함수 --> 생성자 함수를 쓰고 싶은 경우에는 "class"를 사용하는 것이 좋음!!
function Foo(a, b) {
  // this = {}
  console.log(this);
  this.arr = [a, b];
  // return this
}

const a = new Foo(1, 2);
console.log(a);

/*
const Foo2 = () => {
  console.log(this);
}

console.dir(Foo2);

console.dir(Foo);
*/

 

(3) 객체 메서드로 사용하는 경우

// 객체 메서드
function Foo(a, b) {
  console.log(this);
  this.arr = [a, b];
}

const bar = {
  method:Foo;
}

bar.method(5, 6); // [5, 6]


function 키워드를 쓰면 "this"가 자주 바뀐다!!
function으로 선언한 함수가 상황에 따라 "this"가 바뀐다!!
--> function 키워드는 "this binding"이라는 것이 적용되기 때문이다!!

 

 

 

1-4) this binding(this가 어려운 이유)

this binding : function이라는 키워드로 함수를 선언하면 this를 바꿀 수 있다!!

  • bind
  • call
  • apply

(1) function.prototype.bind

function Foo(a, b) {
  console.log(this);
  return [a, b];
}

const fooBind = Foo.bind({name:'sangbeom'}); // function
fooBind(); // [undefined, undefined]
fooBind(1, 2); // [1, 2]
console.log(fooBind);

 

Foo라는 함수를 선언하고,
fooBind라는 변수에 Foo.bind 메서드를 호출해서 this를 바꾼 Foo 함수값을 리턴해줌!!

(2) function.prototype.call
(3) function.prototype.apply
--> call, apply는 인수를 넣어 함수를 즉시 실행시킴!!(단, 인수 형식만 아래와 같이 좀 차이가 있음!)

function Foo(a, b) {
  console.log(this);
  return [a, b];
}

// 첫 번째 인자값 = this 값
Foo.call({name:'sangbeom'}, 1, 2)
Foo.apply({name:'sangbeom'}, [3, 4])



ES6

  • 일반함수 --> arrow function
  • 생성자 함수 --> class
  • 객체 메서드 --> 메서드 축약형

객체 메서드
ES5

// 객체 메서드
const bar = {
  method:function (a, b) {  // binding도 되면서 생성자(constructor)도 있음!!
    console.log(this);
    return [a, b];
  }
}

bar.method(5, 6); // [5, 6]


ES6

// 메서드 축약형
const bar = {
  name:'web7722',
  method(a, b) {  // binding은 되지만 생성자(constructor)가 없음!!
    console.log(this);
    return [a, b];
  }
}

bar.method(1, 2); // [1, 2]



생성자 함수
ES5

// 생성자 함수
function Foo(a, b) {
  console.log(this);
  this.arr = [a, b];
/*
  this.getArr = function() {
  return this.arr;
  }
*/
}

Foo.prototype.getArr = function() {
  return this.arr;
}

const foo = new Foo(1, 2); // {arr:[1, 2], getArr:() => {return this.arr}}


// 상속(Javascript는 실제 가지고 있는 객체, 즉 실체 혹은 인스턴스를 넣어줌으로써 상속을 함!!)
function eagle() {
  this.size = 'big';
}

function pigeon() {
  this.wing = 2;
}

eagle.prototype = new pigeon();

console.dir(eagle);

const bird = new eagle();

console.log(bird);
console.log(bird.wing);
console.log(bird.valueOf());

String.prototype.split(); // String 객체 안에 split 메서드가 있다는 뜻이다!!


ES6

// class
class Foo {
  constructor(a, b) {
    this.arr = [a, b];
  }

  getArr() {
      return this.arr;
  }
}

const a = new Foo();

console.log(a);

for (const key in a) {console.log(key)}

 

 

(정리)
this는 실행될 때 결정됨!!
그리고 function 키워드는 안 됨!
ES6 문법을 사용해야 더 직관적임!!