
Intro
나는 자바로 프로그래밍에 입문했다.
자바는 대표적인 객체지향 프로그래밍 언어이기 때문에, 항상 붕어빵과 붕어빵 틀에 비유되는 클래스와 객체부터 캡슐화, 상속, 추상화, 다형성(캡상추다)와 같은 핵심 원리를 공부해가며 자연스럽게 객체지향적 사고방식을 배워나갔다.
그러다 화면(프론트엔드)을 개발하는 것에 재미를 느끼고 자바스크립트를 접했는데, 가장 당혹스러웠던 사실 중 하나는 자바스크립트가 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 모두 지원하는 멀티 패러다임(Multi-Paradigm) 언어라는 점이었다.
?????????????
객체 지향은 알고 있지만, 프로토타입은 또 무엇이며, 명령형과 함수형 프로그래밍은 또 무슨 말일까?
이번 글에서는 자바스크립트가 추구하는 여러 패러다임부터 이해해보고, 다음 글부터 자바스크립트가 이러한 패러다임들을 어떻게 추구하는지 알아보자.
1. 자바스크립트의 프로그래밍 패러다임
프로그래밍 패러다임
프로그래밍 언어들은 저마다의 컨셉이 있다. 자바가 클래스와 객체로 프로그램을 구성하는 것처럼, 각 언어들은 자신만의 문제 해결 방식을 제시한다. 그리고 이러한 패러다임은 프로그래밍 언어를 사용하는 개발자가 가져야할 관점을 제시하는 역할도 한다.
멀티 패러다임 프로그래밍
반면 자바스크립트는 특정 패러다임에 국한되지 않고, 다양한 패러다임을 아우르는 멀티 패러다임 프로그래밍 언어다. 앞서말했듯, 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원한다.
멀티 패러다임 프로그래밍 언어의 설계 목표는 모든 문제를 가장 쉽고 효율적으로 풀 수 있는 하나의 패러다임은 없다는 것을 인정하고, 프로그래머가 자신의 일에 가장 적합한 것을 사용할 수 있게 하는 것이다.
이는 개발자가 상황에 따라 가장 적합한 패러다임을 선택할 수 있다는 장점이 있지만, 동시에 언어를 심도 있게 다루려면 여러 패러다임의 이해도가 필요하다는 무서운 말이기도 하다.
2. 명령형 프로그래밍(Imperative Programming)

명령형 프로그래밍은 어떻게(How) 문제를 해결할 것인가를 단계별로 명령하는 프로그래밍 방식이다. 대표적인 특징은 다음과 같다.
- 코드가 위에서 아래로 순차적으로 실행된다.
- 변수의 값을 직접 변경하면서 프로그램이 실행된다. (상태 - 변수가 단계별로 변하는 것을 추적하기 용이)
- 조건문과 반복문 등을 사용해 흐름을 제어한다.
명령형 프로그래밍 방식으로 라면을 끓여보자.
- 물을 500ml 넣고 끓인다. (짜게 먹는편)
- 물이 끓으면 면과 스프를 넣는다.
- 3분간 더 끓이고, 계란을 넣는다.
- 그릇에 옮긴다.
이처럼 순서대로 ‘어떻게 끓일지’를 구체적으로 지시한다면, 명령형 프로그래밍 방식에 가깝다고 볼 수 있다.
명령형 프로그래밍의 코드 예시
// 1부터 5까지의 숫자 배열의 합 구하기 const numbers = [1, 2, 3, 4, 5]; let sum = 0; for (let i = 0; i < numbers.length; i++) { sum += numbers[i]; } console.log(sum); // 15
위 코드는 sum 변수를 직접 갱신하여 최종 결과를 만드는 전형적인 명령형 프로그래밍 방식이다.
3. 함수형 프로그래밍(Functional Programming)
선언형 프로그래밍(Declarative programming)
함수형 프로그래밍은 선언형 프로그래밍에서 파생된 패러다임이므로, 선언형 프로그래밍에 대해 먼저 알아보자. 선언형 프로그래밍은 명령형 프로그래밍과 반대되는 개념으로, 무엇을(What) 원하는가에 대해 선언하는 프로그래밍 방식이다.
즉, 결과물을 명시하고, 내부 동작(어떻게 그것이 이루어지는가)에 대해서는 크게 신경 쓰지 않는다. 대표적인 특징은 다음과 같다.
- 코드가 간결하고 직관적이다.
- 의도가 명확하다.
- 유지보수가 쉽다.
이번에는 선언형 프로그래밍 방식으로 라면을 끓여보자.
- "라면 하나 주세요" (끝)
명령형처럼 "500ml 넣고, 3분 끓이고..." 구구절절 절차를 명시하지 않고, "라면을 원한다" 라는 목표만 선언한다. 물론 실제로는 라면 가게의 주방에서 내부 과정을 수행하지만, 우리는 그 세부 과정을 신경 쓸 필요가 없다.
선언형 프로그래밍의 코드 예시
const numbers = [1, 2, 3, 4, 5]; // sum()이라는 함수를 이미 만들어 두었다고 가정 // 내부적으로 더하는 로직은 구현돼 있고, 우리는 '합을 구해 달라'고만 선언 const sum = numbers.sum(); console.log(sum); // 15
여기서는 for문을 통해 직접 상태를 바꾸지 않고, sum()이라는 함수를 사용해 ‘배열 요소들을 모두 더한 값’을 반환하도록 선언한다.
이처럼 어떻게 합을 구할지는 내부 로직에 맡기고, "합을 구해줘"라는 요구사항만 표현한다.
함수형 프로그래밍(Functional Programming)
함수형 프로그래밍의 주요 목표는 상태 변화(Mutation)을 최소화하고, 순수 함수(Pure Function)를 활용해 코드의 예측 가능성과 재사용성을 높이는 것이다. 대표적인 특징은 다음과 같다.
- 순수 함수: 동일 입력에 대해 항상 동일한 출력을 반환
- 불변성: 부수효과(Side Effect) 를 최소화하여, 어디서 어떻게 상태가 바뀌는지 추적할 필요를 줄임
- 고차 함수: 함수를 값처럼 다루어 매개변수로 전달하거나 반환값으로 사용
- 선언형 프로그래밍: “어떻게 동작하는지”보다 “무엇을 해야 하는지”에 초점을 맞추며, 내부 구현은 함수들이 알아서 처리하도록 선언
함수형 프로그래밍의 특징을 구글링하면 항상 이와같은 특징을 찾을 수 있다. 그러나 다소 표현이 직관적이지 않아 이해하기가 힘든데, 나의 경우 이 글을 읽고 함수형 프로그래밍의 개념을 이해할 수 있었다.
함수형 프로그래밍 코드 예시
다시 함수형 프로그래밍 방식으로 라면을 끓여보자. 이제는 주방장의 입장에서 손님이 "라면 하나 주세요"를 했을 때, 라면을 끓여줄 수 있는 함수를 만들면 된다.
예를 들어 "치즈라면 주세요", "땡초라면 주세요"가 들어와도 같은 로직을 재활용할 수 있어야 한다.
// '짜게 먹는 편' 옵션을 넣으면, 항상 동일한 결과를 내는 순수 함수 const makeRamen = ({ water, spicyLevel, boilTime, cheese = false }) => { // 외부 상태를 건드리지 않고, 오직 결과(라면 정보)만 반환 return { water, spicyLevel, boilTime, cheese, egg: true, // ... 기타 재료 }; }; // 함수를 선언적으로 호출 const ramen = makeRamen({ water: 500, spicyLevel: 'high', boilTime: 3 }); const cheeseRamen = makeRamen({ water: 500, spicyLevel: 'high', boilTime: 3, cheese: true, }); console.log('기본 라면:', ramen); console.log('치즈 라면:', cheeseRamen);
4. 객체 지향 프로그래밍(Object-Oriented Programming)

객체 지향 프로그래밍은 명령형 프로그래밍에서 파생된 패러다임이다. 데이터와 그 데이터를 처리하는 동작을 하나의 객체로 묶어 관리한다.
객체 지향을 이해하려면 구글에 '객체 지향 붕어빵'만 검색하면 된다. 실제로는 붕어빵 틀과 붕어빵의 비유는 잘못되었다. 객체 지향 프로그래밍의 대표적인 특징은 다음과 같다.
- 캡슐화(Encapsulation): 데이터와 메서드를 객체 내부에 감춰서, 외부에서 직접 접근하지 못하도록 보호
- 상속(Inheritance): 부모 클래스로부터 속성, 메서드를 물려받아 자식 클래스에서 재사용
- 추상화(Abstraction): 공통된 특성을 추출하여 일반화
- 다형성(Polymorphism): 같은 이름의 메서드가 클래스(또는 객체)에 따라 다르게 동작 가능
객체 지향 프로그래밍 코드 예시
이제는 라면을 끓여줄 식당을 객체 지향적으로 설계해보자. 예컨대, Restaurant라는 부모 클래스를 만들고, 이를 상속받는 KimbapHeaven(김밥천국)이 있다고 가정할 수 있다. 혹은 좀 더 단순하게, 라면만 전문적으로 다루는 틈새라면 클래스를 정의해볼 수도 있다.
class 틈새라면 { constructor(waterAmount, spicyLevel) { this.waterAmount = waterAmount; this.spicyLevel = spicyLevel; } boilWater() { console.log(`${this.waterAmount}ml 물을 끓이는 중...`); } addIngredients() { console.log(`매운 스프(${this.spicyLevel}), 면을 넣었습니다.`); } cook() { this.boilWater(); this.addIngredients(); console.log('3분간 끓이는 중...'); } serve() { console.log('틈새라면을 그릇에 담았습니다.'); return { water: this.waterAmount, spice: this.spicyLevel, egg: true, }; } getRamen() { this.cook(); return this.serve(); } } // 객체 생성 후 메서드 호출 const my틈새라면 = new 틈새라면(500, '빨계떡'); const result = my틈새라면.getRamen(); console.log(result);
위와 같이, 객체(Instance) 를 생성하고, 메서드 boilWater() cook() serve() 등 를 호출해 라면을 만드는 전체 과정을 캡슐화한다. 외부는 getRamen()만 호출하면 되므로 내부 구현에 신경 쓸 필요가 없다.
마무리
이번 글에서는 자바스크립트의 멀티 패러다임 지원 방식을 알아보기 전, 각 패러다임이 어떤식으로 프로그래밍 되는지 알아보았다. 정리하자면 다음과 같다.
- 명령형 프로그래밍: "어떻게 동작해야 하는지"를 작성하여, 프로그램이 수행할 명령들을 순서대로 써놓은 것이다.
- 선언형 프로그래밍: "원하는 것을 어떻게 얻을지"가 아니라, "무엇을 원하는지"를 작성하여 문제의 본질에 집중한다.
- 함수형 프로그래밍: 선언형 프로그래밍을 확장하여, 상태 변화 없이 순수 함수를 통해 로직을 구성한다.
- 객체 지향 프로그래밍: 명령형 프로그래밍 안에서, 데이터를 객체 단위로 모으고(캡슐화), 상속과 다형성으로 재사용과 확장을 돕는다.
자바스크립트는 명령형, 함수형, 객체 지향형 어느 한쪽에 속하지 않고, 다양한 패러다임을 프로토타입(Prototype) 기반의 객체 모델 위에서 동시에 지원하는 멀티 패러다임 언어다.