프론트 공부

모던 자바스크립트 Deep Dive 12~14장 공부

KimMercury 2023. 3. 14. 00:31

12장 함수

수학은 자바스크립트에서 가장 중요한 핵심 기능이다.

  • 프로그래밍에서의 함수의 개념은 수학적 개념과 같다.
  • 수학적 함수는 "입력" 을 받아 "출력"을 내보내는 일련의 과정이다.

수학적 함수
프로그래밍 함수

  • 프로그래밍 언어의 함수는 일련의 과정을 문으로 구현하고 코드블록으로 감싸서 하나의 실행 단위로 정의한 것
  • 함수 내부에 전달 받는 변수를 매개변수, 입력은 인수, 출력을 반환값 이라 한다.
  • 함수는 값이며, 여러개가 존재할 수 있어 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용할 수 있다.

함수를 사용하는 이유

  • 함수는 여러번 호출이 가능하다.
  • 실행 시점을 개발자가 결정 가능하다.

중복 코드를 제거하고 함수를 여러번 호출

  • 중복 코드를 제거하면 사람의 실수가 줄어들어 코드의 신뢰성이 증가한다.
  • 유지보수가 편의성이 중가한다.

함수 리터럴

  • 자바스크립트의 함수는 객체 타입의 값이다.
  • 함수 리터럴은 function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성된다.

함수 리터럴

함수 리터럴 구성 요소

함수 리터럴 구성요소

  • 익명 함수: 함수 리터럴 방식으로 만들어진 이름 없는 함수
  • 함수를 재사용 하는 경우에는 함수 선언문, 함수 표현식 같은 일반 함수를 사용해야 한다.
    • 함수의 이름이 선언 되어있지 않아 재사용이 어렵다!!!
익명함수 형태
function() {
  console.log("hello!");
}
var test = function( a )
  {
   console.log("hello");
  };

function hello(){ // 익명함수 사용 X
  alert("안녕하세요. 환영합니다.");
}
$("#btn").click(hello);

$("#btn").click(function(){ // 익명함수 사용 O
  alert("안녕하세요. 환영합니다.");
});
  • 호이스팅(함수 선언 보다 함수 호출 윗 줄에 위치 해도 실행되는 기능)이 적용되지 않는다.
//익명 함수 호출
orange( );

//익명 함수 선언
var orange = function( ) {
  document.write("This is an orange.");
};
// result : 오류

함수 정의

함수 정의

함수 선언문

  • 함수 선언문은 함수 리터럴 형태와 동일하다. 그러나 함수 선언문은 이름 생략이 불가능하다.
  • 함수 선언문은 표현식이 아니라 문이다.
    • 표현식: 
      • 단일 값을 생성한다
      • 값으로 평가될 수 있는 문
      • 프로그램을 구성하는 기본 단위
함수 선언문
function add(x,y){
	return x + y
}

함수 표현식

  • 자바스크립트의 함수에는 값처럼 변수에 할당이 가능하다.
  • 그 값은 프로퍼티, 배열의 요소 가 될 수 있다.
  • 이러한 값의 성질을 갖는 객체를 일급객체 라고 한다.
  • 일급 객체라는 것은 함수를 값처럼 자유롭게 사용이 가능하다는 것을 의미한다.
함수 표현식
var add = function(x,y){
	return x + y 
}

함수 생성 시점 및 호이스팅

console.dir(add); //add(x,y)
console.dir(sub); // undefined

console.log(add(2,5));// 7
console.log(sub(2,5));// Error
함수 선언문
function add(x, y){
	return x + y;
}
함수 표현식
var sub =function(x, y){
	return x + y;
}
  • 함수 선언문에서는 호이스팅이 일어난다.
  • 함수 표현식에서는 호이스팅이 일어나지 않는다.
    • sub()을 실행하면  var sub을 선언하며 함수를 담는다.
    • var는 호이스팅의 영향을 받으므로 위로 끌어올려진다.
    • 따라서 var sub 가 가장 먼저 실행된다.(변수에 아무 값이 없으므로 undefined가 찍힌다.)
    • 그 후 sub()이 호출되면 위에 선언한 sub가 호출되므로 변수를 호출하는 격이 된다.(error : sub is not function)

Function 생성자 함수

var add = new function('x','y','return x + y');
console.log(add(2,5))
  • Function 생성자 함수는 일반적으로 사용을 권장 하지 않는다.
  • Function 생성자 함수는 클로저를 생성하지 않는다.

화살표 함수

  • ES6에서 도입된 함수이다.
  • function 키워드 대신에 =>을 사용하여 좀 더 간략한 방법으로 함수를 선언할 수 있다.
  • 화살표 함수는 항상 익명 함수로 정한다.
  • 매겨변수가 없을 경우 괄호 필수
  • 본문이 return[식 or 값] 뿐일 경우 {}, return 생략 가능
  • 위에서 return 할 값이 객체인 경우 괄호 필수
화살표 함수
const add = (x,y) => x + y
const add = x => ({ x }); // 인자를 하나만 받는다면 괄호() 생략 가능
const add = (x,y) = a + b; // return 값만 있는 경우 중괄호({}), return 생략 가능

함수 호출

  • 함수는 함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출된다.
  • 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우, 매개변수를 통해 인수를 전달한다.

매개변수와 인수
함수 호출

  • 매개변수는 함수 몸체 내부에서만 참조할 수 있다.
  • 매개변수의 스코프는 함수 내부이다.
  • 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다.
  • 매개변수보다 인자가 더 많은 경우 초과된 인수는 무시된다.

매개변수 인수 일치 x
매개변수 < 인수 인 경우

반환문

  • 함수는 return 키워드와 반환값을 사용햐 실행 결과를 외부로 반환할 수 있다.
  • 반환문은 함수의 실행을 중단하고 함수를 탈출한다.(반환문 아래의 다른 문들은 동작하지 않는다.)
  • return 키워드 뒤에 오는 표현식을 평가해 반환한다.(return 키워드 뒤에 아무것도 없으면 undefine을 반환한다.)
  • 반환문은 함수의 몸체 내부에서만 사용이 가능하다(밖에서 사용시 Error 발생)

참조에 의한 전달과 외부 상태의 변화

  • 매개변수도 함수 몸체 내부에서 변수로 취급되므로 값에 의한 전달, 참조에 대한 전달 방식이 그대로 작동한다.

다양한 함수의 형태

 

재귀함수

  • 함수가 자기 자신을 호출하는 함수이다.
  • 재귀 함수는 반복되는 처리를 위해 사용된다.
  • 재귀 함수는 탈출 조건이 필요하다(스택 오버플로가 발생할 수 있다.)
재귀 함수
function test(a){
if(a<=0) return;
console.log(a);
 test(a - 1)
}

test(5)

결과: 5,4,3,2,1

중첩 함수

  • 함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라고 부른다.
  • 내부 함수를 포함하는 함수를 외부 함수라고 부른다.
  • 내부 함수는 외부 함수 내부에서만 호출이 가능하다.
  • 일반적으로 내부함수는 외부 함수를 돕는 헬퍼 함수로 사용된다
function out(){
  var x = 1;
  function inner(){
    var y = 2;
    console.log(x + y)
  }
  inner();
}

out();

결과: 3

콜백 함수

  • 다른 함수에 매개변수를 넘겨준 함수이다.
  • 매개변수로 넘겨받은 함수는 일단 넘겨받고, 때가 되면 나중에 호출한다는 것이 콜백 함수의 개념이다.
콜백 함수
function add(a, b, callback) {

  callback(a + b);
}

function sayResult(value) {
   console.log(value);
}

add(3, 4, sayResult);

결과: 7

 

13장 스코프

  • 스코프는 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조 할 수 있는 유효 범위가 결정된다.
  • 스코프는 식별자가 유요한 범위를 말한다.

scope

  • 위의 코드에서 함수의 식별자의 유효 범위는 함수 내부 이기 때문에 Error가 발생한다.

식별자 결정

식별자 결정

  • 자바스크립트 엔진은 이름이 같은 두 개의 변수 중 어떤 변수를 참조할 것인지 결정해야 한다.
  • 자바스크립트 엔진은 스코프를 통해 어떤 변수를 참조할 것인지 결정한다.
  • 스코프란 자바스크립트 엔진이 식별자를 검색할 때 사용되는 규칙이라고도 한다.
  • 하나의 값은 유일한 식별자에 연결되어야 한다.

지역, 전역 스코프

  • 전역 스코프
    • 코드의 가장 바깥 영역
    • 전역 변수
    • 전역 변수는 어디에서든 참조할 수 있다.
  • 지역 스코프
    • 함수 몸체 내부
    • 지역 변수
    • 지역변수는 자신이 선언된 지역 스코프 와 하위 지역 스코프에서 유효하다.

스코프 체인

  • 함수는 중첩이 가능하다.(스코프도 중첩이 가능하다.)
  • 스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미한다.
  • 이때 외부함수의 지역 스코프를 내부 함수의 상위 스코프 라고 한다.
  • 모든 스코프가 하나의 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역 스코프 그리고 스코프가 계층으로 연결된 스코프를 스코프 체인 이라고 한다.
  • 자바스크립트 엔진에서 변수를 참조할때 스코프 체인을 통하여 변수를 참조하는 스코프에서 상위 스코프로 이동하면서 선언된 변수를 검색한다.

스코프 체인에 의한 변수 검색

스코프 체인에 의한 함수 검색
var x = "global x"
var y = "global y"

function outer(){
  var z = "global z"
  function inner(){
    var x = "local's local x"
    console.log(x); // local's local x
    console.log(y); // global y
    console.log(z); // global z
  }
  inner()
}
outer()

함수 레벨 스코프

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

블록 레벨 스코프

  • 모든 코드 블록(함수,if 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다.
  • 코드 블록 내부에서 선언한 변수는 지역 변수이다.

예외

  • var 키워드는 블록 레벨 스코프를 따르지 않는다.
  • var 키워드는 코드 블럭 내의 변수는 전역 변수이다.
  • 전역 변수 foo가 선언되었으나. var 키워드는 중복 선언이 허용되므로 아래 코드는 문제가 없다.
  • 단, 블럭 내의 변수 foo가 전역변수 이기 때문에 전역에 선언된 전역 변수 foo의 값이 (123 -> 456)으로 바뀐다.
블록 레벨 키워드 예외
var foo = 123; // 전역 변수
 
console.log(foo); // 123
 
{
  var foo = 456; // 전역 변수
}
 
console.log(foo); // 456

 

  • ES6에서는 블록 레벨 스코프를 따르는 변수를 선언하기 위해 let 키워드를 제공한다.
let foo = 123; // 전역 변수
 
{
  let foo = 456; // 지역 변수
  let bar = 456; // 지역 변수
}
 
console.log(foo); // 123
console.log(bar); // ReferenceError: bar is not defined

렉시컬 스코프

  • 함수가 어디에 선언되었는지에 따라 상위 스코프를 결정한다.
  • 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다.
  • 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.

동적 스코프

  • 함수가 어디서 호출되었는지에 따라 상위 스코프가 결정된다.
렉시컬 스코프
var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // 10? 1?
bar(); // 1

bar 함수를 호출한 곳은 foo함수 내부였지만, bar 함수가 선언된 곳은 전역이기 때문에 x는 전역에 선언된 1의 값을 가진다.

 

14장 전역 변수의 문제점

  • 변수는 선언에 의해 생성되고 할당을 통해 값을 갖는다.
  • 변수는 생성되고 소멸되는 생명주기가 있다.
지역 변수의 생명 주기
function foo(){
var x = "local";
console.log(x); // local
return x;
}
foo();
console.log(x)// Error: x is not defined
  • 지역 변수 x 는 foo 함수가 호출되기 이전까지는 생성되지 않는다.
  • 지역 변수는 함수 호출 된 직후에 함수의 코드가 한줄 씩 실행되기 전에 실행된다.

지역변수 생성 주기

  • 지역 변수 생성 주기는 함수의 생명 주기와 일치한다.

전역 변수의 생명 주기

  • 함수와 달리 전역 코드는 명시적인 호출이 없이 실행된다.
  • 전역변수의 생명주기는 전역 객체의 생명주기와 같다.
    • 전역 객체: 코드가 실행되기 이전에 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체이다.(window, self, this, frames, global)

전역 변수 생성 주기

전역 변수의 문제점

암묵적 결합

  • 전역 변수는 코드 어디서든 참조하고 할당할 수 있는 변수이다.
  • 이는 모든 코드가 전역변수를 침조하고 변형할 수 있는 암묵적 결합을 허용하는 것이다.
  • 변수의 유효 범위가 클수록 코드의 가독성이 나빠지고 의도치 않게 상태가 변경될 위험성도 커진다.

긴 생명주기

  • 전역 변수는 생명주기가 길다. 

스코프 체인 상에서 종점에 존재

  • 전역 변수는 스코프 체인 상에서 종점에 존재한다 -> 전역 변수의 검색 속도가 가장 느리다.

네임 스페이스 오염

  • 자바스크립트의 가장 큰 문제점 중 하나는 파일이 분리되어 있다 해도 하나의 전역 스코프를 공유한다는 것이다.
  • 다른 파일 내에서 동일한 전역 변수나 전역 함수가 같은 스코프에 존재할 경우 예상치 못한 결과를 가져올 수 있다.

전역 변수 사용을 억제하는 방법

 

즉시 실행 함수

  • 함수 정의와 동시에 호출 되는 함수이다.
  • 단 한번만 호출된다.
  • 모든 코드를 즉시 실행 함수에 감싸면 모든 변수는 즉시 실행 함수의 지역변수가 된다.

즉시 실행 함수

네임 스페이스 객체

  • 전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수 처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법이다.

네임 스페이스 객체
네임스페이스 객체 계층구조

  • 네임스페이스 객체에 또 다른 네임스페이스 객체를 프로퍼티로 추가하여 계층적으로 구성할 수 있다.
  • 네임스페이스를 분리하여 식별자 충돌을 방지하는 효과는 있으나 네임스페이스 객체가 전역 변수에 할당 되므로 그다지 유용하지 않다.

모듈 패턴

  • 자바스크립트에서 중요한 패턴 중 하나이며 가장 일반적으로 사용되는 디자인 패턴이다.
  • 프로젝트의 코드를 명확하게 나누고 구분하는데 좋다.
  • 관련 있는 변수 그리고 함수를 즉시 실행함수()로 감싸 하나의 모듈로 만들고 클로져 기반으로 작동한다.
모듈패턴 사용 x
let counter = {
  value: 0,
  increase: function() {
    this.value++ // 메서드 호출을 할 경우, this는 counter을 가리킵니다
  },
  decrease: function() {
    this.value--
  },
  getValue: function() {
    return this.value
  }
}

모듈패턴
var Counter = (function () { //즉시실행함수로 감싼다
  // private 변수
  var num = 0;

  // 클로저
  //외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환한다.
    return {
    increase() {
      return ++num;
    },
    decrease() {
      return --num;
    }
  };
}());
  • Counter는 메서드를 호출하여 값을 담고 있는 프로퍼티 객체를 반환한다.
  • 즉시 실행 함수()는 한번만 실행되므로 Count를 호출할 때마다 num을 초기화 하지 않는다.
  • 즉시 실행 함수()가 반환한 클로저는 Counter 변수에 할당되어 호출된다.
  • num은 클로저에 의해 외부에 접근할 수 없고 메서드들을 통해서 접근이 가능하다.

모듈 패턴을 쓰는 이유

  • 캡슐화, 정보 은닉이 가능하다.
  • 캡슐화 : 객체의 상태를 데이터(속성)와 기능(메서드)을 하나의 단위로 묶는 것
  • 언제든 구현을 수정할 수 있다.

전역변수 억제

  • 전역 변수는 side effect를 발생시키기 떄문에 side effect를 최소화 하면 좋다.

재사용성

  • 모듈로 묶여있기 때문에 재사용성에 좋다.