"인사이드 자바스크립트(한빛미디어)"의 저자(송형주)는 C, Java을 주로 다루는 개발자 관점에서 자바스크립트를 굉장히 쉬운 언어로 치부하고 제대로 배우지 않았다. 하지만 프로젝트 규모가 늘어나면서 자바스크립트 특성에 대해 제대로 이해하지 않고 넘어갔던 사소한 부분들이 문제를 일으켜 고생했었다고 말한다.
나 또한, 학부시절 자바스크립트를 무시 아닌 무시했던 기억이 있다. 그냥 단순히 웹에서 이벤트를 처리하는 단순한 언어로 치부했다. 오랜 시간 다른 길을 걷다가 다시 개발에 관심을 두면서, 거의 모든 프론트엔드가 자바스크립트에 기반한다는 사실을 깨닫게 되었다. 그리고 그 사이, 자바스크립트에도 많은 발전이 있었다.
이번 벨로그는 자바스크립트에서 의존성 주입을 사용하기 위해 필요한 문법과 자료를 정리하는 글(진행형)이다. 자바스크립트로 의존성 주입을 개발할 때 참조할 만한 자료가 별로 없다. 사실, 파이썬으로도 많지 않았다. 이 글을 통해 블로그, 유튜브, 도서, 강의, ChatGPT에서 살펴본 컨텐츠를 종합하여 정리하고, 자바스크립트의 의존성 주입을 완성해 본다.
의존성 주입 자체에 대해 알고자 한다면, Java의 경우 스프링 강의(인프런, 김영한), Python의 경우 Dependency Injector(공식사이트 클릭)에서 도움을 받을 수 있다.
자바스크립트는 자바와 문법이 일부 유사한 스크립트 언어다. ECMAScript라고도 한다. ECMA는 European Computer Manufacturers Association의 약자로서 컴퓨터 하드웨어, 통신, 프로그램 언어에 대한 표준을 개발하는 비영리단체다.
1997년 버전 "ES1"을 시작으로, 2009년 "ES5"가 출시되었고, 2015년까지 "ES5" 버전이 사용되었다. 만약 어떤 글에서 변수를 var로 선언하고 리터럴 객체를 설명한다면 대부분 "ES5" 버전으로 볼 수 있다. 2023년에는 자바스크립트 "ES11"까지 업그레이드 되었다. (자바스크립트 변천사: geeksforgeeks.org)

자바스크립트는 객체지향 프로그래밍 언어이며, 동시에 함수형 프로그래밍 언어다. 두 언어의 장점을 모두 사용할 수 있다. 코드를 보다가 함축적이고 익숙하지 않은 구조를 발견하였다면 함수형 프로그래밍 코드일 것이다. 함수형 프로그래밍 언어는 수학에 가깝게 함수를 구현하는데, 액션과 계산 그리고 데이터를 구분함으로써 분산 시스템에 여러 문제를 해결할 수 있다고 알려져 있다. 하지만 자바스크립트는 클라이언트에서 구동되므로 모든 부분에 걸쳐 함수형 프로그래밍이 도입되어야 하는 것은 아니라고 본다. (클로져, reduce 등은 활용성이 있다고 봄)
개발하면서 함수형(FP)를 사용할 것인지, 객체지향(OOP)를 사용할 것인지는 ChatGPT의 결론이 정답이라고 본다.
Ultimately, the choice between FP and OOP depends on the specific requirements of a project, team preferences, and the problem domain. Many modern programming languages support a mix of both paradigms, allowing developers to choose the most appropriate approach for a given situation.
인간의 진화로 치면 자바스크립트는 버전 "ES6"부터 사람다워졌다. (자바스크립트의 변천사를 인간의 진화에 빗댄 그림) 클래스와 같은 객체지향적인 구문(Syntax)이 도입되었고, 변수의 컨텍스트를 제어하는 "let, const", 모듈을 "import, export" 하는 기능들이 추가되었기 때문이다. 개발과 동떨어진 삶을 살다가 다시 돌아온 사람으로서 이런 구문이 이제 도입되었다는 것 자체가 놀랍다.
# 버전 ES5 : 객체를 생성하는 함수를 만들고, 그 객체의 프로퍼티를 제어하는 메서드는 객체의 프로토타입에 할당(무식해서 적절한 용어인지 의문)하는 방식으로 처리한다.
function Person(name, age) { // Constructor function this.name = name; this.age = age; } Person.prototype.sayHello = function() { // Prototype method console.log('Hello, my name is ' + this.name); }; /* 인스턴스 생성 */ var person = new Person('John', 30); person.sayHello();
# 버전 ES6 : 물론, ES6라 해도 객체지향으로 언어의 구조를 변경한 것은 아니다. 내부적으로는 프로토타입 형식으로 처리된다.
class Person { constructor(name, age) { this.name = name; this.age = age; } sayHello() { console.log(`Hello, my name is ${this.name}`); } } /* 인스턴스 생성 */ const person = new Person('John', 30); // Creating an instance person.sayHello();
자바스크립트는 "ES6"부터 자주 업그레이드 되고 있다. 버전 "ES5"가 6년을 유지한 것에 비해 "ES6"부터 "ES11"은 8년 동안 5차례나 변경되었다. 그러나 온라인 자료들은 이러한 버전을 정확히 표기하지 않고 중구난방으로 작성되어 있다. 예컨데 "var" 대신 "let"과 "const"를 사용하라는 권고가 있는데도 여전히 var를 사용한 코드가 즐비하다. 글의 작성일이 2015년 이후이므로 "var"가 사실 상 폐기 시점인데도, "var"을 사용하여 입문 개발자들에게 많은 혼란을 주고 있다.
물론 오래된 브라우져에서 사용되거나, 과거 코드까지 호환성 있게 유지 보수해야 하므로 "ES5"도 당연히 알아야 한다. 하지만 글을 작성하는 사람이 버전을 표기한다면 읽는 사람들이 보다 정확히 이해할 수 있을 것이라고 믿는다.
Getter와 Setter는 "ES5" 리터럴 객체에서도 사용되었던 구문이다. "ES6"의 클래스에서도 동일한 방식으로 사용할 수 있다.
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
set fullName(name) {
const parts = name.split(' ');
this._firstName = parts[0];
this._lastName = parts[1];
}
get age() {
return this._age;
}
set age(age) {
if (age < 0 || age > 150) {
throw new Error('Invalid age');
this._age = age;
}
}
}
인터페이스란 클래스에서 구현해야 하는 메서드 등을 미리 선언해 두는 일종의 추상 클래스다. 말이 어렵지, 그냥 클래스 쿠키 커터(Cookie Cutter)라고 봐도 무당하다. 쿠키를 찍어내 듯 클래스를 찍어내기 위해 틀이다. 자바에서는 Interface 구문으로 인터페이스를 선언하고 implements 구문으로 구현한다. 하지만 자바스크립트에는 이러한 인터페이스가 없다. (타입스크립트에는 있다) 대신 다른 방법으로 자바스크립트에서 인터페이스를 구현할 수 있다.
// ES6+
// 추상 클래스(인터페이스)를 사용(extends)하여 클래스를 구현하는 방법
class MyInterface {
method1() { throw new Error('Method not implemented'); }
method2() { throw new Error('Method not implemented'); }
}
class MyClass extends MyInterface {
method1() {
// implementation
}
method2() {
// implementation
}
}
class 추상클래스_Shape {
constructor() {
if (new.target === 추상클래스_Shape) {
throw new Error("이 클래스는 추상클래입니다. 직접 인스턴스로 만들 수 없습니다.");
}
}
}
class Singleton {
static instance = null;
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = [];
Singleton.instance = this;
return this;
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
addData(data) {
this.data.push(data);
}
getData() {
return this.data;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
instance1.addData("Hello");
instance2.addData("World");
console.log(instance1.getData()); // ["Hello", "World"]
console.log(instance2.getData()); // ["Hello", "World"]
https://github.com/inversify/InversifyJS/releases
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>InversifyJS Example</title>
<script src="path/to/inversify.js"></script>
</head>
<body>
<script>
// Define interfaces and classes
const interfaces = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon")
};
class Ninja implements interfaces.Warrior {
fight() {
return "Ninja fighting!";
}
}
class Katana implements interfaces.Weapon {
hit() {
return "Katana hitting!";
}
}
// Inversify container setup
const container = new inversify.Container();
container.bind(interfaces.Warrior).to(Ninja);
container.bind(interfaces.Weapon).to(Katana);
// Resolve dependencies
const ninja = container.get(interfaces.Warrior);
const katana = container.get(interfaces.Weapon);
// Example usage
console.log(ninja.fight());
console.log(katana.hit());
</script>
</body>
</html>