네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'2021/11'에 해당되는 글 16건


반응형

2. 변수

변수(Variable)는 프로그램에서 사용되는 데이터를 일정기간동안 기억하여 필요한 때에 다시 사용하기 위해 데이터에 고유의 이름인 식별자(identifier)를 명시한 것이다. 변수에 명시한 고유한 식별자를 변수명이라 하고 변수로 참조할 수 있는 데이터를 변수값이라 한다.

 

변수는 var, let, const 키워드를 사용하여 선언하고 할당 연산자를 사용해 값을 할당한다. 그리고 식별자인 변수명을 사용해 변수에 저장된 값을 참조한다.

 

1) 동적 타이핑(Dynamic Typing)

자바스크립트는 동적 타입(dynamic/weak type) 언어이다. 이것은 변수의 타입 지정(Type annotation)없이 값이 할당되는 과정에서 값의 타입에 의해 자동으로 타입이 결정(Type Inference)될 것이라는 뜻이다. 따라서 같은 변수에 여러 타입의 값을 할당할 수 있다. 이를 동적 타이핑(Dynamic Typing)이라 한다.

var foo;

console.log(typeof foo); //undefined

foo = null;
console.log(typeof foo); // object

foo = {};
console.log(typeof foo); // object

foo = 3;
console.log(typeof foo); // number

foo = 3.14;
console.log(typeof foo); // number

foo = 'Hi';
console.log(typeof foo); // string

foo = true;
console.log(typeof foo); // boolean

 

2) 변수 호이스팅(Variable Hoisting)

console.log(foo); // 1. undefined

var foo = 123;
console.log(foo); // 2. 123

{
	var foo = 456;
}
console.log(foo); // 3. 456

var 키워드를 사용하여 선언한 변수는 중복 선언이 가능하기 때문에 위 코드는 문법적으로 문제가 없다.

1에서 변수 foo는 아직 선언되지 않았으므로 ReferenceError: foo is not defined 가 발생할 것을 기대했겠지만 콘솔에는 undefined가 출력된다.

이것은 C-family 언어와는 차별되는 자바스크립트의 특징으로 모든 선언문은 호이스팅(Hoisting)되기 때문이다.

 

호이스팅이란 var 선언문이나 function 선언문 등 모든 선언문이 해당 scope의 선두로 옮겨진 것처럼 동작하는 특성을 말한다. 즉, 자바스크립트는 모든 선언문(var, let, const, function, function*, class)이 선언되기 이전에 참조 가능하다.

변수가 어떻게 생성되며 호이스팅은 어떻게 이루어지는지 좀 더 살펴보면 변수는 3단계에 걸쳐 생성된다. 

1) 선언 단계 (Declaration phase)

변수 객체(Variable Object)에 변수를 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.

2) 초기화 단계 (Initializing phase)

변수 객체에 등록된 변수를 메모리에 할당한다. 이 단계에서 변수는 undefined로 초기화된다.

3) 할당 단계(Assignment phase)

undefined로 초기화된 변수에 실제값을 할당한다.

 

var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 즉, 스코프에 변수가 등록되고 변수는 메모리에 공간을 확보한 후 undefined로 초기화된다. 따라서 변수 선언문 이전에 변수에 접근하여도 Variable Object에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다. 이러한 현상을 변수 호이스팅이라 한다.

 

이후 변수 할당문에 도달하면 비로소 값의 할당이 이루어진다.

var 키워드로 선언된 변수의 생명 주기

 

앞에서 살펴본 예제를 호이스팅 관점에서 다시 보면 1이 실행되기 이전에 var foo = 123; 이 호이스팅 되어 1 구문 앞에 var foo; 가 옮겨진다.  하지만 변수 선언 단계와 초기화 단계가 할당 단계와 분리되어 진행되기 때문에 이 단계에서는 foo에는 undefined가 할당되어 있다. 변수 foo에 값이 할당되는 것은 2행에서 실행된다.

 

자바 스크립트의 변수는 다른 C-family와는 달리 블록 레벨 스코프(block-level scope)를 가지지 않고 함수 레벨 스코프 (function-level scope)를 갖는다. 단, ES6에서 도입된 let, const 키워드를 사용하면 블록 레벨 스코프를 사용할 수 있다.

* 함수 레벨 스코프 (Function-level scope)

함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

* 블록 레벨 스코프 (Block-level scope)

코드 블록 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다.

 

따라서 코드 블록 내의 변수 foo는 전엳변수이므로 전역에 선언된 변수 foo에 할당된 값을 재할당하기 때문에 3의 결과는 456이 된다.

 

3) var 키워드로 선언된 변수의 문제점

ES5에서 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는 것이다. var 키워드로 선언된 변수는 아래와 같은 특징을 갖는다. 이는 다른 C-family 언어와는 차별되는 특징(설계상 오류)으로 주의를 기울이지 않으면 심각한 문제가 발생한다.

1. 함수 레벨 스코프 (Function-level scope)

- 전역 변수의 남발

- for loop 초기화 식에서 사용한 변수를 for loop 외부 또는 전역에서 참조할 수 있다.

2. var 키워드 생략 허용

- 의도하지 않은 변수의 전역화

3. 중복 선언 허용

- 의도하지 않은 변수값 변경

4. 변수 호이스팅

- 변수를 선언하기 전에 참조가 가능

 

대부분의 문제는 전역변수로 인해 발생한다. 전역변수는 간단한 애플리케이션인 경우, 사용이 편리한 면이 있지만 불가피한 상황을 제외하고 사용을 억제해야 한다. 전역 변수는 유효 범위(scope)가 넓어서 어디서 어떻게 사용될 지 파악하기 힘들다. 이는 의도하지 않은 변수의 변경이 발생할 수 있는 가능성이 증가한다. 또한 여러 함수와 상호 의존하는 등 부수 효과(side effect)가 있을 수 있어서 복잡성이 증가한다.

변수의 유효범위는 좁을 수록 좋다.

ES6는 이러한 var의 단점을 보완하기 위해 let과 const 키워드를 도입하였다.

 

 

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

데이터 타입과 변수

프로그래밍은 변수를 통해 값을 저장하고 참조하며 연산자로 값을 연산, 평가하고 조건문과 반복문에 의한 흐름제어로 데이터의 흐름을 제어하고 함수로 재사용이 가능한 구문의 집합을 만들며 객체, 배열 등으로 자료를 구조화하는 것이다.

변수는 값의 위치(주소)를 기억하는 저장소이다. 값의 위치란 값이 위치하고 있는 메모리 상의 주소(address)를 의미한다. 즉, 변수란 값이 위치하고 있는 메모리 주소(Memory address)에 접근하기 위해 사람이 이해할 수 있는 언어로 명명한 식별자(identifier)이다.

변수 선언과 할당의 구조

메모리에 값을 저장하기 위해서는 먼저 메모리 공간을 확보해야 할 메모리의 크기(byte)를 알아야 한다.

이는 값의 종류에 따라 확보해야 할 메모리의 크기가 다르기 때문이다. 이때 값의 종류, 즉 데이터의 종류를 데이터 타입(Data Type)이라 한다.

자바스크립트는 동적 타입(Dynamic/Weak Type) 언어이다. 변수의 타입 지정(Type annotation)없이 값이 할당되는 과정에서 자동으로 변수의 타입이 결정(타입 추론, Type Inference)된다. 즉, 변수는 고정된 타입이 없다. 따라서 같은 변수에 여러 타입의 값을 자유롭게 할당할 수 있다.

var str = 'Hello';
var num = 1;
var bool = true;

var foo = 'string';
console.log(typeof foo); // string
foo = 1;
console.log(typeof foo); // number

1. 데이터 타입

데이터 타입(Data Type)은 프로그래밍 언어에서 사용할 수 있는 데이터(숫자, 문자열, 불린 등)의 종류를 말한다.

자바스크립트의 모든 값은 데이터 타입을 갖는다. ECMAScript 표준 (ES6)는 7개의 데이터 타입을 제공한다.

 

# 원시 타입 (primitive data type)

- boolean

- null

- undefined

- number 

- string 

- symbol (ES6 에서 추가)

# 객체 타입 (object/reference type)

- object

 

예를 들어 숫자(number) 타입 1과 문자열(string) 타입 ‘1’은 비슷하게 보이지만 다른 타입의 값이다. 숫자 타입의 값은 주로 산술 연산을 위해 만들지만 문자열 타입의 값은 주로 텍스트로 출력하기 위해 만든다. 이처럼 개발자는 명확한 의도를 가지고 타입을 구별하여 값을 만들 것이고 자바스크립트 엔진은 타입을 구별하여 값을 취급할 것이다.

 

1) 원시 타입 (Primitive Data Type)

원시 타입의 값은 변경 불가능한 값(Immutable value)이며 pass-by-value(값에 의한 전달)이다.

 

1-1) number

자바스크립트는 하나의 숫자 타입만 존재한다. 

ES 표준에 따르면, 숫자 타입의 값은 배정밀도 64비트 부동소수점 형(double-precision 64-bit floating-point format)을 따른다. 즉, 모든 수를 실수처럼 처리하여 정수만을 표현하기 위한 특별한 데이터 타입은 없다.

var integer = 10;        // 정수
var double = 10.12;      // 실수
var negative = -20;      // 음의 정수
var binary = 0b01000001; // 2진수
var octal = 0o101;       // 8진수
var hex = 0x41;          // 16진수

2진수, 8진수, 16진수 리터럴은 메모리에 동일한 배정밀도 64비트 부동소수점 형식의 2진수로 저장된다. 자바스크립트는 2진수, 8진수, 16진수 데이터 타입을 제공하지 않기 때문에 이들 값을 참조하면 모두 10진수로 해석된다.

console.log(binary); // 65
console.log(octal);  // 65
console.log(hex);    // 65

// 표기법만 다를뿐 같은 값이다.
console.log(binary === octal); // true
console.log(octal === hex);    // true

자바스크립트의 숫자 타입은 정수만을 위한 타입이 없고 모든 수를 실수를 처리한다. 정수로 표시된다해도 사실은 실수다. 따라서 정수로 표시되는 수 끼리 나누더라도 실수가 나올 수 있다.

console.log(1 === 1.0); // true

var result = 4 / 2;
console.log(result); // 2
result = 3 /2;
console.log(result); // 1.5

추가적으로 3가지 특별한 값들도 표현할 수 있다.

  • Infinity : 양의 무한대
  • -Infinity : 음의 무한대
  • NaN : 산술 연산 불가(not-a-number)
var pInf = 10 / 0;  // 양의 무한대
console.log(pInf);  // Infinity

var nInf = 10 / -0; // 음의 무한대
console.log(nInf);  // -Infinity

var nan = 1 * 'string'; // 산술 연산 불가
console.log(nan);       // NaN

 

1-2) string 

문자열(String) 타입은 텍스트 데이터를 나타내는데 사용한다. 문자열은 0개 이상의 16-bit 유니코드 문자(UTF-16)들의 집합으로 전세계 문자를 대부분 표현할 수 있다. 문자열은 작은 따옴표(') 또는 큰 따옴표(") 안에 텍스트를 넣어 생성한다. 가장 일반적인 표기법은 작은 따옴표를 사용하는 것이다.

자바스크립트의 문자열은 원시 타입이며 변경 불가능(immutable)하다. 이것은 한 번 문자열이 생성되면, 그 문자열을 변경할 수 없다는 것을 의미한다.

var str = 'Hello';
str = 'world';

첫번째 구문이 실행되면 메모리에 문자열 'Hello'가 생성되고 식별자 str은 메모리에 생성된 문자열 'Hello'의 메모리 주소를 가리킨다. 그리고 두번째 구문이 실행되면 이전에 생성된 문자열 'Hello'을 수정하는 것이 아니라 새로운 문자열 'world'를 메모리에 생성하고 식별자 str은 이것을 가리킨다. 이때 문자열 'Hello'와 'world'는 모두 메모리에 존재하고 있다. 변수 str은 문자열 'Hello'를 가리키고 있다가 문자열 'world'를 가리키도록 변경되었을 뿐이다.

var str = 'string';
// 문자열은 유사배열이다.
for (var i = 0; i < str.length; i++) {
  console.log(str[i]);
}

// 문자열을 변경할 수 없다.
str[0] = 'S';
console.log(str); // string

문자열은 배열처럼 인덱스를 통해 접근할 수 있다. 이와 같은 특성을 갖는 데이터를 유사 배열이라 한다.

str[0] = 'S' 처럼 이미 생성된 문자열의 일부 문자를 변경해도 반영되지 않는다. (이때 에러는 발생하지 않는다.) 한번 생성된 문자열은  read only로 변경할 수 없다. 이것을 변경 불가능(immutable)이라 한다.

그러나 새로운 문자열을 재할당하는 것은 물론 가능하다. 이는 기존 문자열을 변경하는 것이 아니라 새로운 문자열을 새롭게 할당하는 것이기 때문이다.

var str = 'string';
console.log(str); // string

str = 'String';
console.log(str); // String

str += ' test';
console.log(str); // String test

str = str.substring(0, 3);
console.log(str); // Str

str = str.toUpperCase();
console.log(str); // STR

1-3) boolean

불린(boolean) 타입의 값은 논리적 참, 거짓을 나타내는 truefalse 뿐이다.

var foo = true;
var bar = false;

// typeof 연산자는 타입을 나타내는 문자열을 반환한다.
console.log(typeof foo); // boolean
console.log(typeof bar); // boolean

비어있는 문자열과 null, undefined, 숫자 0은 false로 간주된다. 타입 변환에서 살펴볼 것이다.

 

1-4) undefined

undefined 타입의 값은 undefined가 유일하다. 선언 이후 값을 할당하지 않은 변수는 undefined 값을 가진다. 즉, 선언은 되었지만 값을 할당하지 않은 변수에 접근하거나 존재하지 않은 객체 프로퍼티에 접근할 경우 undefined가 반환된다.

이는 변수 선언에 의해 확보된 메모리 공간을 처음 할당이 이루어질 때까지 빈 상태(쓰레기 값(Garbage value))로 내버려두지 않고 자바스크립트 엔진이 undefined로 초기화하기 때문이다.

var foo;
console.log(foo); // undefined

이처럼 undefined는 개발자가 의도적으로 할당한 값이 아니라 자바스크립트 엔진에 의해 초기화된 값이다. 변수를 참조했을 때 undefined가 반환된다면 참조한 변수가 선언 이후 값이 할당된 적 없는 변수라는 것은 개발자는 간파할 수 있다. 그렇다면 개발자가 의도적으로 undefined를 할당해야하는 경우는 없다. 자바스크립트 엔진이 변수 초기화에 사용하는 이 값을 개발자가 마음대로 할당한다면 undefined의 본래 취지에 어긋날 뿐더러 혼란을 줄 수 있다. 그럼 변수의 값이 없다는 것을 명시하고 싶은 경우에는 null을 할당한다.

 

1-5) null

null 타입의 값은 null이 유일하다. 자바스크립트는 대소문자를 구별(case-sensitive)하므로 null은 Null, NULL 등과 다르다. 

프로그래밍 언어에서 null은 의도적으로 변수에 값이 없다는 것을 명시할 떄 사용한다. 이는 변수가 기억하는 메모리 주소의 참조 정보를 제거하는 것을 의미하며 자바스크립트 엔진은 누구도 참조하지 않는 메모리 영역에 대해 가비지 컬렉션을 수행할 것이다.

var foo = 'Lee';
foo = null;  // 참조 정보가 제거됨

또는 함수가 호출되었으나 유효한 값을 반환할 수 없는 경우, 명시적으로 null을 반환하기도 한다. 예를 들어, 조건에 부합하는 HTML 요소를 검색해 반환하는 Document.querySelector()는 조건에 부합하는 HTML 요소를 검색할 수 없는 경우, null을 반환한다.

var element = document.querySelector('.myElem');
// HTML 문서에 myElem 클래스를 갖는 요소가 없다면 null을 반환한다.
console.log(element); // null

타입을 나타내는 문자열을 반환하는 typeof 연산자로 null 값을 연산해보면 null이 아닌 object가 나온다. 이는 자바스크립트의 설계상 오류이다.

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

따라서 null 타입을 확인할 때 typeof 연산자를 사용하면 안되고 일치 연산자(===)를 사용하여야 한다.

var foo = null;
console.log(typeof foo === null); // false
console.log(foo === null); // true

 

1-6) symbol

심볼(symbol)은 ES6에서 새롭게 추가된 7번째 타입으로 변경 불가능한 원시 타입의 값이다. 심볼은 주로 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키(property key)를 만들기 위해 사용한다. 심볼은 Symbol 함수를 호출해 생성한다. 이때 생성된 심볼 값은 다른 심볼 값들과 다른 유일한 심볼값이다. 

// 심볼 key는 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키
var key = Symbol('key');
console.log(typeof key); // symbol

var obj = {};
obj[key] = 'value';
console.log(obj[key]); // value

 

2) 객체 타입 (Object Type, Reference Type)

객체는 데이터와 그 데이터에 관련한 동작(절차, 방법, 기능)을 모두 포함할 수 있는 개념적 존재이다. 이름과 값을 가지는 데이터를 의미하는 프로퍼티(property)와 동작을 의미하는 메서드(method)를 포함할 수 있는 독립적 주체이다.

자바스크립트는 객체(object) 기반의 스크립트 언어로서 자바스크립트를 이루고 있는 거의 모든 것이 객체이다. 원시 타입을 제외한 나머지 값들(배열, 함수, 정규 표현식 등)은 모두 객체이다. 또한 객체는 pass-by-reference(참조에 의한 전달) 방식으로 전달된다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

remote의 branch 들을 확인할 수 있는 명령어 (원격 브랜치 확인)

$ git branch -r

 

remote branch 삭제하는 명령어 (원격 브랜치 삭제)

$ git push origin --delete [branch name]

 

local branch 삭제하는 명령어 (로컬 브랜치 삭제)

$ git branch -d [branch name]
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

자바스크립트의 기본 문법

1. 변수

변수(Variable)는 값(value)를 저장(할당)하고 그 저장된 값을 참조하기 위해 사용한다. 한번 쓰고 버리는 값이 아닌 유지(캐싱)할 필요가 있는 값은 변수에 담아 사용한다. 또한 변수 이름을 통해 값의 의미를 명확히 할 수 있어 코드의 가독성이 좋아진다.

변수는 위치(주소)를 기억하는 저장소이다. 위치란 메모리 상의 주소(address)를 의미한다. 즉, 변수란 메모리 주소(Memory address)에 접근하기 위해 사람이 이해할 수 있는 언어로 지정한 식별자(identifier)이다.

변수를 선언할 때 var 키워드를 사용한다. 할당 연산자 = 는 변수에 값을 할당하기 위해 사용된다.

var x; // 변수의 선언
x = 10; // 정수값의 할당

 

2. 값

var str = 'Hello world';

위 문자열 리터럴 'Hello world'는 문자열 타입의 값이다.

// 숫자 리터럴
10.50
1024

// 문자열 리터럴
'Hello'
"World"

// 불린 리터럴
true
false

// null 리터럴
null

// undefined 리터럴
undefined

// 객체 리터럴
{ name: 'Jeon', gender: 'female' }

// 배열 리터럴
[ 1, 2, 3 ]

// 정규식 리터럴
/ab+c/

// 함수 리터럴
function() {}

숫자, 문자열, 불린과 같은 원시 타입의 리터럴은 다양한 연산자의 피연산자가 되어 하나의 값으로 평가될 수 있다. 이렇게 리터럴은 연산에 의해 하나의 값이 될 수 있다.

// 산술 연산
1024 * 2 + 512

자바스크립트의 모든 값은 데이터 타입을 갖는다. 자바스크립트는 7가지 데이터 타입을 제공한다.

# 원시 타입 (primitive type)

- number

- string

- boolean

- null

- undefined

- symbol (ES6)

 

# 객체 타입 (Object data type)

- object

 

자바스크립트는 다른 언어와 다르게 변수를 선언할 때 데이터 타입을 미리 지정하지 않는다. 다시 말해, 변수에 할당된 값의 타입에 의해 동적으로 변수 타입이 결정된다. 이를 동적 타이핑이라 하며 자바스크립트가 다른 프로그래밍 언어와 구별되는 특징 중 하나이다.

 

3. 연산자

연산자(Operator)는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입 연산 등을 수행해 하나의 값을 만든다. 이때 연산의 대상을 피연산자(Operand)라 한다.

// 산술 연산자
var area = 5 * 4 + 3; // 23

// 문자열 연결 연산자
var str = 'My name is ' + 'Jeon'; // "My name is Jeon"

// 할당 연산자
var color = 'red'; // "red"

// 비교 연산자
var foo = 3 > 5; // false

// 논리 연산자
var bar = (5 > 3) && (2 < 4);  // true

// 타입 연산자
var type = typeof 'Hi'; // "string"

// 인스턴스 생성 연산자
var today = new Date(); // Sat Dec 01 2018 00:57:19 GMT+0900 (한국 표준시)

피연산자의 타입은 반드시 일치할 필요는 없다. 이때 자바스크립트는 암묵적 타입 강제 변환을 통해 연산을 수행한다.

var foo = 1 + '10'; // '110'
var bar = 1 * '10'; // 10

 

4. 키워드

키워드는 수행할 동작을 규정한 것이다. 예를 들어 var 키워드는 새로운 변수를 생성할 것을 지시한다.

// 변수의 선언
var x = 3 + 9; 

// 함수의 선언
function foo (arg) {
  // 함수 종료 및 값의 반환
  return ++arg;
}

var i = 0;
// 반복문
while (1) {
  if (i > 5) {
  	break;
  }
  console.log(i);
  i++;
}

 

 

...

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

화살표 함수 (Arrow function)

1. 화살표 함수의 선언

화살표 함수(Arrow function)는 function 키워드 대신 화살표(=>)를 사용하여 보다 간략한 방법으로 함수를 선언할 수 있다. 하지만 모든 경우 화살표 함수를 사용할 수 있는 것은 아니다. 화살표 함수의 기본 문법은 아래와 같다.

// 매개변수 지정 방법
    () => { ... } // 매개변수가 없는 경우
     x => { ... } // 매개변수가 한 개인 경우, 소괄호를 생략할 수 있다.
(x, y) => { ... } // 매개변수가 여러 개인 경우, 소괄호를 생략할 수 없다.

// 함수 문제 지정 방법
x => { return x * x } // single line block
x => x * x			  // 함수 몸체가 한줄의 구문이라면 중괄호를 생략할 수 있으며 암묵적으로 return된다. 위 표현과 동일하다.

() => { return { a: 1}; }
() => ({ a: 1}); 	// 위 표현과 동일하다. 객체 반환시 소괄호를 사용한다.

() => {				// multi line block
  const x = 10;
  return x * x;
}

 

2. 화살표 함수의 호출

화살표 함수는 익명 함수로만 사용할 수 있다. 따라서 화살표 함수를 호출하기 위해서는 함수 표현식을 사용한다.

// ES5
var pow = function (x) { return x * x; };
console.log(pow(10)); // 100

// ES6
const pow = x => x * x;
console.log(pow(10)); // 100

또는 콜백 함수로 사용할 수 있다. 이 경우 일반적인 함수 표현식보다 표현이 간결하다.

// ES5
var arr = [1, 2, 3];
var pow = arr.map(function (x) {
    return x * x;
}
console.log(pow); // [1, 4, 9]

// ES6
const arr = [1, 2, 3];
const pow = arr.map(x => x * x);
console.log(pow); // [1, 4, 9]

 

3. this

function 키워드로 생성한 일반 함수와 화살표 함수의 가장 큰 차이점은 this이다.

 

1) 일반함수의 this

자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정된다. 다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.

 

콜백 함수 내부의 this는 전역 객체 window를 가리킨다.

function Prefixer(prefix) {
	this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
	// (A)
    return arr.map(function (x) {
    	return this.prefix + ' ' + x; // (B)
    });
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

(A) 지점에서의 this는 생성자 함수 Prefixer가 생성한 객체, 즉 생성자 함수의 인스턴스(pre)이다.

(B) 지점에서 사용한 this는 아마도 생성자 함수 Prefixer가 생성한 객체가 아니라 전역 객체 window를 가리킨다. 이는 생성자 함수와 객체의 메소드를 제외한 모든 함수(내부 함수, 콜백 함수 포함) 내부의 this는 전역 객체를 가리키기 때문이다.

 

콜백 함수 내부의 this가 메서드를 호출한 객체(생성자 함수의 인스턴스)를 가리키게 하려면 아래의 3가지 방법이 있다.

// Solution 1: that = this
function Prefixer(prefix) {
  this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
  var that = this;  // this: Prefixer 생성자 함수의 인스턴스
  return arr.map(function (x) {
    return that.prefix + ' ' + x;
  });
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));
// Solution 2: map(func, this)
function Prefixer(prefix) {
  this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
  return arr.map(function (x) {
    return that.prefix + ' ' + x;
  }, this); // this: Prefixer 생성자 함수의 인스턴스
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

ES5에 추가된 Function.prototype.bind()로 this를 바인딩한다.

// Solution 3: bind(this)
function Prefixer(prefix) {
  this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
  return arr.map(function (x) {
    return that.prefix + ' ' + x;
  }.bind(this)); // this: Prefixer 생성자 함수의 인스턴스
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

2) 화살표 함수의 this

일반 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다고 하였다.

화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다. 동적으로 결정되는 일반 함수와는 달리 화살표 함수의 this는 언제나 상위 스코프의 this를 가리킨다. 이를 Lexical this라 한다. 화살표 함수는 앞서 살펴본 Solution 3의 Syntactic sugar 이다.

 

* 화살표 함수의 this 바인딩 객체 결정 방식은 함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프와 유사하다.

function Prefixer(prefix) {
  this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
  // this는 상위 스코프인 prefixArray 메소드 내의 this를 가리킨다.
  return arr.map(x => `${this.prefix} ${x}`);
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

화살표 함수는 call, apply, bind 메서드를 사용하여 this를 변경할 수 없다.

window.x = 1;
const normal = function () { return this.x; };
const arrow = () => this.x;

console.log(normal.call({ x: 10 })); // 10
console.log(arrow.call({ x: 10 })); // 1

 

4. 화살표 함수를 사용해서는 안되는 경우

화살표 함수는 Lexical this를 지원하므로 콜백 함수로 사용하기 편리하다. 하지만 화살표 함수를 사용하는 것이 오히려 혼란을 불러오는 경우도 있으므로 주의하여야 한다.

 

1) 메서드

화살표 함수로 메서드를 정의하는 것은 피해야 한다. 화살표 함수로 메서드를 정의해 보자.

// Bad
const person = {
	name: 'Jeon',
    sayHi: () => console.log(`Hi ${this.name}`)
};

person.sayHi(); // Hi undefined

위 예제의 경우, 메서드로 정의한 화살표 함수 내부의 this는 메서드를 소유한 객체, 즉 메서드를 호출한 객체를 가리키지 않고 상위 컨택스트인 전역 객체 window를 가리킨다. 따라서 화살표 함수로 메서드를 정의하는 것은 바람직하지 않다.

이와 같은 경우 메서드를 위한 단축 표기법인 ES6의 축약 메서드 표현을 사용하는 것이 좋다.

// Good
const person = {
	name: 'Jeon',
    sayHi() { // == sayHi: funcion() {
    	console.log(`Hi ${this.name}`);
    }
};

person.sayHi(); // Hi Jeon

 

2) prototype

화살표 함수로 정의된 메서드를 prototype에 할당하는 경우도 동일한 문제가 발생하다. 화살표 함수로 정의된 메서드를 prototype에 할당하여 보자.

// Bad
const person = {
  name: 'Jeon',
};

Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);

person.sayHi(); // Hi undefined

 

화살표 함수로 객체의 메서드를 정의했을 때와 같은 문제가 발생한다. 따라서 prototype에 메서드를 할당하는 경우, 일반 함수를 할당한다.

// Good
const person = {
  name: 'Jeon',
};

Object.prototype.sayHi = function() {
  console.log(`Hi ${this.name}`);
};

person.sayHi(); // Hi Jeon

 

3) 생성자 함수

화살표 함수는 생성자 함수로 사용할 수 없다. 생성자 함수는 prototype 프로퍼티를 가지머 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 하지만 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.

const Foo = () => {};

// 화살표 함수는 prototye 프로퍼티가 없다
console.log(Foo.hasOwnProperty('prototype')); // false

const foo = new Foo(); //TypeError: Foo is not a constructor

 

4) addEventListener 함수의 콜백 함수

addEventListener 함수의 콜백 함수를 화살표 함수로 정의하면 this가 상위 컨텍스트인 객체 window를 가리킨다.

// Bad 
var button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log(this === window); // true
  this.innerHtml = 'Clicked button';
});

따라서 addEventListener 함수의 콜백 함수 내에서 this를 사용하는 경우, function 키워드로 정의한 일반 함수를 사용하여야 한다. 일반 함수로 정의된 addEventListener 함수의 콜백 함수 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다.

// Good
var button = document.getElementById('myButton');

button.addEventListener('click', function() {
  console.log(this === button); // true
  this.innerHtml = 'Clicked button';
});
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !