코딩/Javascript

자바스크립트 this 완전 분석

드리프트 2021. 6. 21. 23:51
728x170

목차

 

1. this는 어려워요!

 

2. 함수 호출(Function Invocation)

 

    2.1. this 키워드는 함수 호출이다.

 

    2.2. 엄격 모드(strict mode)에서의 함수 호출과 this

 

   2.3. 내부 함수에서의 this

 

3. 메서드 호출(method invocation)

 

    3.1 메서드 호출에서의 this

 

    3.2. 객체에서 메서드 분리

 

4. 생성자 호출(constructor invocation)

 

    4.1. 생성자 호출에서의 this

 

    4.2. new 키워드를 생략했을 때 발생하는 문제

 

5. 간접 호출(indirect invocation)

 

6. 바인딩 함수(Bound function)

 

    6.1 바인딩 함수 안에서의 this

 

    6.2 타이트 컨텍스트 바인딩(Tight context binding)

 

7. 애로우 함수(Arrow function)

 

    7.1 애로우 함수에서의 this

 

    7.2 애로우 함수로 메서드 정의하기

 

 

 

 



1. this는 어려워요!

this 키워드는 자바스크립트를 이용해서 코드를 짰었던 그 오랜 기간 동안 참 난해한 키워드였는데요.

Java, PHP 또는 기타 언어에서는 this 키워드는 현재 객체의 인스턴스를 가리킵니다.

Java나 PHP에서는 this 키워드는 메서드 밖에서는 사용할 수 없으며 이 점이 this 키워드를 쉽게 이해 할 수 있었던 이유였는데요.

그러나 자바스크립트에서는 상황이 아주 다릅니다.

자바스크립트에서의 this는 함수 호출(function invocation - 일명 exection)의 컨텍스트(context)입니다.

자바스크립트 언어에는 4가지 함수 호출 유형이 있습니다.

함수 호출(function invocation) : alert('Hello World!')
메서드 호출(method invocation) : console.log('Hello World!')
생성자 호출(constructor invocation) : new RegExp('\\d')
간접 호출(indirect invocation) : alert.call(undefined, 'Hello World!')

위 4개의 호출 유형은 각각의 방법으로 컨텍스트를 정의하므로 개발자가 예상하는 것과 다르게 작동할 수 있습니다.

또한 엄격 모드(strict mode)로 코드를 작성할 때는 실행 컨텍스트(execution context)에도 영향을 주기 때문에 주의해야 합니다.

this 키워드를 이해하는 핵심 열쇠는 함수 호출(function invocation)과 그것이 컨텍스트(context)에 미치는 영향을 파악하는 겁니다.


이 글에서는 호출(invocation) 설명에 초점을 맞추고, 함수 호출(function invocation)이 this 키워드에 어떻게 영향을 미치는지 그리고 this의 값을 식별할때 발생되는 위험성을 알아 보겠습니다.

시작하기 전에 몇 가지 용어를 정의하고 가겠습니다.

함수 호출(function invocation)은 함수 본문을 만드는 코드를 실행하거나 단순히 함수를 호출하는 것입니다.

예를 들어 parseInt 함수 호출은 parseInt('15')라고 쓰면 parseInt 함수 호출이 됩니다.

호출 컨텍스트(context of invocation)는 함수 본문 내에서 this 값입니다.

함수 범위(Scope of function)는 함수 본문 내에서 액세스 할 수 있는 변수(variables) 및 함수(function)의 집합입니다.



2. 함수 호출(Function Invocation)

 

함수 호출은 다음과 같이 실행됩니다.

함수 객체 뒤에 여는 괄호 "(", 쉼표로 구분 되는 파라미터들, 그리고 닫는 괄호 ")"가 올 때 함수 호출이 수행됩니다.

예를 들어 parseInt('18')같이 말입니다.

함수 호출의 간단한 예는 다음 예제를 보십시요.

function hello(name) {
	return 'Hello ' + name + '!';
} 

// 함수 호출 (Function invocation)
const message = hello('World');

hello('World')는 함수 호출입니다.

hello 표현식은 함수 객체로 귀결되고 그 뒤에 'World' 인수가 있는 한 쌍의 괄호가 옵니다.

함수 호출 표현식은 속성 접근자 obj.myFunc()처럼 메서드 호출일 수는 없습니다.

예를 들어 [1,5].join(',')은 함수 호출이 아니라 메서드 호출입니다. 여기서 함수 호출과 메서드 호출사이의 차이점을 알아야 합니다.

더 자세한 예는 IIFE(immediately-invoked function expression) - 즉시 호출된 함수 표현식을 보면서 이해해 보겠습니다.

// 즉시 호출된 함수 표현식
const message = (function(name) {
	return 'Hello ' + name + '!';
})('World');

IIFE도 함수 호출입니다. 첫 번째 괄호와 그 괄호와 쌍을 이루는 괄호까지가 즉, (function (name) {...})은 함수 객체로 귀결되는 표현식이고 그 뒤에 'World' 인수가 있는 한 쌍의 괄호가 이어집니다. ('World').


2.1. this 키워드는 함수 호출이다.

 

this 키워드는 함수 호출(function invocation)의 전역 개체(global object)입니다.


전역 개체는 실행 환경에 의해 결정됩니다. 예를 들어 브라우저에서의 전역 개체는 window 개체입니다.

함수 호출(function invocation)에서 실행 컨텍스트(execution context)는 전역 개체(global object)입니다.

다음 함수에서 컨텍스트를 확인해 보겠습니다.

function sum(a, b) {
	console.log(this === window); // => true 
    this.myNumber = 20; 
    // 글로벌 개체 window에 'myNumber' 속성 추가
    
    return a + b;
}

// sum()은 함수로써 호출됩니다. 
// sum()의 this 키워드는 글로벌 개체 즉, window 입니다.
sum(15, 16); // => 31 
window.myNumber; // => 20

sum(15, 16)이 호출 될 때 자바스크립트는 this를 전역 개체 (브라우저에서는 window)로 자동 설정합니다.

this가 함수 범위 (최상위 범위 : 전역 실행 컨텍스트) 외부에서 사용되는 경우 전역 개체와 동일합니다.

console.log(this === window); // => true

this.myString = 'Hello World!';
console.log(window.myString); // => 'Hello World!'


<!-- html 파일 안에서 -->
<script type="text/javascript">
	console.log(this === window); // => true 
</script>


2.2. 엄격 모드(strict mode)에서의 함수 호출과 this

 

엄격 모드(strict mode) 함수 호출에서의 this는 undefined 와 같습니다.


엄격 모드(strict mode)는 ECMAScript 5.1부터 사용할 수 있습니다.

ECMAScript 5.1은 더 나은 보안과 더 강력한 오류 검사를 제공합니다.

엄격 모드(strict mode)를 활성화하려면 함수 본문의 맨 위에 'use strict' 지시문을 배치하면 됩니다.

일단 활성화되면 엄격 모드(strict mode)는 실행 컨텍스트에 영향을 미치고, 일반 함수 호출에서 this를 undefined 로 취급합니다.

위 예제와 달리 실행 컨텍스트는 더 이상 전역 개체가 아닙니다.

엄격 모드에서 호출되는 함수의 예를 보겠습니다.

function multiply(a, b) {
    'use strict'; // 엄격 모드(strict mode) 설정 
    console.log(this === undefined); // => true 
    
    return a * b; 
} 

// 엄격 모드(strict mode)에서의 multiply() 함수 호출 
// multiply()의 this는 undefined입니다. 
multiply(2, 5); // => 10

엄격 모드(strict mode)에서 multiply(2, 5)가 함수로 호출되면, this는 undefined로 설정됩니다.

엄격 모드(strict mode)는 현재 범위뿐 아니라 내부 범위(내부에서 선언 된 모든 함수)에서도 활성화됩니다.

function execute() { 
    'use strict'; 
    
    function concat(str1, str2) {
    // 내부 함수에서도 엄격 모드(strict mode) 활성화 됨
    console.log(this === undefined); // => true 
    
    return str1 + str2; 
} 

	// concat()이 엄격 모드(strict mode)에서 함수로써 호출 
	// concat() 함수의 this는 undefined로 설정됨 
	concat('Hello', ' World!'); // => "Hello World!" 
} 

execute();

'use strict'는 실행 본문의 맨 위에 있으면서 그 범위(scope) 내에서 엄격 모드를 활성화합니다.

concat은 실행 범위 내에서 선언되었기 때문에 엄격 모드를 상속합니다.

그리고 concat('Hello', 'World!') 호출은 this를 undefined로 설정합니다.

단일 자바스크립트 파일에는 엄격 및 비 엄격 모드가 모두 포함될 수 있습니다.

따라서 동일한 호출 유형에 대해 단일 스크립트에서 다른 컨텍스트 동작을 가질 수 있습니다.

function nonStrictSum(a, b) { 
    // 비엄격모드 (non-strict mode) 
    console.log(this === window); // => true 
    
    return a + b; 
} 

function strictSum(a, b) { 
    'use strict'; 
    
    // 엄격 모드(strict mode) 활성화  
    console.log(this === undefined); // => true 
    
    return a + b; 
} 
    
// nonStrictSum() 함수는 비엄격모드에서 함수 호출되어서 
// this 키워드는 window 객체로 설정됨 
nonStrictSum(5, 6); // => 11 
    
// strictSum() 함수는 엄격 모드에서 함수 호출되어서 
// this 키워드는 undefined로 설정됨 
strictSum(8, 12); // => 20



2.3. 내부 함수에서의 this

함수 호출에서 쉽게 헷갈리는 것은 this가 외부 함수와 내부 함수에서 동일하다고 생각하는 겁니다.

내부 함수(애로우(arrow) 함수 제외)의 컨텍스트는 "자체 호출 유형(type)"에만 의존하고 외부 함수의 컨텍스트에는 의존하지 않습니다.

this를 원하는 값으로 만들려면 간접 호출(.call() 또는 .apply() 사용)을 사용하여 내부 함수의 컨텍스트를 수정하거나 .bind() 사용하여 바인딩 된 함수를 만듭니다.

다음 예제는 두 숫자의 합계를 계산하는 예제입니다.

const numbers = { 
    numberA: 5, 
    numberB: 10, 
    
    sum: function() { 
    	console.log(this === numbers); 
        // 내부 함수의 컨텍스트는 자체 호출 유형(type)에만 의존 즉, 여기서는 numbers 타입으로 결과는 true 
        
        function calculate() { 
            // 함수 호출에서 실행 컨텍스트는 전역 개체가 된다.
            // 여기서 this는 비엄격 모드에서는 window 개체이고, 엄격모드에서는 undefined임 
        
            console.log(this === numbers); 
            // 함수 호출에서 실행 컨텍스트는 전역 개체입니다. 그래서 this는 window가 됩니다. 
            // false를 출력 
        
            return this.numberA + this.numberB; 
      }
      
 	return calculate(); 
    } 
}; 

numbers.sum(); 
// => 비엄격 모드에서는 NaN 을 출력하고, 엄격모드에서는 TypeError 뿜어냅니다.

numbers.sum()은 객체에 대한 메서드 호출이므로 위 코드에서 첫번째 나오는 console.log의 this 키워드는 numbers 타입입니다.

calculate() 함수는 sum() 내부에 정의되어 있으므로 calculate()를 호출 할 때 this는 numbers 타입의 개체를 가질 것으로 예상 할 수 있습니다.

calculate()는 함수 호출(메서드 호출이 아님)이므로 this는 전역 객체 window이거나, 엄격 모드에서는 undefined 입니다.

외부 함수 numbers.sum()에 numbers 객체로 컨텍스트가 있어도 여기서는 영향을 주지 않습니다.

numbers.sum()의 호출 결과는 NaN입니다. (또는 오류가 발생합니다. TypeError : strict 모드에서 undefined 속성 'numberA'를 읽을 수 없습니다).

확실히 5 + 10 = 15 처럼 예상 된 결과가 아닙니다.

모두 calculate()가 올바르게 호출되지 않았기 때문입니다.

이 문제를 해결하려면, calculate() 함수가 numbers.sum() 메서드와 동일한 컨텍스트로 실행되어 this.numberA 및 this.numberB 속성에 액세스 할 수 있어야 합니다.

한 가지 해결책은 calculate.call(this) (함수 간접 호출)을 호출하여 calculate()의 컨텍스트를 원하는 컨텍스트로 수동으로 변경하는 것입니다.

const numbers = { 
    numberA: 5, 
    numberB: 10, 
    
    sum: function() { 
    	console.log(this === numbers); 
        // 내부 함수의 컨텍스트는 자체 호출 유형에만 의존 즉, 여기서는 numbers 타입으로 결과는 true 
        
        function calculate() { 
           // 여기서 this는 비엄격 모드에서는 window 개체이고, 엄격모드에서는 undefined임 

           console.log(this === numbers); 
           // 함수 호출에서 실행 컨텍스트는 전역 개체입니다. 그래서 this는 window가 됩니다. 
           // 그러나, calculate.call(this)로 컨텍스트를 수정했기 때문에
           // this는 numbers로 지정되었습니다.
           // 그래서 이번에는 true 를 출력합니다.

           return this.numberA + this.numberB; 
   		} 
   
       // .call() 메서드를 사용하여 컨텍스트를 수정 
       // 이 위치의 this는 sum 내부 함수이므로 자체 호출 유형(타입)인 numbers가 됨 
       return calculate.call(this); 
 	} 
 
 }; 
 
 numbers.sum(); // => 15

calculate.call(this)은 평소와 같이 calculate() 함수를 실행하지만 추가적으로 컨텍스트를 첫 번째 매개 변수의 값으로 수정합니다.

이제 this.numberA + this.numberB는 numbers.numberA + numbers.numberB와 같아지게 됩니다.

이 함수는 예상 결과 5 + 10 = 15를 반환하게 됩니다.

또 다른 해결책은 최근 유행하는 애로우 함수를 사용하는 것입니다.

즉, 내부 함수의 컨텍스트는 자체 호출 유형에만 의존하고 외부 함수의 컨텍스트에는 의존하지 않지만 내부 함수가 애로우 함수일때는 적용되지 않는다는 룰 때문입니다.

const numbers = { 
	numberA: 5, 
    numberB: 10,
    
    sum: function() { 
    	console.log(this === numbers); // => true 
        
        const calculate = () => { 
        	console.log(this === numbers); 
            // 내부 함수를 애로우 함수로 표현했기 때문에 
            // 외부 함수의 컨테스트에 의존하게 되어서 
            // true를 출력합니다. 
            
            return this.numberA + this.numberB; 
        } 
        
        return calculate(); 
    } 
}; 

numbers.sum(); // => 15





3. 메서드 호출(method invocation)

메서드는 개체의 속성에 저장된 함수입니다. 예를 들면 :

const myObject = { 

    // helloMethod 는 메서드라고 불립니다. 개체의 속성으로 함수를 표현한겁니다.  
    helloMethod: function() { 
    	return 'Hello World!'; 
    } 
}; 

const message = myObject.helloMethod();

helloMethod는 myObject 개체의 메소드입니다.

메서드에 액세스하려면 속성 접근자 myObject.helloMethod를 사용하면 됩니다.

메서드 호출은 함수 개체 속성 접근자 형식의 표현식 뒤에 여는 괄호 "(" 그리고 쉼표로 구분 된 파라미터들과 닫는 괄호 ")"가 올 때 수행됩니다.

이전 예제를 상기하면 myObject.helloMethod()는 myObject 개체에 대한 helloMethod의 메서드 호출입니다.

메서드 호출의 더 많은 예는 [1, 2].join(',') 또는 /\s/.test('beautiful world ')와 같은 형태입니다.

우리는 함수 호출과 메서드 호출의 차이점을 이해하는 것이 중요합니다!

메서드 호출에는 obj.myFunc() 또는 obj['myFunc']()와 같이 속성 접근자 양식이 필요하지만 함수 호출은 myFunc() 처럼 속성 접근자 양식이 필요하지 않습니다.

const words = ['Hello', 'World']; 
words.join(', '); // 메서드 호출 
const obj = { 
	myMethod() { 
	    return new Date().toString(); 
    } 
}; 

obj.myMethod(); // 메서드 호출 

const func = obj.myMethod; 
func(); // 함수 호출 
parseFloat('16.6'); // 함수 호출 
isNaN(0); // 함수 호출



3.1 메서드 호출에서의 this

 

this 키워드는 메소드 호출에서 메소드를 소유하는 객체입니다.


객체에서 메소드를 호출 할 때 this는 메소드를 소유하는 객체입니다.

숫자를 증가시키는 메서드를 사용하여 객체를 만들어 보겠습니다.

const calc = { 
    num: 0, 
    increment() { 
    	console.log(this === calc); // => true 
        
        this.num += 1; 
        
        return this.num; 
    } 
}; 

// 메서드 호출(method invocation). this 는 calc 객체 
calc.increment(); // => 1 
calc.increment(); // => 2

calc.increment()를 호출하면 increment 함수의 컨텍스트가 calc 객체가 됩니다.

따라서 this.num을 사용하여 number 속성을 증가시키는 것이 제대로 작동합니다.

다른 경우를 살펴 보겠습니다. 자바스크립트 객체는 프로토 타입에서 메서드를 상속합니다.

상속 된 메서드가 개체에서 호출 될 때 호출 컨텍스트는 여전히 개체 자체입니다.

const myDog = Object.create({ 
    sayName() { 
        console.log(this === myDog); // => true     
        return this.name; 
    } 
}); 

myDog.name = 'Milo'; 

// 메서드 호출(method invocation). this는 myDog 개체 
myDog.sayName(); // => 'Milo'

Object.create()는 새 객체 myDog를 만들고 첫 번째 인수에서 프로토 타입을 설정합니다.

myDog 객체는 sayName 메소드를 상속합니다.

myDog.sayName()이 실행되면 myDog이 호출 컨텍스트가 됩니다.

ECMAScript 2015 클래스 구문에서 메서드 호출 컨텍스트는 인스턴스 자체이기도합니다.

class Planet { 
    constructor(name) { 
        this.name = name; 
    } 
    
    getName() { 
        console.log(this === earth); // => true 
        return this.name; 
    } 
} 

const earth = new Planet('Earth'); 

// 메서드 호출(method invocation). 컨텍스트는 바로 earth 객체임. 
earth.getName(); // => 'Earth'



3.2. 객체에서 메서드 분리

const alone = myObj.myMethod

위 코드처럼 메소드는 객체에서 분리된 변수로 추출 할 수 있습니다.

메서드가 alone() 처럼 객체에서 분리되어 단독으로 호출되면, this 는 아마 메서드가 정의된 myObj 객체라고 쉽게 생각 할 수 있습니다.

좀더 정확히 얘기하자면 객체없이 메서드가 호출되면 함수 호출이 발생합니다.

여기서 this는 전역 객체 window 이거나 엄격 모드에서는 undefined로 설정됩니다.

만약 여기서 .bind() 사용하여 특정 this를 바인드한 함수일 경우 즉,

const alone = myObj.myMethod.bind(myObj) 일때는

alone() 이라는 함수 호출에서도 this는 myObj가 됩니다.

왜냐하면 메서드를 소유한 객체를 바인딩하여 컨텍스트를 수정했기 때문입니다.

다음 예제는 Pet 생성자를 정의하고 myCat 이라는 인스턴스를 만듭니다.

그런 다음 1 초 후에 setTimout()이 myCat 객체 정보를 출력합니다.

function Pet(type, legs) { 
    this.type = type; 
    this.legs = legs; 
    
    this.logInfo = function() { 
    	console.log(this === myCat); // => false 
        
        // 내부 함수의 컨텍스트는 자체 호출 유형에만 의존하고 외부 함수의 컨텍스트에는 의존하지 않습니다. 
        // 여기서는 내부 함수이기 때문에 this는 함수 호출의 전역 개체 window가 됨 
        console.log(`The ${this.type} has ${this.legs} legs`);
    } 
} 

const myCat = new Pet('Cat', 4); 
// "The undefined has undefined legs" 출력 
// 엄격 모드에서는 TypeError 출력 

setTimeout(myCat.logInfo, 1000);

쉽게 생각해보면 setTimeout(myCat.logInfo, 1000) 문장이 myCat.logInfo() 함수를 호출하여 myCat 객체에 대한 정보를 출력해야한다고 생각할 수 있습니다.

그러나 불행하게도 setTimout(myCat.logInfo) 처럼 메소드는 매개 변수로 전달 될 때 객체와 분리됩니다. 다음 경우도 동일합니다.

setTimout(myCat.logInfo); // 이 문장은 다음 두문장과 동일

const extractedLogInfo = myCat.logInfo; 
setTimout(extractedLogInfo);

분리된 logInfo가 함수로 호출되면 전역 객체이거나 엄격 모드에서는 undefined으로 설정됩니다.(myCat 객체는 아님).

따라서 개체 정보가 올바르게 출력되지 않습니다.

그러나 .bind() 메서드를 사용한 함수는 객체와 바운드됩니다.

분리된 메소드가 myCat 객체와 결합되면 컨텍스트 문제가 해결됩니다.

function Pet(type, legs) { 
	this.type = type; 
    this.legs = legs; 
    
    this.logInfo = function() { 
    	console.log(this === myCat); // => true 
        console.log(`The ${this.type} has ${this.legs} legs`); 
    }; 
} 

const myCat = new Pet('Cat', 4); // myCat 객체로 바운드된 함수 생성 

const boundLogInfo = myCat.logInfo.bind(myCat); 
// "The Cat has 4 legs" 출력 

setTimeout(boundLogInfo, 1000);

myCat.logInfo.bind(myCat)는 logInfo와 똑같이 실행되는 새 함수를 반환하지만, 여기서의 this는 함수 호출에서도 myCat이 됩니다.

또 다른 방안은 logInfo() 메서드를 애로우 함수로 재 정의하는 것입니다.

function Pet(type, legs) { 
    this.type = type; 
    this.legs = legs; 
    
    this.logInfo = () => { 
    	console.log(this === myCat); // => true 
        // 내부 함수의 컨텍스트는 자체 호출 유형에만 의존하고 외부 함수의 컨텍스트에는 의존하지 않습니다. 
        // 단, 애로우 함수의 컨텍스트는 외부 함수에 의존 
        // 여기서는 애로우 함수이기 때문에 외부 함수에 의존, 즉, 안쪽의 this는 외부 함수 this myCat이 됨. 
        
        console.log(`The ${this.type} has ${this.legs} legs`); 
    }; 
} 

const myCat = new Pet('Cat', 4); // "The Cat has 4 legs" 출력 
setTimeout(myCat.logInfo, 1000);

클래스를 사용하고 this를 메서드의 클래스 인스턴스에 바인딩하려면 화살표 함수를 클래스 속성으로 사용하면 됩니다.

class Pet { 
    constructor(type, legs) { 
        this.type = type; 
        this.legs = legs; 
    } 
    
    logInfo = () => { 
    	console.log(this === myCat); // => true 
        console.log(`The ${this.type} has ${this.legs} legs`); 
    } 
} 

const myCat = new Pet('Cat', 4); 
// "The Cat has 4 legs" 출력 

setTimeout(myCat.logInfo, 1000); 




4. 생성자 호출(constructor invocation)

생성자 호출은 new 키워드 다음에 함수 객체로 평가되는 표현식, 여는 괄호 "(", 쉼표로 구분 된 파라미터들, 그리고 닫는 괄호 ")"가 올 때 수행됩니다.

생성 호출의 예 :

new Pet('cat', 4);

new RegExp( '\\d');

다음 예제는 Country 함수를 선언 한 다음 생성자로 호출합니다.

function Country(name, traveled) {
  this.name = name ? name : 'United Kingdom';
  this.traveled = Boolean(traveled); // transform to a boolean
}

Country.prototype.travel = function() {
  this.traveled = true;
};

// 생성자 호출(Constructor invocation)
const france = new Country('France', false);

// 생성자 호출(Constructor invocation)
const unitedKingdom = new Country;

france.travel(); // Travel to France

new Country('France', false)는 Country 함수의 생성자 호출입니다.

 

이 호출은 이름 속성이 'France'인 새 개체를 만듭니다.

생성자가 인수없이 호출되면 즉, new Country 처럼 괄호 안을 생략 할 수 있습니다.

ECMAScript 2015부터 자바스크립트는 클래스 구문을 사용하여 생성자를 정의 할 수 있습니다.

class City {
  constructor(name, traveled) {
    this.name = name;
    this.traveled = false;
  }

  travel() {
    this.traveled = true;
  }
}

// 생성자 호출(Constructor invocation)
const paris = new City('Paris', false);
paris.travel();

new City('Paris')는 생성자 호출입니다.

 

객체의 초기화는 constructor 이라는 클래스의 특수 메서드에 의해 처리됩니다.

 

이 특수 메서드는 this를 새로 생성된 객체로 할당합니다.

생성자 함수의 역할은 인스턴스를 초기화하는 것입니다. 

 

생성자 호출은 생성자의 프로토 타입에서 속성을 상속하는 새로운 빈 객체를 만듭니다.

속성 접근자 myObject.myFunction 앞에 new 키워드가 오면 자바스크립트는 생성자 호출을 수행하지만 메서드 호출은 수행하지 않습니다.

예를 들어 new myObject.myFunction() 문장은 먼저 속성 접근자를 사용하여 함수를 추출한 다음 ( extractFunction = myObject.myFunction)

 

새 객체를 만들기 위해 생성자로 호출됩니다 (new extractFunction()).

 

 

4.1. 생성자 호출에서의 this

 

this는 생성자 호출에서 새로 생성 된 객체입니다.

 

생성자 호출의 컨텍스트는 새로 생성 된 객체입니다. 

 

생성자는 생성자 인수에서 가져온 데이터로 객체를 초기화하고 속성에 대한 초기 값을 설정하며 이벤트 핸들러를 연결합니다.

 

다음 예에서 확인해 보겠습니다.

function Foo () {
  // this 는 fooInstance 객체임
  this.property = 'Default Value';
}

// 생성자 호출(Constructor invocation)
const fooInstance = new Foo();
fooInstance.property; // => 'Default Value'

new Foo()는 컨텍스트가 fooInstance 인 생성자를 호출합니다.

 

Foo 내부에서 객체가 초기화됩니다.

 

this.property는 'Default Value' 로 할당됩니다.

클래스 구문(ES2015에서 사용 가능)을 사용할 때도 동일한 시나리오가 발생하며 생성자 메서드에서 초기화만 발생합니다.

class Bar {
  constructor() {
    // this는 barInstance 객체임
    this.property = 'Default Value';
  }
}

// 생성자 호출(Constructor invocation)
const barInstance = new Bar();
barInstance.property; // => 'Default Value'

new Bar()가 실행될 때 자바스크립트는 빈 객체를 생성하여 constructor() 메서드의 컨텍스트로 만듭니다.

 

이제 this.property = 'Default Value' 처럼 객체에 속성을 추가 할 수 있습니다.

 

 

4.2. new 키워드를 생략했을 때 발생하는 문제

 

일부  자바스크립트 함수는 생성자로 호출 될 때뿐만 아니라 함수로 호출 될 때도 인스턴스를 생성합니다.

 

예를 들어 RegExp :

const reg1 = new RegExp('\\w+');
const reg2 = RegExp('\\w+');

reg1 instanceof RegExp; // => true
reg2 instanceof RegExp; // => true
reg1.source === reg2.source; // => true

new RegExp('\\ w +') 및 RegExp('\\ w +')를 실행할 때 자바스크립트는 동등한 정규 표현식 개체를 만듭니다.

 

함수 호출을 사용하여 객체를 만드는 것은 잠재적인 문제점을 내포합니다(팩토리 패턴 제외).

 

일부 생성자는 new 키워드가 누락 된 경우 객체를 초기화하는 논리를 생략 할 수 있기 때문입니다.

 

다음 예제는 이 문제를 잘 보여줍니다.

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}

// Function invocation
const car = Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount // => 4
car === window // => true

Vehicle 함수는 컨텍스트 객체에 type 및 wheelsCount 속성을 설정하는 함수입니다.

Vehicle('Car', 4)를 실행하면 car.type은 'Car'이고 car.wheelsCount는 4 인  속성을 가진 object car가 반환됩니다.

 

얼핏 보기에 위에서 처럼 const car = Vehicle('Car', 4); 라고 해도 새 개체를 만들고 초기화하는 데 적합하다고 생각할 수 있습니다.

 

그러나 함수 Vehicle 내의 this는 다음에 따라서

this 키워드는 함수 호출(function invocation)의 전역 개체(global object)입니다.

window 전역 개체가 되므로 Vehicle( 'Car', 4)은 결국 window 개체에 속성을 설정하는 형태가 됩니다.

 

여기서 볼수 있듯이 새 개체가 생성되지 않고 window 전역 개체에 단순히 속성을 설정하는 형태가 된겁니다.

 

명백히 실수입니다.

 

따라서, 생성자 호출이 예상되는 경우 new 연산자를 꼭 사용해야합니다.

function Vehicle(type, wheelsCount) {
  if (!(this instanceof Vehicle)) {
    throw Error('Error: Incorrect invocation');
  }

  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}

// 생성자 호출(Constructor invocation)
const car = new Vehicle('Car', 4);
car.type               // => 'Car'
car.wheelsCount        // => 4
car instanceof Vehicle // => true

// 함수 호출(Function invocation). Throws an error.
const brokenCar = Vehicle('Broken Car', 3);

new Vehicle('Car', 4) 잘 작동합니다.

 

생성자 호출에 new 키워드가 있기 때문에 새 객체가 생성되고 초기화됩니다.

실행 컨텍스트가 올바른 객체 유형인지 확인하기 위해 생성자 함수에 this instanceof Vehicle 처럼 인스턴스에 검증이 추가되었습니다.

 

Vehicle('Broken Car', 3)이 new 없이 실행될 때마다 예외 에러가 발생합니다.

 

 

 

 

5. 간접 호출(indirect invocation)

 

this는 간접 호출에서 .call() 또는 .apply()의 첫 번째 인수로 사용됩니다.

this는 간접 호출에서 .call() 또는 .apply()에 첫 번째 인수로 전달 된 값입니다.

 

다음 예제는 간접 호출 컨텍스트를 보여주는 예제입니다.

 

const rabbit = { name: 'White Rabbit' };

function concatName(string) {
  console.log(this === rabbit); // => true
  return string + this.name;
}

// 간접 호출(Indirect invocations)
concatName.call(rabbit, 'Hello ');  // => 'Hello White Rabbit'
concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'

간접 호출은 함수가 특정 컨텍스트로 실행되어야 할 때 유용합니다. 

 

예를 들어, 엄격 모드에서 항상 window이거나 또는 undefined인 상황에서 함수 호출로 컨텍스트 문제를 해결하기 위해 간접 호출을 이용할 수 있습니다.

 

객체에 대한 메서드 호출을 시뮬레이션하는 데 사용할 수 있습니다 (이전 코드 샘플 참조).

 

또 다른 실용적인 예는 상위 생성자를 호출하기 위해 ES5에서 클래스 계층 구조를 만드는 것입니다.

 

function Runner(name) {
  console.log(this instanceof Rabbit); // => true
  this.name = name;
}

function Rabbit(name, countLegs) {
  console.log(this instanceof Rabbit); // => true
  // 간접 호출(Indirect invocation), 상위 생성자 호출.
  Runner.call(this, name);
  this.countLegs = countLegs;
}

const myRabbit = new Rabbit('White Rabbit', 4);
myRabbit; // { name: 'White Rabbit', countLegs: 4 }

Rabbit 내부의 Runner.call(this, name)은 객체를 초기화하기 위해 부모 함수를 간접적으로 호출합니다.

 

 

 

6. 바인딩 함수(Bound function)

 

바인딩 된 함수는 컨텍스트 또는 인수가 특정 값에 바인딩 된 함수입니다.

 

.bind() 메서드를 사용하여 바인딩 된 함수를 만듭니다.

 

원래 함수와 바인딩 된 함수는 동일한 코드와 범위를 공유하지만 실행시 컨텍스트와 인수가 다릅니다.

 

myFunc.bind(thisArg [, arg1, arg2, ...)는 첫 번째 인수 thisArg를 컨텍스트로 그리고 바인딩 할 인수 arg1, arg2, ...의 선택적 목록을 허용합니다.

 

.bind()는 컨텍스트가 thisArg에 바인딩되고 인수가 arg1, arg2, ...에 바인딩 된 새 함수를 반환합니다.

 

다음 코드는 바인딩 된 함수를 만들고 나중에 호출합니다.

function multiply(number) {
  'use strict';
  return this * number;
}

// 컨텍스트가 있는 바인딩 된 함수 만들기
const double = multiply.bind(2);

// 바인딩 된 함수 호출
double(3); // => 6
double(10); // => 20

multiply.bind(2)는 숫자 2로 바인딩 된 새로운 함수 객체 double을 반환합니다.

 

multiply와 double은 동일한 코드와 범위를 갖습니다.

 

함수를 즉시 호출하는 .apply() 및 .call() 메서드와 달리 .bind() 메서드는 this 값이 미리 정의 된 상태로 나중에 호출 될 것으로 예상되는 새 함수만 반환합니다.

 

 

6.1 바인딩 함수 안에서의 this

 

this는 바인딩된 함수를 호출 할 때 즉, 예를 들어 myFunc.bind(thisArg)의 첫 번째 인수입니다.

.bind()의 역할은 .bind()에 전달 된 첫 번째 인수로 컨텍스트를 가지는 새로운 함수를 만드는 것입니다.

 

this 값을 미리 정의하여 함수를 만들 수 있는 강력한 기술입니다.

 

바인딩 된 함수를 구성하는 방법을 살펴 보겠습니다.

 

const numbers = {
  array: [3, 5, 10],

  getNumbers() {
    return this.array;
  }
};

// 바운딩 함수 생성
const boundGetNumbers = numbers.getNumbers.bind(numbers);
boundGetNumbers(); // => [3, 5, 10]

// 객체에서 메서드 추출
const simpleGetNumbers = numbers.getNumbers;
simpleGetNumbers(); // => undefined 또는 엄격모드에서는 에러 발생

numbers.getNumbers.bind(numbers)는 컨텍스트가 숫자에 바인딩 된 boundGetNumbers 함수를 반환합니다.

 

그런 다음 boundGetNumbers()가 this를 numbers로 호출하고 올바른 배열 객체를 반환합니다.

 

그 다음 코드에서는 함수 numbers.getNumbers는 바인딩없이 변수 simpleGetNumbers로 추출됩니다.

 

이후 함수 호출에서 simpleGetNumbers()는 this가 window 개체가 되고 또는 엄격 모드에서는 undefined이지만 numbers 객체는 아닙니다.

 

이 경우 simpleGetNumbers()는 배열을 올바르게 반환하지 않습니다.

 

 

 

6.2 타이트 컨텍스트 바인딩(Tight context binding)

 

.bind()는 영구적인 컨텍스트 링크를 만들고 항상 유지합니다.

 

바운드 함수는 .call() 또는 .apply()를 다른 컨텍스트와 함께 사용하는 경우 연결된 컨텍스트를 변경할 수 없거나 rebound도 아무 효과가 없습니다.

 

바인드 된 함수의 생성자 호출만이 이미 바인드 된 컨텍스트를 변경할 수 있지만 이는 일반적으로 수행하는 작업이 아닙니다 (생성자 호출은 바인딩되지 않은 일반 함수를 사용해야 함).

 

다음 예제에서는 바인딩 된 함수를 만든 다음 미리 정의 된 컨텍스트로 변경하려고 합니다.

 

function getThis() {
  'use strict';
  return this;
}

const one = getThis.bind(1);

one();         // => 1

one.call(2);   // => 1
one.apply(2);  // => 1
one.bind(2)(); // => 1

new one();     // => Object

 

new one() 만 바인딩 된 함수의 컨텍스트를 변경합니다.

 

다른 유형의 호출은 항상 1과 같습니다.

 

 

 

7. 애로우 함수(Arrow function)

 

 

Arrow 함수는 더 짧은 형식으로 함수를 선언하고 컨텍스트를 바인딩하도록 설계되었습니다.

 

다음과 같은 방법으로 사용할 수 있습니다.

 

const hello = (name) => {
  return 'Hello ' + name;
};

hello('World'); // => 'Hello World'

// 짝수 찾기 루틴
[1, 2, 5, 6].filter(item => item % 2 === 0); // => [2, 6]

화살표 함수는 되게 간단한 문법이며, function 키워드는 없습니다.

 

화살표 함수에 statement가 하나만 있으면 return 키워드를 생략 할 수도 있습니다.

 

화살표 함수는 익명이지만 이름을 유추 할 수 있습니다.

 

lexical 함수 이름이 없습니다 (재귀, 이벤트 핸들러 분리에 유용함).

 

또한 일반 함수가 제공하는 arguments 객체를 제공하지 않습니다.

 

대신 ES2015 rest parameters 변수를 사용하여 수정됩니다.

 

const sumArguments = (...args) => {
  console.log(typeof arguments); // => 'undefined'
  return args.reduce((result, item) => result + item);
};

sumArguments.name      // => ''
sumArguments(5, 5, 6); // => 16

 

 

7.1 애로우 함수에서의 this

 

애로우 함수안의 this는 애로우 함수가 정의되는 곳을 둘러싸는 컨텍스트입니다.

애로우 함수는 자체 실행 컨텍스트를 생성하지 않지만 정의 된 외부 함수에서 this를 가져옵니다.

 

즉, 애로우 함수는 this를 lexical하게 해결합니다.

 

다음 예제는 컨텍스트 투명도 속성을 보여줍니다.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  log() {
    console.log(this === myPoint); // => true
    setTimeout(() => {
      console.log(this === myPoint);      // => true
      console.log(this.x + ':' + this.y); // => '95:165'
    }, 1000);
  }
}
const myPoint = new Point(95, 165);
myPoint.log();

setTimeout()은 log() 메서드와 동일한 컨텍스트(myPoint 객체)를 사용하여 애로우 함수를 호출합니다.

 

표시된대로 애로우 함수는 정의 된 함수에서 컨텍스트를 "상속"합니다.

 

애로우 함수가 아인 일반 함수는 자체 컨텍스트를 생성합니다 (strict 모드에서는 window 또는 undefined).

 

따라서 함수 표현식에서 동일한 코드가 제대로 작동하도록 하려면 컨텍스트를 수동으로 바인딩해야합니다.

이렇게 말이죠, setTimeout (function() {...}. bind(this)).

 

뭔가 코드가 길어지고 장황하게 됩니다. 그래서 애로우 함수를 사용하면 코드가 간결해지고 짧아 지는 효과를 얻을 수 있습니다.

 

애로우 함수가 최상위 범위(모든 함수 외부)에 정의 된 경우 컨텍스트는 항상 전역 개체 (브라우저에서는 window)입니다.

const getContext = () => {
 console.log(this === window); // => true
 return this;
};

console.log(getContext() === window); // => true

애로우 함수를 사용하면 this는 lexical 하게 애로우 함수와 끝까지 바인딩 됩니다.

 

컨텍스트 수정 방법을 사용하는 경우에도 수정할 수 없습니다.

 

const numbers = [1, 2];

(function() { 
  const get = () => {
    console.log(this === numbers); // => true
    return this;
  };
  
  console.log(this === numbers); // => true
  get(); // => [1, 2]
  
  // Try to change arrow function context manually
  get.call([0]);  // => [1, 2]
  get.apply([0]); // => [1, 2]
  
  get.bind([0])(); // => [1, 2]
}).call(numbers);

화살표 함수 get()이 어떻게 호출 되든 상관없이 항상  컨텍스트 numbers를 유지합니다.

 

다른 컨텍스트로 간접 호출하는 get.call([0]) 또는. get.apply([0])도 효과가 없고,또한 get.bind([0])()로 리바인딩하는 것도 효과가 없습니다.

 

애로우 함수는 생성자로 사용할 수 없습니다.

 

생성자로 호출하면 new get()에서 오류가 발생합니다. "TypeError : get은 생성자가 아닙니다."

 

 

7.2 애로우 함수로 메서드 정의하기

 

애로우 함수를 사용하여 객체에 대한 메서드를 선언 할 수 있습니다.

 

function (param) {..} 대신에 (param) => {...} 함수 표현식 처럼  그형태는 매우 짧습니다.

 

이 예제는 애로우 함수를 사용하여 Period 클래스에서 format() 메서드를 정의합니다.

 

function Period (hours, minutes) { 
  this.hours = hours;
  this.minutes = minutes;
}

Period.prototype.format = () => {
  console.log(this === window); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};

const walkPeriod = new Period(2, 30);
walkPeriod.format(); // => 'undefined hours and undefined minutes'

format은 애로우 함수이며 전역 컨텍스트 (최상위 범위)에서 정의되므로 this는 window가 됩니다.

개체 walkPeriod.format()에 대한 메서드로 형식이 format이 실행 되더라도 window 개체는 호출 컨텍스트로 유지됩니다.

 

 애로우 함수에 다른 호출 유형에서 변경되지 않는 정적 컨텍스트가 있기 때문에 발생합니다.


이 메서드는 'undefined hours and undefined minutes'을 반환합니다.

 

함수 표현식은 일반 함수가 호출에 따라 컨텍스트를 변경하기 때문에 문제를 해결합니다.

 

function Period (hours, minutes) {
  this.hours = hours;
  this.minutes = minutes;
}

Period.prototype.format = function() {
  console.log(this === walkPeriod); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};

const walkPeriod = new Period(2, 30);
walkPeriod.format(); // => '2 hours and 30 minutes'

walkPeriod.format ()은 컨텍스트 walkPeriod 객체를 사용하여 객체에 대한 메서드 호출입니다.

 

this.hours는 2로 평가되고 this.minutes는 30으로 평가되므로 메서드는 '2 hours and 30 minutes'이라는 올바른 결과를 출력합니다.

 

끝.

 

그리드형