항해99

JavaScript 문법 종합반 3주차

Hyeon_E 2023. 5. 25. 21:33

[ 데이터 타입 심화 ] 

▶ 데이터 타입의 종류(기본형과 참조형)

이미지 출저:  https://velog.io/@imjkim49/자바스크립트-데이터-타입-정리

자바스크립트에서 값의 타입은 크게 기본형(Primitive Type)참조형(Reference Type)으로 구분됨

기본형과 참조형의 구분 기준은 값의 저장 방식과, 불변성 여부

 

· 기본형과 참조형의 구분 기준

  1. 복제의 방식
    • 기본형 : 값이 담긴 주소값을 바로 복제
    • 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
  2. 불변성의 여부
    • 기본형 : 불변성을 띔
    • 참조형 : 불변성을 띄지 않음

▶ 메모리와 데이터에 관한 배경지식

· 메모리, 데이터

 비트(bit)

  • 컴퓨터가 이해할 수 있는 가장 작은 단위
  • 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 조각을 의미
    • 이 작은 조각들이 모여서 여러분들이 흔히 들으시는 ‘메모리’가 만들어지는 것

바이트(byte)

  • 0과 1만 표현하는 비트를 모두 찾기는 부담
  • 1개 → 2개 → … → 8개(새로운 단위 : byte)
    • 즉 8bit = 1byte

메모리(memo + ry): byte 단위로 구성

  • 모든 데이터는 byte 단위의 식별자인 메모리 주소값을 통해서 서로 구분이 됨

java, c와 다른 javascript의 메모리 관리 방식(feat. 정수형)

  1. 8을 저장하는 방법
    1. JS : let a = 8(8byte)
    2. JAVA
      1. byte a = 8(1byte)
      2. short a = 8(2byte)
      3. int a = 8(4byte)
      4. long a = 8(16byte)
  2. java 또는 c언어가 초기에 등장했을 때 숫자 데이터 타입은 크기에 따라 다양하게 지정해줘야 할 만큼 개발자가 handling 할 요소들이 많았음 하지만 javascript는 이런 부분에서는 상당히 편리. 메모리 이슈까지는 고민하지 않아도 되기 때문

· 식별자, 변수

  • var testValue = 3
  • 변수 = 데이터 = ex) 3
  • 식별자 = 변수명 = ex) testValue

▶ 변수 선언과 데이터 할당

· 할당 예시

 

/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';

/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';

 

주소 ... 1002 1003 1004 1005 ...
데이터   이름: str
데이터:  5004
       
주소 ... 5002 5003 5004 5005 ...
데이터   다른 데이터 다른 데이터 "test!"    

 

· 값을 바로 변수에 대입하지 않는 이유(=무조건 새로 만드는 이유)

  1. 자유로운 데이터 변환
    • 이미 입력한 문자열이 길어진다면?
        • 숫자는 항상 8byte로 고정이지만, 문자는 고정이 아님(영문 : 1byte, 한글 : 2byte)
        • 그래서, 이미 1003 주소에 할당된 데이터를 변환하려 할 때 훨씬 더 큰 데이터를 저장하려 한다면
          • 1004 이후부터 저장되어있는 모든 데이터를 오른쪽으로 다 미뤄야함
  2. 메모리의 효율적 관리
    1. 똑같은 데이터를 여러번 저장해야 한다면?
      1. 1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당해야한다면 모든 변수를 별개로 인식한다고 한다면, 1만개의 변수 공간을 확보해야 함
      2. 1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당하는 상황을 가정
        • 이때, 변수 영역에 저장되는 데이터는 2바이트로 가정
          1. 바로 대입하는 경우
            • 숫자형은 8바이트 고정이므로 1만개 * 8byte = 8만 byte
          2. 변수 영역에 별도 저장
            • 변수영역: 2byte * 1만개 = 2만byte
            • 데이터 영역: 8바이트 * 1개 = 8byte
            • 총 2만 8byte

▶ 기본형 데이터와 참조형 데이터

·  메모리를 기준으로 다시한번 생각해보는 두 가지 주요 개념

  1. 변수 vs 상수 = 변수영역 메모리 변경 여부
    1. 변수 : 변수 영역 메모리를 변경할 수 있음
    2. 상수 : 변수 영역 메모리를 변경할 수 없음
  2. 불변하다 vs 불변하지 않다 = 데이터 영역 메모리 변경 여부
    1. 불변하다 : 데이터 영역 메모리를 변경할 수 없음
    2. 불변하지 않다 : 데이터 영역 메모리를 변경할 수 있음

 

· 불변값과 불변성(with 기본형 데이터)

 

// a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봄

// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정
var a = 'abc';
// a라는 값이 변수영역에 들어있고 'abc'는 데이터 영역에 들어가 있음

a = a + 'def';
// 'abc'는 그대로 있고 'abcdef'가 새롭게 데이터 영역에 생김
// 즉 'def'라는 값이 @5002라는 주소에 추가되는 것이 아님
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있음
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됨

 

·  가변값과 가변성(with 참조형 데이터)

참조형 데이터의 변수 할당 과정

 

// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요함
var obj1 = {
	a: 1,
	b: 'bbb,
};

 

기본형 데이터의 변수 할당 과정과 차이점 : 객체의 변수(프로퍼티) 영역의 별도 존재 여부

참조형 데이터가 불변하지 않다(가변하다)라고 하는 이유

 

var obj1 = {
	a: 1,
	b: 'bbb',
};

// 데이터가 변경됨
obj1.a = 2;

 

주소(변수 영역) 1001 1002 1003 1004
데이터 obj1 / 7103 ~      
주소(데이터 영역) 5001 5002 5003 5004
데이터 1 'bbb' 2  
주소(obj1 별도 공간) 7103 7104 7105 7106
데이터 a / 5003 b / 5002    
  1. 변경테스트 과정 과정은 아래와 같음
    1. 변경할 값인 숫자 2를 데이터 영역에서 검색함
    2. 없네요! 2를 새로 추가하고, 해당 주소(ex : @5003)를 obj1을 위한 별도 영역에 갈아껴줌
  2. 데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경이 가능
    • 이것 때문에 참조형 데이터를 흔히, ‘불변하지 않다(=가변하다)’라고 함

중첩객체의 할당

자바스크립트에서 중첩객체란, 객체 안에 또 다른 객체가 들어가는 것을 말함

객체는 배열, 함수 등을 모두 포함하는 상위개념이기 때문에 배열을 포함하는 객체도 중첩객체라고 할 수 있음

 

var obj = {
	x: 3,
	arr: [3, 4, 5],
}

// obj.arr[1]의 탐색과정

 

주소(변수) 1001 1002 1003 1004 1005 ...
데이터 obj / 7103~          
주소(데이터) 5001 5002 5003 5004 5005 ...
데이터 3 4 5      
주소(obj 별도) 7103 7104 ...
데이터 x / 5001 arr / 8104~  
주소(arr 별도) 8104 8105 8106 ...
  0 / 5001 1 / 5002 2 / 5003  

 

참조 카운트가 0인 메모리 주소의 처리

  • 참조카운트란 무엇일까
    • 객체를 참조하는 변수나 다른 객체의 수를 나타내는 값
    • 참조 카운트가 0인 객체(쓸모없게 된 아이들, 다른 곳에서 참조되지 않는 아이들)는 더 이상 사용되지 않으므로 가비지 컬렉터에 의해 메모리에서 제거됨
  • 가비지컬렉터(GC, Garbage Collector)
    • 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할을함
    • 자바스크립트는 가비지 컬렉션을 수행함으로써 개발자가 명시적으로 메모리 관리를 하지 않아도 되도록 지원
    • 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없음

·  변수 복사의 비교

// STEP01. 선언
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형

// STEP02. 복사
var b = a; //기본형
var obj2 = obj1; //참조형

 

주소(변) 1001 1002 1003 1004 1005 ...
데이터 a / 5001 obj / 7103 ~ b / 5001 obj2 / 7103~    
주소(데) 5001 5002 5003 5004 5005 ...
데이터 10 'ddd'        
주소(obj) 7103 7104 ...
데이터 c / 5001 d / 5002  

 

 

복사 이후 값 변경(객체의 프로퍼티 변경)

var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형

var b = a; //기본형
var obj2 = obj1; //참조형

// STEP03. 변경
b = 15;
obj2.c = 20;

 

주소(변) 1001 1002 1003 1004 1005 ...
데이터 a / 5001 obj / 7103 ~ b / 5003 obj2 / 7103~    
주소(데) 5001 5002 5003 5004 5005 ...
데이터 10 'ddd' 15 20    
주소(obj) 7103 7104 ...
데이터 c / 5004 d / 5002  

 

기본형과 참조형의 변수 복사 시 주요한 절차의 차이점은 다음과 같음

  • 기본형
    • 숫자 15라는 값을 데이터 영역에서 검색 후 없다면 생성
    • 검색한 결과주소 또는 생성한 주소를 변수 영역 b에 갈아끼움
    • a와 b는 서로 다른 데이터 영역의 주소를 바라보고 있기 때문에 영향 없음
  • 참조형
    • 숫자 20이라는 값을 데이터 영역에서 검색 후 없다면 생성
    • 검색한 결과주소 또는 생성한 주소 obj2에게 지정되어 있는 별도 영역(7103~)에 갈아끼움
    • obj1도 똑같은 주소를 바라보고 있기 때문에 obj1까지 변경이 됨
    • 바로 아래와 같은 현상이 생기는 것
// 기본형 변수 복사의 결과는 다른 값!
a !== b;

// 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과😭)
obj1 === obj2;

 

·  복사 이후 값 변경(객체 자체를 변경)

만약, 객체의 프로퍼티(=속성)에 접근해서 값을 변경하는 것이 아니라 객체 자체를 변경하는 방식으로 값을 바꾼다면?

//기본형 데이터
var a = 10;
var b = a;

//참조형 데이터
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;
obj2 = { c: 20, d: 'aaa'};

 

주소(변수) 1001 1002 1003 1004 1005 ...
데이터 a/ 5001 obj1 / 7103~ b / 5003 obj2 / 8204~    
주소(데이터) 5001 5002 5003 5004 5005 ...
데이터 10 'ddd' 15 20 'aaa'  
주소(obj) 7103 7104 ...
데이터 c / 5004 d / 5002  
주소(obj2) 8204 8205 8206 ...
  c / 5001 d / 5005    

 

obj2 변수는 참조형 데이터이고, 참조형 데이터의 값을 변경한 것임에도 불고하고 이전 케이스와는 다르게 obj1과는 바라보는 데이터 메모리 영역의 값이 달라졌음

 

참조형 데이터가 ‘가변값’이라고 할 때의 ‘가변’은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때 성립한다고 할 수 있음

 

▶ 불변객체

·  불변 객체의 정의

객체를 예로 들면, 객체의 속성에 접근해서 값을 변경하면 가변이 성립했음

반면, 객체 데이터 자체를 변경(새로운 데이터를 할당)하고자 한다면 기존 데이터는 변경되지 않음

즉, 불변하다라고 볼 수 있음

 

·  불변 객체의 필요성

객체의 가변성에 따른 문제점 예시

 

// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경 -> 가변
var changeName = function (user, newName) {
	var newUser = user; //할당 방식으로 복사
	newUser.name = newName;
  //새롭게 만든 복사한 newUser의 속성에 접근해서 새로운 이름을 할당
	return newUser;
};

// 변경한 user정보를 user2 변수에 할당
// 가변이기 때문에 user1도 영향을 받게 됨
var user2 = changeName(user, 'twojang');

// 결국 아래 로직은 skip하게 됨
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true

 

문제점을 개선한 예시

 

// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
	return {  //새로운 객체를 리턴
		name: newName,
		gender: user.gender,
	};
};

// 변경한 user정보를 user2 변수에 할당
// 불변이기 때문에 user1은 영향이 없음
var user2 = changeName(user, 'twojang');

// 아래 로직이 수행
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍

 

하지만 위 방법이 최선은 아님

changeName 함수는 새로운 객체를 만들기 위해 변경할 필요가 없는 gender 프로퍼티를 하드코딩으로 입력했음

⇒ 만일 이러한 속성이 10개 10개 입력해야하는 것

그래서 나온 다른 방법으로 '얕은복사'가 있음

 

·  더나은 방법: 얕은 복사

패턴과 적용

 

//이런 패턴은 어떨까요?
var copyObject = function (target) {
	var result = {};

	// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있음
	// 하드코딩을 하지 않아도 괜찮음
	// copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면 됨
	for (var prop in target) {
		result[prop] = target[prop];
	}
	return result;
}

 

얕은 복사를 예제에 적용했을때

 

var user = {
	name: 'wonjang',
	gender: 'male',
};

var user2 = copyObject(user);
user2.name = 'twojang';

if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);
console.log(user === user2);

 

·  얕은 복사 vs 깊은 복사

이 패턴도 여전히 문제가 있을까?

여전히 문제가 있음

왜냐하면, 중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문. 이것이 얕은 복사의 한계

  1. 얕은 복사 : 바로 아래 단계의 값만 복사(위의 예제)
    • 문제점 : 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때, 주소값만 복사
  2. 깊은 복사 : 내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법
    • 얕은복사뿐 아니라 깊은 복사까지 할줄 알아야 근본적인 문제를 해결할 수 있음
  3. 중첩된 객체에 대한 얕은 복사 살펴보기

 

var user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

var user2 = copyObject(user);

user2.name = 'twojang';

// 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라짐
console.log(user.name === user2.name); // false

// 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같음
// 더 혼란스러워 지는 것
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true
//url안에 내용을 변경하면 아래의 아래 단계이기때문에 불변성 유지X

// 아래 예도 똑같음
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

 

 

4. 결국, ser.urls 프로퍼티도 불변 객체로 만들어야 함

5. 중첩된 객체에 대한 깊은 복사 살펴보기

 

var user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

// 1차 copy
var user2 = copyObject(user);

// 2차 copy -> 이렇게까지 해줘야만 함
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);

 

6. 결론 : 객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사

재귀적 수행!

 

- 재귀적 수행이란?

⇒ 함수나 알고리즘이 자기 자신을 호출하여 반복적으로 실행되는 것을 말함

 

7. 결론을 적용한 코드는 완벽히 다른 객체를 반환

 

var copyObjectDeep = function(target) {
	var result = {};
	if (typeof target === 'object' && target !== null) {
		for (var prop in target) {
			result[prop] = copyObjectDeep(target[prop]);
            // 객체이고 null이 아닐경우 자기자신을 다시 실행
		}
	} else {
		result = target;
	}
	return result;
}

 

이렇게 되면, 그토록 원하던 ‘깊은 복사’를 완벽하게 구현할 수 있음

 

//결과 확인
var obj = {
	a: 1,
	b: {
		c: null,
		d: [1, 2],
	}
};
var obj2 = copyObjectDeep(obj);

obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;

console.log(obj);
console.log(obj2);

 

8.  마지막 방법으로 JSON(=JavaScript Object Notation)을 이용하는 방법도 존재

하지만 적절한 방법은 아님 왜냐하면 안되는게 좀 많음

  • 장점:
    • JSON.stringify() 함수를 사용하여 객체를 문자열로 변환한 후, 다시 JSON.parse() 함수를 사용하여 새로운 객체를 생성하기 때문에, 원본 객체와 복사본 객체가 서로 독립적으로 존재
    • 따라서 복사본 객체를 수정해도 원본 객체에 영향을 미치지 않음
    • JSON을 이용한 깊은 복사는 다른 깊은 복사 방법에 비해 코드가 간결하고 쉽게 이해할 수 있음
  • 단점:
    • JSON을 이용한 깊은 복사는 원본 객체가 가지고 있는 모든 정보를 복사하지 않음
      • 예를 들어, 함수나 undefined와 같은 속성 값은 복사되지 않음
    • JSON.stringify() 함수는 순환 참조(Recursive Reference)를 지원하지 않음
      • 따라서 객체 안에 객체가 중첩되어 있는 경우, 이 방법으로는 복사할 수 없음
    • 따라서 JSON을 이용한 깊은 복사는 객체의 구조가 간단하고, 함수나 undefined와 같은 속성 값이 없는 경우에 적합한 방법
      • 만약 객체의 구조가 복잡하거나 순환 참조가 있는 경우에는 다른 깊은 복사 방법을 고려해야 함

▶ undefined와 null

둘 다 없음을 나타내는 값. 하지만 미세하게 다르고, 그 목적 또한 다름

 

·  undefined

  • 사용자(=개발자)가 직접 지정할 수도 있지만 일반적으로는 자바스크립트 엔진에서 값이 있어야 할 것 같은데 없는 경우, 자동으로 부여함
    • 변수에 값이 지정되지 않은 경우, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
    • .이나 [ ]로 접근하려 할 때, 해당 데이터가 존재하지 않는 경우
    • return 문이 없거나 호출되지 않는 함수의 실행 결과
var a;
console.log(a); // (1) 값을 대입하지 않은 변수에 접근

var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 property에 접근
// console.log(b); // 오류 발생

var func = function() { };
var c = func(); // (3) 반환 값이 없는 function
console.log(c); // undefined

 

 

지금 undefined로 나오는 이 변수가, 필요에 의해 할당한건지 자바스크립트 엔진이 반환한건지 구분할 수 없음

‘없다’를 명시적으로 표현할 때는 undefined를 사용하면 X

 

·  null

  • 용도 : ‘없다’를 명시적으로 표현할 때
  • 주의 : typeof null

typeof null이 object인 것은 유명한 javascript 자체 버그

var n = null;
console.log(typeof n); // object

//동등연산자(equality operator)
console.log(n == undefined); // true
console.log(n == null); // true

//일치연산자(identity operator)
console.log(n === undefined);
console.log(n === null);

 

[ 실행컨텍스트(스코프, 변수, 객체, 호이스팅) ]

자바스크립트의 '실행 컨텍스트'는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 다음과 같은 일을 함

  1. 선언된 변수를 위로 끌어올리구요 = 호이스팅(hoisting)
  2. 외부 환경 정보를 구성하구요.
  3. this 값을 설정해요.

이런 현상들 때문에 JS에서는 다른 언어랑은 다른 특징들이 나타남

▶ 실행 컨텍스트란?

실행 컨텍스트를 이해하기 위해서는, 콜 스택에 대한 이해가 반드시 필요함

 

· 스택 vs 큐

이미지 출처 : https://velog.io/@leejuhwan/스택STACK과-큐QUEUE

 

· 콜 스택(call stack)

실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

즉, 동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아

컨텍스트를 구성하고 이것을 위에서 설명드린 ‘스택’의 한 종류인 콜스택에 쌓아올림

가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행하는 방법으로 코드의 환경 및 순서를 보장할 수 있음

 

컨텍스트의 구성

1. 구성방법(여러가지가 있지만, 함수만 생각하면 됨)

  • 전역공간
  • eval() 함수
  • 함수(우리가 흔히 실행컴텍스트를 구성하는 방법)

2. 실행컨텍스트 구성 예시 코드

// ---- 1번
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // ---- 2번
	console.log(a);
}
outer(); // ---- 3번
console.log(a);

 

3. 실행 컨텍스트 구성 순서

위의 코드 실행 턴텍스트 구성 순서를 보면

코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

 

4. 결국은 특정 실행 컨텍스트가 생성되는(또는 활성화되는) 시점이 콜 스택의 맨 위에 쌓이는(노출되는) 순간을 의미

곧, 현재 실행할 코드에 해당 실행 컨텍스트가 관여하게 되는 시점을 의미한다고 받아들여주시면 정확함

 

· 실행 컴텍스트 객체의 실체(=담기는 정보)

  • VariableEnvironment(VE)
    • 현재 컨텍스트 내의 식별자 정보(=record)를 갖고 있음
      • ex) var a = 3 라면, var a를 의미
    • 외부 환경 정보(=outer)를 갖고 있음
    • 선언 시점 LexicalEnvironment의 snapshot
  • LexicalEnvironment(LE)
    • VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영
  • ThisBinding
    • this 식별자가 바라봐야할 객체

▶ VariableEnvironment, LexicalEnvironment의 개요

· VE vs LE

이 두가지는 담기는 항목은 완벽하게 동일하나 스냅샷 유지여부 다름

  • VE : 스냅샷을 유지. 즉, 생길 때 그 모습을 계속 간직함
  • LE : 스냅샷을 유지하지 않음. 즉, 실시간으로 변경사항을 계속해서 반영

결국, 실행 컨텍스트를 생성할 때 VE에 정보를 먼저 담은 다음

이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용

 

· 구성요소

  • VE, LE모두 동일하며, ‘environmentRecord’와 ‘outerEnvironmentReference’로 구성
  • environmentRecord(=record)
    • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장.
    • 함수에 지정된 매개변수 식별자, 함수자체, var로 선언된 변수 식별자 등
  • outerEnvironmentReference(=outer)

▶ LexicalEnvironment(1) - environmentRocord(=record)와 호이스팅

· 개요

  • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집)됨
    • '기록된다'라고 이해해보면, 'record'라는 말과 일맥상통
  • 수집 대상 정보
    • 함수에 지정된 매개변수 식별자
    • 함수 자체
    • var로 선언된 변수 식별자 등
  • 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집
    • 순서대로 수집한다고 했지, 코드가 실행되지는 않음

· 호이스팅

  • 변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태
    • JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것
  • 변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’
    • 가상개념이라는 말은, 실제로는 그렇진 않더라도 사람이 이해하기 쉬운 말로 풀어 표현했다는 것을 의미

· 호이스팅 규칙

호이스팅 법칙1: 매개변수 및 변수는 선언부를 호이스팅 함

 

적용전 예시

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

function a (x) {
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);

 

매개변수 적용 예시

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

function a () {
	var x = 1;
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);

 

호이스팅 적용 예시

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

function a () {
	var x;
	var x;
	var x;

	x = 1;
	console.log(x);
	console.log(x);
	x = 2;
	console.log(x);
}
a(1);

 

호이스팅 법칙2 : 함수 선언은 전체를 호이스팅함

적용전 예시

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

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

 

 

호이스팅 적용 예시

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

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

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

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

 

함수 선언문을 함수 표현식을 변경하면

 

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

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

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

	console.log(b);	//'bbb'	
	console.log(b);	//'bbb'
}
a();

 

호이스팅을 다루는 김에, 함수의 정의방식 3가지주의해야 할 내용

함수 선언문, 함수 표현식

< 함수 정의의 3가지 방식 >

 

// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok

// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
var b = function () { /* ... */ }
b(); // 실행 ok

// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
var c = function d () { /* ... */ } 
c(); // 실행 ok
d(); // 에러!

 

< 주의해야 할 내용 >

- 함수 선언문, 함수 표현식

더보기

< 중간 교통 정리!! >

실행 컨텍스트실행할 코드에 제공할 환경 정보들을 모아놓은 객체

그 객체 안에는 3가지가 존재

  • VariableEnvironment
  • LexicalEnvironment
  • hisBindings

VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다름

 LE는 다음 2가지 정보를 가지고 있음

  • record(=environmentRecord) ← record의 수집과정이 hoisting
  • outer(=outerEnvironmentReference)

 

함수 선언문과 함수 표현식은, 호이스팅에서 달라짐

  • 함수 선언문은 함수 전체가 위로 끌어올려 짐
  • 함수 표현식은 변수 부분만 위로 끌어올려 짐

예시

 

console.log(sum(1, 2));
console.log(multiply(3, 4));

function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

var multiply = function (a, b) { // 함수 표현식 multiply
	return a + b;
}

 

호이스팅 예시

 

// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

// 변수는 선언부만 hoisting

var multiply; 

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { // 변수의 할당부는 원래 자리
	return a + b;
};

 

- 함수 선언문을 주의해야 하는 이유

 

함수 선언문 예시

...

console.log(sum(3, 4));

// 함수 선언문으로 짠 코드
// 100번째 줄 : 시니어 개발자 코드(활용하는 곳 -> 200군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2);

...

// 함수 선언문으로 짠 코드
// 5000번째 줄 : 신입이 개발자 코드(활용하는 곳 -> 10군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2);

console.log(c);

 

sum이라는 중복된 이름을 가진 함수 2개가 있음 밑에 sum 함수가 그 함수보다 밑에 있는 함수 호출에만 영향을 미칠꺼 같지만 호이스팅으로 인해 함수전체가 위로 올라가게 됨. 그로인하여 전체적으로 잘못된 영향을 미침

 

함수 표현식 예시

 

...

console.log(sum(3, 4));

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2);

...

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2);

console.log(c);

 

그래서 협업을 많이 하고, 복잡한 코드일 수록 전역 공간에서 이루어지는 코드 협업일수로 함수 표현식을 활용하는 습관을 들여야 함

▶ LexicalEnvironment(2) - 스코프, 스코프 체인, outerEnvironmentReference(=outer)

실행컨텍스트 관점에서의 스코프

 

· 주요 용어

  • 스코프
    • 식별자에 대한 유효범위를 의미
    • 대부분 언어에서 존재
  • 스코프 체인
    • 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
  • outerEnvironmentReference(이하 outer)
    • LE의 구성요소중 outer의 역할스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)

· 스코프 체인

  1. outer는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조
    1. 참조 = 그 당시의 환경 정보를 저장한다는 정도로 이해
  2. 예를 들어, A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)한 경우
    • ex) stack으로 밑에서 위로 있는 데이터를 생각하면 전역, A, B, C가 있다면 A는 전역을, B는 A를, C는 B를 outer로 갖고 있다고 생각하면 됨
  3. 결국 타고 올라가다 보면 전역 컨텍스트의 LexicalEnvironment를 참조하게 됨
  4. 항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능
  5. 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능

 

예시

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); // 이 값은 뭐가 나올까요? 마찬가지로 이유도!

 

전역,in → outer,in → inner(a는 내부에서 해결했기 때문에 스코프 체이닝과는 관련X),in → inner,out → outer(a는 outer에서 불린 a를 가져온 것, 스코프 체이닝),out → 전역,out

 

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

 

[ this(정의, 활용방법, 바인딩, call, apply, bind) ]

클래스로 생성한 인스턴스를 말하는 다른 객체지향 언어와 다르게 JS에서는 this가 어디에서나 사용될 수 있음

▶ 상황에 따라 달라지는 this

더보기

this를 이해하기 위한 리마인드

실행 컨텍스트실행할 코드에 제공할 환경 정보들을 모아놓은 객체

그 객체 안에는 3가지가 존재함

  • VariableEnvironment
  • LexicalEnvironment
  • ThisBindings

 

this는 실행 컨텍스트가 생성될 때 결정됨. 이 말을 this를 bind한다(=묶는다) 라고도 함

다시 말하면. this는 함수를 호출할 때 결정된다라고 할 수 있음

 

· 전역 공간에서의 this

  • 전역 공간에서 this는 전역 객체를 가리킴
  • 런타임 환경에 따라 this는 window(브라우저 환경) 또는 global(node 환경)를 각각 가리킴
    • 런타임 환경은 프로그램이 구동중인 환경을 말함
      • node 파일이름.js로 구동하고 있다면 node 환경이 되는 것
      • html파일 안에 숨겨놓아서 크롬브라우저 등에서 연다면 브라우저 환경
// 브라우저 환경 this 확인
console.log(this);
console.log(window);
console.log(this === window);

// node 환경 this 확인
console.log(this);
console.log(global);
console.log(this === global);

 

· 메서드로서 호출할때 그 메서드 내부에서의 this

< 함수 vs 메서드 >

함수와 메서드, 상당히 비슷해 보이지만 엄연한 차이가 존재함, 바로 독립성 차이

함수는 그 자체로 독립적인 기능을 수행하지만 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행함

  • 함수: this → 전역객체
  • 메서드: this → 호출의 주체
// 함수
함수명();

// 메서드
객체.메서드();

 

< this의 할당 >

// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미
var func = function (x) {
	console.log(this, x);
};
func(1); // Window { ... } 1

// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미
// obj는 곧 { method: f }를 의미
var obj = {
	method: func,
};
obj.method(2); // { method: f } 2

 

< 함수로서의 호출과 메서드로서의 호출 구분 기준 : . [ ] >

점(.)으로 호출하든, 대괄호([ ])로 호출하든 결과는 같음

 

var obj = {
	method: function (x) { console.log(this, x) }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

 

메서드 내부에서의 this

this에는 호출을 누가 했는지에 대한 정보가 담김

 

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 >

  • 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않음(호출 주체가 알 수 없기때문)
  • 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미
  • 함수로서 ‘독립적으로’ 호출할 때this 항상 전역객체를 가리킨다는 것을 주의해야 함

< 메서드의 내부함수에서의 this >

메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미

var obj1 = {
	outer: function() {
		console.log(this); //(1) obj1
		var innerFunc = function() {
			console.log(this); //(2) 전역객체, (3) obj2
		}
		innerFunc();

		var obj2 = {
			innerMethod: innerFunc
		};
		obj2.innerMethod();
	}
};
obj1.outer();

 

this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)은 중요하지 않고

오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지가 관건!!

 

< 메서드의 내부 함수에서의 this 우회 >

1. 변수를 활용하는 방법

  • 내부 스코프에 이미 존재하는 this를 별도의 변수(ex : self)에 할당하는 방법
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();

 

2. 화살표 함수(=this를 바인딩하지 않는 함수)

  • ES6에서 처음 도입된 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없음
    • this는 이전의 값-상위값-이 유지됨
    • ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 화살표함수를 도입했음
    • 그래서 일반함수와 화살표 함수의 가장 큰 차이점 this binding 여부가 가장 적절한 답
var obj = {
	outer: function() {
		console.log(this); // (1) obj
		var innerFunc = () => {
			console.log(this); // (2) obj
		};
		innerFunc();
	}
}

obj.outer();

 

· 콜백 함수 호출 시 그 함수 내부에서의 this

먼저 콜백함수란 “어떠한 함수, 메서드의 인자(매개변수)로 넘겨주는 함수

콜백함수 내부의 this는 해당 콜백함수를 넘겨받은 함수(메서드)가 정한 규칙에 따라 값이 결정됨

콜백 함수도 함수기 때문에 this는 전역 객체를 참조하지만(호출 주체가 없음)

콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있음

 

예시

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
//setTimeout에 인자로 들어가는 콜백함수

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});
//forEach에 인자로 들어가는 콜백함수

// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});
//addEventListener에 두번째 인자로 들어가는 콜백함수
//addEventListener의 경우 콜백함수를 호출할때 자신의 this를 상속하게끔 되어있음
  • setTimeout 함수, forEach 메서드
    • 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않으므로, this는 곧 window객체
  • addEventListner 메서드는 콜백 함수 호출 시
    • 자신의 this를 상속하므로, this는 addEventListner의 앞부분(button 태그)

 

· 생성자 함수 내부에서의 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

 

생성자 함수 내부에서의 this는 항상 인스턴스를 지칭함

 

▶ 명시적 this 바인딩

자동으로 부여되는 상황별 this의 규칙을 깨고 this에 별도의 값을 저장하는 방법

 

· call 메서드

  1. 호출 주체인 함수를 즉시 실행하는 명령어
  2. call명령어를 사용하여, 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 binding할 수 있음

전역 객체를 바라보는 현상에서 명시적 binding 예시

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

// no binding
func(1, 2, 3); // global을 바라봄

// 명시적 binding
// call을 할때 this로 바인딩해주고 싶은 객체로 앞에다가 써주면 됨
// func 안에 this에는 {x: 1}이 binding 됨
func.call({ x: 1 }, 4, 5, 6); // { x: 1 } 4 5 6

 

 

호출 주체가 있은 경우에 명시적 binding 예시

 

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

obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

 

· apply 메서드

call 메서드와 완전히 동일함

다만, this에 binding할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태로 넘겨줌

 

예시

var func = function (a, b, c) {
	console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

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

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

· call / apply 메서드 활용

<유사배열객체(array-like-object)에 배열 메서드를 적용>

유사배열의 조건

  • 반드시 length가 필요함(필수 조건)
  • index번호가 0번부터 시작해 1씩 증가해야함(필수조건이 아니지만 안하면 예상치 못한 결과가 생길 수 있음)
//객체에는 배열 메서드를 직접 적용할 수 없음
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있음
var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]

 

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

 

< Array.from 메서드(ES6) >

call/apply를 통해 this binding을 하는 것이 아니라 객체 → 배열로의 형 변환 만을 위해서도 쓸 수 있지만

원래 의도와는 거리가 먼 방법. 따라서, ES6에서는 Array.from이라는 방법을 제시

 

// 유사배열
var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3
};

// 객체 -> 배열
var arr = Array.from(obj);

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

 

< 생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거) >

function Person(name, gender) {
	this.name = name;
	this.gender = gender;
}
function Student(name, gender, school) {
	Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
	this.school = school;
}
function Employee(name, gender, company) {
	Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
	this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');

 

Student, Employee 모두 Person. name과 gender 속성 모두 필요함

그러니 Student와 Employee 인스턴스를 만들 때 마다 세 가지 속성을

모두 각 생성자 함수에 넣기 보다는 Person이라는 생성자 함수를 별도로 빼는게 ‘구조화’에 도움이 더 됨

 

< 여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용 >

apply를 통해 비효율적인 코드를 효율적인 코드로 바꿀 수 있음

 

예시

//비효율
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
	// 현재 돌아가는 숫자가 max값 보다 큰 경우
	if (number > max) {
		// max 값을 교체
		max = number;
	}

	// 현재 돌아가는 숫자가 min값 보다 작은 경우
	if (number < min) {
		// min 값을 교체
		min = number;
	}
});

console.log(max, min);

 

위의 코드는 너무 길고 가독성이 떨어짐

 

변경된 예시

//효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);

// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);

 

< bind 메서드 >

call과 비슷해 보이지만 call과는 다르게 즉시 호출하지는 않고

넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드

  • 함수에 this를 미리 적용
  • 부분 적용 함수 구현할 때 용이

예시

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);  //global
};
func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않음
// bind하고 새로운 함수를 return해주기 때문에 bindFunc1으로 새로운 함수가 된것
//this가 {x:1}로 고정됨
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
// 원래는 4개의 인자가 필요한데 4,5를 고정시켜놓고 2개의 인자만 필요
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

 

name 프로퍼티

bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 ‘bound’ 라는 접두어가 붙음(추적하기가 용이)

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);

// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func

 

상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기

내부함수

self 등의 변수를 활용한 우회법보다 call, apply, bind를 사용하면 깔끔하게 처리 가능

즉 지금은 잘 안씀

 

예시(call)

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

		// call을 이용해서 즉시실행하면서 this를 넘김
		innerFunc.call(this); // obj
	}
};
obj.outer();

 

예시2(bind)

var obj = {
	outer: function() {
		console.log(this);
		var innerFunc = function () {
			console.log(this);
		}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
		innerFunc();
	}
};
obj.outer();

 

콜백함수

  • 콜백함수도 함수이기 때문에, 함수가 인자로 전달될 때는 함수 자체로 전달(this가 유실됨)
  • bind메서드를 이용해 this를 입맛에 맞게 변경 가능함

예시

var obj = {
	logThis: function () {
		console.log(this);
	},
	logThisLater1: function () {
		// 0.5초를 기다렸다가 출력해요. 정상동작하지 않아요.
		// 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 잃어버렸어요!(유실)
		setTimeout(this.logThis, 500);
	},
	logThisLater2: function () {
		// 1초를 기다렸다가 출력해요. 정상동작해요.
		// 콜백함수에 this를 bind 해주었기 때문이죠.
		setTimeout(this.logThis.bind(this), 1000);
	}
};

obj.logThisLater1();
obj.logThisLater2();

 

· 화살표 함수의 예의사항

  • 화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외됨
  • 화살표 함수 내부에는 this의 할당과정(바인딩 과정)이 아에 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
  • this우회, call, apply, bind보다 편리한 방법
var obj = {
	outer: function () {
		console.log(this);	//[Function: outer]
		var innerFunc = () => {
			console.log(this);	//[Function: outer]
		};
		innerFunc();
	};
};
obj.outer();

 

숙제

더보기

첫번째 문제 (객체복사)

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

// 객체 만들어 프로퍼티 복사하기
var getAged = function (user, passedTime) {

  //이렇게 만들면 참조형이기때문에 원하는 결과가 나오지 않음

  //var newUser = user;

  //newUser.age += passedTime


    var result = {};                //새 객체를 만들어서
    for (var prop in user) {   //새롭게 만든 객체에 하나하나 복사해서 넣어줌
        result[prop] = user[prop];
    }
    result.age += passedTime; 
    return result;
}


var agedUser = getAged(user, 6);

var agedUserMustBeDifferentFromUser = function (user1, user2) {
    if (user1 !== user2) { 
        console.log("Passed! If you become older, you will be different from you in the past!")
    } else {
        console.log("Failed! User same with past one");
    }
}

agedUserMustBeDifferentFromUser(user, agedUser);


두번째 문제

var fullname = 'Ciryl Gane'

var fighter = {
    fullname: 'John Jones',
    opponent: {
        fullname: 'Francis Ngannou',
        getFullname: function () {
            // 1. 객체 this 바인딩 : 프란시스 은가누
            return this.fullname;
        }
    },

    getName: function() {
        // 2. 객체 this 바인딩 : 존 존스
        return this.fullname;
    },

    getFirstName: () => {
        // 3. 함수 this 바인딩 : 시릴
        return this.fullname.split(' ')[0];
    },

    getLastName: (function() {
       // 4. 함수 this 바인딩 : 시릴
        return this.fullname.split(' ')[1];
    })()

}

console.log('Not', fighter.opponent.getFullname(), 'VS', fighter.getName());

//opponent.으로 호출의 주체가 메서드. this는 opponent블럭을 가리키게 됨('Francis Ngannou')

//위와 같이 fighter.으로 fighter블럭을 가라킴('John Jones')
console.log('It is', fighter.getName(), 'VS', fighter.getFirstName(), fighter.getLastName);

//화살표 함수로 화살표 함수 특성상 this 바인딩을 안하기 때문에 화살표함수는 상위의 this를 바라봄('Ciryl Gane')

//마지막에 ()가 없는 것을 볼 수 있음 그래서 getLastName블럭만 보면 됨. 스스로 선언을 해놓고 호출까지 하는 모습임. 이럴경우 호출의 주체가 없음 스스로 선언하고 호출까지 바로 했기 때문 이런것을 즉시실행 함수라고 부름. 호출주체가 없기 때문에 전역객체를 바라봄('Ciryl Gane')