[JS] TIL 6. 스코프, 클로저, 객체지향, 매개변수
in PROGRAMING STUDY on Javascript
스코프(Scope)
JavaScript의 Scope의 의미와 적용 범위를 이해할 수 있다.
JavaScript의 Scope 주요 규칙을 이해할 수 있다.
2.1 중첩 규칙
2.2 block scope(block-level scope) vs. function scope(function-level scope)
2.3
let
,const
,var
의 차이2.4 전역 변수와 전역 객체의 의미
클로저(Closure)
Closure의 의미와 Closure가 가지는 Scope Chain을 이해할 수 있다
Closure의 유용하게 쓰이는 몇 가지 코딩 패턴을 이해할 수 있다.
매개변수
Parameter의 갯수가 유동적인 함수를 만들 수 있다.
ES6에서 사용하는 Rest parameter 및 ES5의 방법인
arguments
키워드를 이용할 수 있다.Default parameter를 사용할 수 있다.
객체 지향 JavaScript
객체 지향 프로그래밍의 기본적인 컨셉을 이해할 수 있다.
1.1 class, instance 등의 용어를 이해할 수 있다.
1.2
new
키워드를 사용해 instance를 생성할 수 있다.1.3 S6
class
키워드를 사용할 수 있다.prototype을 이용해 클래스의 원형을 만드는 방법을 이해할 수 있다.
Codestates의 소프트웨어 엔지니어링 강의를 듣고 정리한 필기입니다. 😀
1. 스코프(Scope)
1.1 Scope의 정의
Scope는 영어 단어로써, 범위라는 뜻을 가지고 있다. 범위라는 개념이 있어야, 작성된 코딩이 어디까지 작동하고, 어디까지 작동하지 않는지를 알 수 있다. 이는 잘 작동하는 코드를 작성하는 데 있어 잘 알아야 하는 기초나 마찬가지다.
let greeting = 'Hello'
function greetSomeone() {
let firstName = 'Josh';
return greeting + ' ' + firstName;
}
greetSomeone(); // => 'Hello Josh'
firstName; // => ReferenceError
위 코드를 읽어보면, firstName
이라는 변수를 콘솔에 찍어보면 RefenceError
가 난다. 변수가 정의된 적이 없다는 뜻이다. 즉, greetSomeone()
을 사용한 후에도 선언할때 정의된 변수firstName
는 밖에서 접근할 수 없다. 이것을 통해 우리 눈에 보이지 않는 경계가 있음을 알 수 있다. 이것이 스코프이다.
위 그림은 스코프를 우리가 볼 수 있도록 표시한 것이다. Local scope
범위 내에서 선언된 것은 Global scope
에서는 참조할 수 없다. 즉, 위 그림에서 보는 것처럼 firstNam
에 접근할 수 있는 범위는 Local scope
에 한정되어있다.
1.2 Scope의 규칙
1.2.1 Local Scope
vs. Global Scope
Local Scope
랑 Global Scope
는 부모자식으로 보면 이해가 쉽다. 위에서 확인했듯이 Local Scope
범위 내에서 선언한 변수는 밖에서 사용할 수 있다. 하지만 Global Scope
에서 선언한 것은 Local Scope
에서 사용할 수 있다. 즉, greeting
이라는 변수는 함수 안쪽에서 사용할 수 있다는 말이다.
이러한 것을 미루어보면 스코프는 ¹중첩이 가능하다. 함수 안에 함수를 넣을 수도 있다는 말이다. 또한 ²Global Scope
에서 정의된 변수(전역변수)는 어디서든 접근이 가능하며, ³지역변수는 함수 내에서 전역변수보다 더 높은 순위를 가진다.
let name = "Richard";
function showName() {
let name = "Jack";
console.log(name); // => Jack
}
console.log(name); // => Richard
showName();
console.log(name); // => Richard
위 코드를 보면 Jack
이라는 값은 지역변수에서 선언된 것이기 때문에, showName()
함수를 사용하든 하지 않든 밖에서의 name
의 값은 Richard
이다.
let name = "Richard";
function showName() {
name = "Jack"; // let이라는 키워드 없이 사용
console.log(name); // => Jack
}
console.log(name); // => Richard
showName();
console.log(name); // => Jack
let
이라는 키워드가 없으면 전역변수(Global scope
)로 변수가 선언되어, showName()
이라는 변수를 사용하면 name
의 값은 Jack
이 된다.
1.2.2 Function Scope
vs. Block Scope
그다음은 Function Scope
와 Block Scope
의 차이에 대한 것이다. Block 이라는 용어를 살펴보면 중괄호로 시작하고 끝나는 단위를 말한다.
if (true) {
console.log('I am in the block');
}
for (let i = 0; i < 10; i ++ ) {
console.log(i);
}
function scope() {
return 'I am in the block'
}
{
console.log('I am in the block');
}
그래서 if문
, for문
, 함수 혹은 그냥 중괄호를 여닫을 수 있다. 그리고 또한 이 Block을 스코프를 구분하는 단위로 생각할 수도 있다.
for(let i=0; i<5; i++) {
console.log(i); // 다섯번 iteration
}
console.log('final i:', i); // ReferenceError
for문
밖에서 사용되는 console.log
는 block 범위를 벗어났기 때문에 레퍼런스 오류가 발생한다. ▶ 선언 키워드(var
, let
)
본 블로그에서는 선언 키워드를 let
만을 사용했지만, 공식 레퍼런스나 다른 블로그를 살펴보면 var
라는 키워드를 사용한 것을 볼 수 있을 것이다. 두 키워드의 차이는 여러 가지가 있지만, Scope 범위에서도 차이가 난다.
자바스크립트는 기본적으로 함수 단위로 자신만의 Scope를 갖는다. 이것은 자바스크립트 엔진이 그렇다는 말이고, var
키워드를 썼을 때는 함수 단위로 자신만의 스코프를 갖는다. (old way) let
키워드는 function
키워드처럼 뭉뚱그려진 것이 아니라 Block 단위로 구분되어 훨씬 더 예측하기 쉬운 코드이다.
for (var i = 0; i < 5; i ++ ) {
console.log(i);
}
console.log('final i:' i); // => 5
선언 키워드를 var
로 하였기 때문에 block에 한정되어있지 않고, for문
의 block을 벗어나도 i
를 쓸 수 있게 된다. 비단 이것만 본다면 ‘편한데?’ 라고 생각할 수도 있다. 하지만 이러한 것을 Scope 오염을 초래할 수 있다.(키워드를 아예 사용하지 않고 선언하는 것 또한 마찬가지다.)
첫번째 문제는 유효범위 즉, Scope가 어디까지인지를 모른다는 것이다. 때문에 뒤에서 i
를 재사용/재선언을 할 수 있다는 위험이 있다. 재사용/재선언을 한다면, 코드를 읽는 사람들(혹은 나중에 자신이 작성한 코드를 읽을 때)은 해당 변수에 어떤 값이 들어있는지 정확히 모를 것이다.
▶ 선언 키워드(var
, let
, const
)
1.2.3 전역 변수와 window
객체
크롬의 Developer console 창에 window
라고 검색을 해보면 객체가 하나 나온다. 이 window
객체는 전역 범위를 대표하는 객체이다. 그리고 var
를 이용해 선언하거나 키워드 없이 선언한 변수는 이 window
객체와 연결이 된다.
window
도 결국은 객체이기 때문에 굉장히 다양한 키와 값들이 존재하며, 이것들은 전부 전역 영역에서 사용할 수 있다.
2. 클로저(Closure)
클로저란 외부함수의 변수에 접근할 수 있는 내부 함수 또는, 이런 작동원리를 일컫는 용어이다.
그렇다면 위의 코드엣 가장 하위에 있는innerFn()
가 접근할 수 있는 Scope는 3개이다. 즉, outerFn()
과 fountion에서 완전히 벗어난 위치까지 말이다.
outerFn()
을 실행시키면, console.log(outerVar)
; 의 값(outer)이 먼저 콘솔 창에 찍힐 것이다. 그리고서는 innerFn() {
의 형태로 되어있는 아직 실행되지 않은 함수가 리턴된다. 즉, outerFn()
의 리턴 값은 함수이다.
innerFn()
도 실행시키려면 위의 코드처럼 outerFn
을 두 번 실행시켜주면 된다. outerFn()()
이런 형태가 가능한 것은 위에서 보았듯이 outerFn()
자체가 함수이기 때문이다. 해서 아직 실행되지 않은 함수를 리턴하지 않고, innerFn()
안에 있는 console.log(innerVar)
를 실행시킨 것이다.
outerFn()
실행 시켰을 때, console.log(outerVar)
와 리턴값인 실행되지 않은 함수, innerFn
이 출력된다. 이것을 let innerFn = outerFn();
를 통해innerFn
이라는 변수에 담아주었다. console.log(outerVar)
값만 찍히고 return
값만 찍히는 이유는 실행이 아니라 할당을 했기 때문이다.
outerFn()
를 담은 innerFn
를 실행시키면 역시나 실행되지 않은 함수 그 자체가 출력된다.
이 상태에서 innerFn
이라는 변수를 실행시키면 (innerFn()
인 이유는 innerFn
이라는 변수 안에 innerFn()
이라는 함수가 들어갔기 때문에 소괄호를 붙여 함수 실행할때 처럼 만들어준다.) inner
라는 스트링만 딸랑 나온다. 왜냐하면 innerFn()
함수만 실행시켰기 때문이다.
이런 패턴 자체를 클로저 함수라고 부른다. 위의 예제에서는 innerFn()
이라고 되어있는 Function을 클로저 함수라고 부른다. 클로저 함수 안에서는 지역 변수(innerVar
), 외부함수변수(outerVar
), 전역변수(gobalVar
), 이 세 가지 스코프의 접근이 가능하다.
3. 객체 지향 JavaScript
객체 지향 프로그래밍이란, 하나의 모델이 되는 청사진(blueprint)을 만들고, 그 청사진을 바탕으로 한 객체(object)를 만드는 프로그래밍 패턴이다.
예를 들어서 car
는 청사진이고, car
브랜드는 여러 개가 있다. 현대의 Avante
, BMW의 Mini
도 있을 수도 있고, Beetles
가 있을 수도 있다. 이런 식으로 car
라는 청사진을 통해서 찍어낸 게 바로 객체(Audi
, Nissan
, Volve
)고, 이런 패턴을 보고 객체지향 프로그래밍이라고 한다.
여기서 잘 기억해야 하는 것은 청사진은 Class
, 이런 청사진을 바탕으로 한 객체는 Instance
라고 한다는 것이다. 그냥 객체처럼 괄호 닫고 괄호 열고, 그런 단순한 과정으로 만들 수 있게는 아니다.
자바스크립트 패턴으로 봤을 때, 아래처럼 표현이 가능하다. Class
는 단순 함수(function)이고, Instance
를 만들 때 Class
를 사용하는데, new
키워드를 써야 하는 것을 볼 수 있다.
// ES5는 클래스를 함수로 정의할 수 있다.
function Car(brand, name, color) {
// 인스턴스가 만들어질 때 실행되는 코드
}
// ES6는 class라는 키워드를 이용해서 정의할 수 있다.
class Car(brand, name, color) {
constructor(brand,name, color) {
// 인스턴스가 만들어질 때 실행되는 코드
}
}
class
를 만들 때 두 가지 방법을 사용할 수 있다. 하나는 ES5의 문법이고, 하나는 ES6의 문법이다. ES5는 보통 함수를 정의하듯이 하면 되지만, ES6는 constructor
라는 키워드도 사용한다.
여기서 재미있는 것은 ES5, ES6 모두 Class
의 이름이 대문자로 시작한다는 것이다. 이것은 약속이기 때문에 소문자로 클래스가 작성되어있으면 이상한 것이다
let avante = new Car ('hyundai', 'avante','blue');
let mini = new Car ('bmw', 'mini','light green');
let beetles = new Car ('volkswagen', 'beetles','pink');
Instances
는 Car
라는 클래스의 고유한 속성과 메소드를 가지며, 생성될 때 new
키워드를 사용한다.
콘솔창에 찍어보면 다음과 같다. Class
인 Car
의 매개변수 자리에는 brand
, model
, color
라는 있다. 지금은 Instanes
와 Class
가 어떻게 작용하는지 알아보기 위해서, 인스턴스가 만들어질 때 실행되는 코드는 작성하지 않았다. 그동안, function 키워드로 시작한 함수를 실행할 때는 Car()
이라는 형태를 사용했지만, 지금은 new
키워드와 변수를 사용하여 실행시킬 것이다. (let avante = new Car(‘hyundai’, ‘avante’, ‘black’);
)
이런 식으로 선언해준 avante
를 콘솔 창에 찍으면 Car{}
라고 찍힌다. 이것은 ‘나는 Car야, Car라는 class의 instances야’라는 말이다. 다시 한번 말하면 function Car(brand, model, color){}
는 Class
고 avante
는instanes
이다.
객체 지향에는 속성과 메소드가 있다. Class
(객체지향 자바스크립트)는 현실 세계에 있는 코드를 컴퓨터로 옮기고자 할 때 많이 사용된다.
Car
라는 Class
의 속성에는 브랜드, 모델, 최대속도, 현재 연료 등이 표현되고, 메소드라는 것은 Car
라는 class
를 통해서 하고자 하는 액션이다. 메소드는 실행하는 함수이기 때문에, Refuel()
은 연료를 채우는 것이고, setSpeed()
는 속도를 정하는 것이고, drive()
는 운전하는 것이다. 속성명, 메소드 명을 보면 현실에서의 행동과 매우 비슷하다.
현재 내가 이 차가 가지고 있는 속성이나, 할 수 있는 정의를 클래스에 해주는 것이다. 그리고 인스턴스에서 이것들을 이용한다.
// ES5
function Car(brand, name, color) {
this.brand = brand;
this.name = name;
this.color = color;
}
Car.prototype.refuel = function() {
console.log(this.brand + '가 운전을 시작합니다.');
}
Car.prototype.drive = function() {
console.log(this.brand + '에 연료를 공급합니다.');
}
// ES6
class Car(brand, name, color) {
constructor(brand,name, color) {
this.brand = brand;
this.name = name;
}
refuel () {
console.log(this.brand + '가 운전을 시작합니다.');
}
drive () {
console.log(this.brand + '에 연료를 공급합니다.');
}
}
속성과 메소드의 정의는 위처럼 하면 된다. 처음 보는 키워드가 나온다. this
. this
에 대한 이야기는 다음에 할 예정이다. 다만 기억할 것은 this
를 이용해서 속성을 지정해준다는 것이다.
let avante = new Car ('hyundai', 'avante','blue');
avante.color // => 'blue'
avante.drive() // 아반떼가 운전을 시작합니다.
let mini = new Car ('bmw', 'mini','light green');
mini.brand // => 'bmw'
mini.refuel() // 미니에 연료를 공급합니다.
mini
나 avante
나 동일한 성격을 가지고 있지만, 그 성격의 값은 각각 다르다. 값이 다르다. 그래서 고유의 속성이 표현된다. 이렇게 만들어주는 것이 this
키워드이다.
4. 매개변수
function timeToGoHome(speed, distance) {
let time = distance/sepeed;
return time;
}
timeToGoHome(20, 100) ;
지금까지 우리가 배웠던 매개변수는 function 함수명(parameters) {}
형태로 사용되었을 것이다. 또한, 이 함수를 사용할 때는 함수명(전달인자)
으로 사용했다. timeToGoHome
의 parameters는 speed
와 distance
이고, 전달 인자는 20과 10이다.
Math.max(3,5,8,10); // => 10
Math.min(3,5,8,10); // => 3
function getMaxNum( ) { // 이때 파라미터를 어떻게 줄까?
}
Math.max(3,5,8,10); // => 10
하지만 Math.max
처럼 전달 인자의 길이가 유동적이라면 어떻게 표현해야 할까.
function getMaxNum( ...nums ) {
console.log(nums) // [ 3, 5, 8, 10 ]
return nums.reudce(function(acc, cur) {
if (acc > cur) {
return acc;
} else {
return cur;
}
});
}
Math.max(3,5,8,10); // => 10
바로 Rest Parameter(...)
을 이용해 매개변수를 지정해준다. 이때는 매개변수가 배열로 전달된다.
function getMaxNum( arguments ) {
console.log(arguments) // [ 3, 5, 8, 10 ]
return arguments.reudce(function(acc, cur) {
if (acc > cur) {
return acc;
} else {
return cur;
}
});
}
Math.max(3,5,8,10); // => 10
arguments
라는 키워드를 이용할 수도 있다. (ES5)
function getRoute(destination, departure = 'ICN') {
return '출발지 : ' + departure + ', '
+ '도착지 : ' + destination;
}
getRoute('KOR'); // => '출발지 : ICN, 도착지 : KOR'
매개변수에 기본값(Default Parameter)를 할당해줄 수도 있다.
function getRoute(departure = 'ICN', destination) {
return '출발지 : ' + departure + ', '
+ '도착지 : ' + destination;
}
getRoute(undefined, 'KOR'); // => '출발지 : ICN, 도착지 : KOR'
이러한 기본값이 중간이나 처음에 있다면 undefined
를 넘겨줬을 때 기본값으로 처리한다.