[JS] TIL 6. 스코프, 클로저, 객체지향, 매개변수


스코프(Scope)

  1. JavaScript의 Scope의 의미와 적용 범위를 이해할 수 있다.

  2. 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)

  1. Closure의 의미와 Closure가 가지는 Scope Chain을 이해할 수 있다

  2. Closure의 유용하게 쓰이는 몇 가지 코딩 패턴을 이해할 수 있다.

매개변수

  1. Parameter의 갯수가 유동적인 함수를 만들 수 있다.

  2. ES6에서 사용하는 Rest parameter 및 ES5의 방법인 arguments 키워드를 이용할 수 있다.

  3. Default parameter를 사용할 수 있다.

객체 지향 JavaScript

  1. 객체 지향 프로그래밍의 기본적인 컨셉을 이해할 수 있다.

    1.1 class, instance 등의 용어를 이해할 수 있다.

    1.2 new 키워드를 사용해 instance를 생성할 수 있다.

    1.3 S6 class 키워드를 사용할 수 있다.

  2. 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는 밖에서 접근할 수 없다. 이것을 통해 우리 눈에 보이지 않는 경계가 있음을 알 수 있다. 이것이 스코프이다.

title

위 그림은 스코프를 우리가 볼 수 있도록 표시한 것이다. Local scope 범위 내에서 선언된 것은 Global scope에서는 참조할 수 없다. 즉, 위 그림에서 보는 것처럼 firstNam에 접근할 수 있는 범위는 Local scope에 한정되어있다.

1.2 Scope의 규칙

1.2.1 Local Scope vs. Global Scope

Local ScopeGlobal Scope는 부모자식으로 보면 이해가 쉽다. 위에서 확인했듯이 Local Scope범위 내에서 선언한 변수는 밖에서 사용할 수 있다. 하지만 Global Scope에서 선언한 것은 Local Scope에서 사용할 수 있다. 즉, greeting이라는 변수는 함수 안쪽에서 사용할 수 있다는 말이다.

title

이러한 것을 미루어보면 스코프는 ¹중첩이 가능하다. 함수 안에 함수를 넣을 수도 있다는 말이다. 또한 ²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 ScopeBlock 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)

title

1.2.3 전역 변수와 window 객체

크롬의 Developer console 창에 window라고 검색을 해보면 객체가 하나 나온다. 이 window 객체는 전역 범위를 대표하는 객체이다. 그리고 var를 이용해 선언하거나 키워드 없이 선언한 변수는 이 window 객체와 연결이 된다.

title

window도 결국은 객체이기 때문에 굉장히 다양한 키와 값들이 존재하며, 이것들은 전부 전역 영역에서 사용할 수 있다.



2. 클로저(Closure)

클로저란 외부함수의 변수에 접근할 수 있는 내부 함수 또는, 이런 작동원리를 일컫는 용어이다.

title

그렇다면 위의 코드엣 가장 하위에 있는innerFn()가 접근할 수 있는 Scope는 3개이다. 즉, outerFn()과 fountion에서 완전히 벗어난 위치까지 말이다.

title

outerFn()을 실행시키면, console.log(outerVar) ; 의 값(outer)이 먼저 콘솔 창에 찍힐 것이다. 그리고서는 innerFn() { 의 형태로 되어있는 아직 실행되지 않은 함수가 리턴된다. 즉, outerFn()의 리턴 값은 함수이다.

title

innerFn()도 실행시키려면 위의 코드처럼 outerFn을 두 번 실행시켜주면 된다. outerFn()() 이런 형태가 가능한 것은 위에서 보았듯이 outerFn() 자체가 함수이기 때문이다. 해서 아직 실행되지 않은 함수를 리턴하지 않고, innerFn()안에 있는 console.log(innerVar)를 실행시킨 것이다.

title

outerFn()실행 시켰을 때, console.log(outerVar)와 리턴값인 실행되지 않은 함수, innerFn이 출력된다. 이것을 let innerFn = outerFn();를 통해innerFn이라는 변수에 담아주었다. console.log(outerVar) 값만 찍히고 return값만 찍히는 이유는 실행이 아니라 할당을 했기 때문이다.

title

outerFn()를 담은 innerFn를 실행시키면 역시나 실행되지 않은 함수 그 자체가 출력된다.

title

이 상태에서 innerFn이라는 변수를 실행시키면 (innerFn()인 이유는 innerFn이라는 변수 안에 innerFn()이라는 함수가 들어갔기 때문에 소괄호를 붙여 함수 실행할때 처럼 만들어준다.) inner라는 스트링만 딸랑 나온다. 왜냐하면 innerFn() 함수만 실행시켰기 때문이다.

title

이런 패턴 자체를 클로저 함수라고 부른다. 위의 예제에서는 innerFn()이라고 되어있는 Function을 클로저 함수라고 부른다. 클로저 함수 안에서는 지역 변수(innerVar), 외부함수변수(outerVar), 전역변수(gobalVar), 이 세 가지 스코프의 접근이 가능하다.



3. 객체 지향 JavaScript

객체 지향 프로그래밍이란, 하나의 모델이 되는 청사진(blueprint)을 만들고, 그 청사진을 바탕으로 한 객체(object)를 만드는 프로그래밍 패턴이다.

title

예를 들어서 car는 청사진이고, car 브랜드는 여러 개가 있다. 현대의 Avante, BMW의 Mini도 있을 수도 있고, Beetles가 있을 수도 있다. 이런 식으로 car라는 청사진을 통해서 찍어낸 게 바로 객체(Audi, Nissan, Volve)고, 이런 패턴을 보고 객체지향 프로그래밍이라고 한다.

title

여기서 잘 기억해야 하는 것은 청사진은 Class, 이런 청사진을 바탕으로 한 객체는 Instance라고 한다는 것이다. 그냥 객체처럼 괄호 닫고 괄호 열고, 그런 단순한 과정으로 만들 수 있게는 아니다.

title

자바스크립트 패턴으로 봤을 때, 아래처럼 표현이 가능하다. 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');

InstancesCar라는 클래스의 고유한 속성과 메소드를 가지며, 생성될 때 new 키워드를 사용한다.

title

콘솔창에 찍어보면 다음과 같다. ClassCar의 매개변수 자리에는 brand, model, color라는 있다. 지금은 InstanesClass가 어떻게 작용하는지 알아보기 위해서, 인스턴스가 만들어질 때 실행되는 코드는 작성하지 않았다. 그동안, function 키워드로 시작한 함수를 실행할 때는 Car()이라는 형태를 사용했지만, 지금은 new 키워드와 변수를 사용하여 실행시킬 것이다. (let avante = new Car(‘hyundai’, ‘avante’, ‘black’);)

이런 식으로 선언해준 avante를 콘솔 창에 찍으면 Car{}라고 찍힌다. 이것은 ‘나는 Car야, Car라는 class의 instances야’라는 말이다. 다시 한번 말하면 function Car(brand, model, color){}Classavanteinstanes이다.

title

객체 지향에는 속성과 메소드가 있다. 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()   // 미니에 연료를 공급합니다.

miniavante나 동일한 성격을 가지고 있지만, 그 성격의 값은 각각 다르다. 값이 다르다. 그래서 고유의 속성이 표현된다. 이렇게 만들어주는 것이 this 키워드이다.



4. 매개변수

function timeToGoHome(speed, distance) {
    let time = distance/sepeed;
    return time;
}

timeToGoHome(20, 100) ;

지금까지 우리가 배웠던 매개변수는 function 함수명(parameters) {} 형태로 사용되었을 것이다. 또한, 이 함수를 사용할 때는 함수명(전달인자)으로 사용했다. timeToGoHome의 parameters는 speeddistance이고, 전달 인자는 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를 넘겨줬을 때 기본값으로 처리한다.




© 2020. by RIVER

Powered by RIVER