JavaScript의 프로토타입 기반 객체 지향
JavaScript는 프로토타입 기반의 객체 지향 언어입니다. 이는 클래스 기반 객체 지향 언어와는 다르게, 객체들이 클래스의 인스턴스로 생성되는 대신 객체를 직접 생성하고 다른 객체를 상속받을 수 있습니다. JavaScript에서 모든 객체는 다른 객체로부터 프로퍼티와 메서드를 상속받을 수 있는 프로토타입 객체를 가지고 있습니다.
생성자 함수의 사용
생성자 함수는 객체를 생성하기 위해 사용되는 특별한 함수입니다. new 키워드와 함께 사용되며, 생성자 함수 내부에서 this 키워드를 사용하여 객체의 초기 속성을 설정합니다.
샘플 코드 및 설명
// 생성자 함수 예시
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
};
}
// 프로토타입을 이용해 모든 Person 객체에 메서드 추가
Person.prototype.sayGoodbye = function() {
console.log("Goodbye from " + this.name + "!");
};
// 객체 생성
const john = new Person("John", 30);
const jane = new Person("Jane", 25);
// 메서드 호출
john.greet();
// 결과: "Hello, my name is John and I am 30 years old."
jane.greet();
// 결과: "Hello, my name is Jane and I am 25 years old."
john.sayGoodbye();
// 결과: "Goodbye from John!"
jane.sayGoodbye();
// 결과: "Goodbye from Jane!"
설명
- 생성자 함수 (Person): Person 생성자 함수는 name과 age 속성을 초기화하며, 각 인스턴스에 greet 메서드를 직접 추가합니다. 이 메서드는 해당 객체의 이름과 나이를 출력합니다.
- 프로토타입 메서드 (sayGoodbye): Person.prototype.sayGoodbye 메서드는 모든 Person 인스턴스가 상속받는 메서드로, 객체의 이름과 함께 작별 인사를 출력합니다. 프로토타입에 메서드를 추가하면 모든 인스턴스에서 사용할 수 있어 메모리를 효율적으로 사용할 수 있습니다.
프로토타입 객체의 확장과 상속
- 프로토타입 체인을 통한 속성 메서드 접근: 인스턴스가 직접 속성을 가지고 있지 않을 경우, 프로토타입 체인을 따라 상위 프로토타입에서 해당 속성이나 메서드를 찾습니다.
- 프로퍼티 쉐도잉 (Property Shadowing): 인스턴스 또는 하위 프로토타입에서 같은 이름의 프로퍼티를 가지고 있으면 상위 프로토타입의 프로퍼티를 가리게 됩니다.
- 메소드 오버라이딩 (Method Overriding): 상속받은 메서드를 하위 클래스 또는 객체에서 재정의하는 것입니다.
샘플 코드 및 설명
// 생성자 함수 정의
function Person() {
this.name = "John"; // 인스턴스에 직접 속성 추가
}
// 프로토타입에 age 속성 추가
Person.prototype.age = 28;
// 프로토타입에 toString 메서드 추가
Person.prototype.toString = function() {
return `이름: ${this.name}, 나이: ${this.age}`;
};
// 인스턴스 생성
const joy = new Person();
joy.name = "Joy"; // 프로퍼티 쉐도잉: 기존의 'John'을 'Joy'로 변경
// 메소드 오버라이딩: toString 메서드 재정의
joy.toString = function() {
return `사용자 정보: ${this.name}, ${this.age}세`;
};
// 결과 출력
console.log(joy.toString()); // "사용자 정보: Joy, 28세"
// toString 메서드가 오버라이딩 되어 인스턴스에 추가된 새로운 정의가 사용됩니다.
console.log(joy.age); // 28
// age는 직접 인스턴스에 정의되지 않았으므로 프로토타입 체인을 통해 가져옵니다.
설명
- 이 코드는 Person 생성자 함수와 그 프로토타입을 사용해 Person 인스턴스를 만듭니다.
- joy 객체에서 name 속성은 기존에 프로토타입 체인으로부터 가져오던 것을 인스턴스 레벨에서 새로 정의하여 쉐도잉 합니다.
- toString() 메소드는 오버라이딩하여 새로운 동작을 정의합니다.
- age 속성은 인스턴스에 직접 정의되지 않았기 때문에 프로토타입 체인을 통해 접근됩니다.
*Class문법
ES6+ 버전에서 도입된 JavaScript의 class 구문에 대한 설명입니다. class는 보다 명확하고 간결한 구문을 제공하여, 객체의 생성과 상속을 용이하게 합니다.
- Constructor: 객체의 초기 상태 설정.
- Methods: 인스턴스와 정적 메서드.
- Accessor Properties (getters and setters): 프로퍼티 접근과 설정을 위한 메서드.
- Static Methods: 인스턴스가 아닌 클래스 자체에 바인딩된 메서드.
샘플 코드 및 설명
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Getter 메서드: 이름을 대문자로 반환
get uppercaseName() {
return this.name.toUpperCase();
}
// Setter 메서드: 나이 설정
set plusYear(age) {
this.age += age;
}
// 정적 메서드: 사람의 다리 개수 반환 (예시로 2를 반환)
static legCount() {
return 2;
}
}
// 정적 메서드 사용
console.log(Person.legCount()); // 2
// 결과: 2
// 인스턴스 생성
const joy = new Person("joy", 28);
// Setter를 통한 나이 증가
joy.plusYear = 3;
// Getter를 통한 이름 접근 및 인스턴스의 나이 출력
console.log(joy.uppercaseName, joy.age); // 'JOY' 31
// 결과: 'JOY' 31
this 바인딩 - 화살표 함수
- 화살표 함수의 특징: 화살표 함수는 자신만의 this를 가지지 않고, 선언될 때의 상위 스코프의 this를 사용합니다.
- 화살표 함수 사용 시 주의점: 화살표 함수는 생성자 함수로 사용될 수 없습니다. 즉, new 키워드와 함께 사용할 수 없습니다.
샘플 코드 및 설명
const person = {
name: 'Alice',
age: 25,
greet: function() {
console.log(`Hello, my name is ${this.name}`);
},
farewell: () => {
console.log(`Goodbye from ${this.name}`);
}
};
person.greet(); // "Hello, my name is Alice"
// 결과: "Hello, my name is Alice" - 함수에서 `this`는 person 객체를 가리킵니다.
person.farewell(); // "Goodbye from "
// 결과: "Goodbye from " - 화살표 함수에서 `this.name`은 undefined입니다. `this`는 상위 스코프인 전역 컨텍스트(window)의 `this`를 가리키며, 그곳에는 'name' 프로퍼티가 없습니다.
// 화살표 함수를 생성자로 사용하는 시도
const Animal = () => {
this.type = 'animal';
};
try {
const dog = new Animal();
} catch (error) {
console.error(error); // TypeError: Animal is not a constructor
}
// 결과: TypeError: Animal is not a constructor
*Scope 설명
JavaScript에서 "스코프(Scope)"는 변수 및 함수가 접근 가능한 범위를 의미합니다. 스코프는 코드의 다양한 부분에서 어떤 변수들이 사용 가능한지 결정합니다. JavaScript에는 주로 세 가지 타입의 스코프가 있습니다:
- 전역 스코프(Global Scope): 전역 스코프에 선언된 변수는 어디서든 접근 가능합니다.
- 함수 스코프(Function Scope): 함수 내에서 선언된 변수는 해당 함수 내에서만 접근 가능하며, 함수 외부에서는 접근할 수 없습니다.
- 블록 스코프(Block Scope): let 또는 const로 선언된 변수는 그 변수가 선언된 블록, 문장 또는 표현식 내에서만 접근 가능합니다. 이는 주로 {}로 구분된 코드 블록 내에서 유효합니다.
// 전역 변수
var globalVar = "I am global";
function testFunctionScope() {
// 함수 스코프 내의 변수
var functionScopedVar = "I am inside a function";
console.log(globalVar); // 전역 변수는 함수 내에서 접근 가능
console.log(functionScopedVar); // 함수 내에서 선언된 변수는 접근 가능
}
testFunctionScope();
// 결과: "I am global"
// 결과: "I am inside a function"
console.log(globalVar); // 전역 스코프 변수 접근 가능
// 결과: "I am global"
try {
console.log(functionScopedVar); // 함수 외부에서는 접근 불가
} catch (e) {
console.error("Error:", e.message);
// 결과: Error: functionScopedVar is not defined
}
if (true) {
// 블록 스코프 변수
let blockScopedVar = "I am inside a block";
console.log(blockScopedVar); // 블록 내에서 접근 가능
// 결과: "I am inside a block"
}
try {
console.log(blockScopedVar); // 블록 외부에서는 접근 불가
} catch (e) {
console.error("Error:", e.message);
// 결과: Error: blockScopedVar is not defined
}
JavaScript 클로저(Closure) 설명
클로저는 JavaScript의 강력한 기능 중 하나로, 함수가 선언될 때의 렉시컬 환경(Lexical Environment)에 대한 참조를 계속 유지하는 함수입니다. 클로저를 통해 내부 함수는 외부 함수의 변수에 접근할 수 있으며, 외부 함수의 실행이 끝난 후에도 외부 함수의 변수를 사용할 수 있습니다. 이 특성 덕분에 데이터 캡슐화와 정보 은닉을 구현할 수 있습니다.
클로저의 주요 사용 사례
- 데이터 은닉과 캡슐화: 외부에서 접근할 수 없는 private 변수를 만들어 관리할 수 있습니다.
- 상태 유지: 클로저를 이용하면 함수의 상태를 호출 간에 유지할 수 있습니다.
- 콜백 함수에서의 상태 유지: 비동기 처리나 이벤트 처리에 사용되는 콜백 함수에서 상태를 유지하고자 할 때 유용합니다.
샘플 코드 및 설명
function makeCounter() {
let count = 0; // `count`는 `makeCounter`의 지역 변수입니다.
// 클로저 정의: `increment` 함수는 `count` 변수를 참조합니다.
function increment() {
count += 1;
console.log(count);
}
return increment;
}
const counter1 = makeCounter(); // `counter1`은 `increment` 클로저를 참조합니다.
counter1(); // 1
// 결과: 1
counter1(); // 2
// 결과: 2
const counter2 = makeCounter(); // 새로운 `makeCounter` 호출로 새 클로저 생성
counter2(); // 1
// 결과: 1
counter2(); // 2
// 결과: 2
counter1(); // 3
// 결과: 3
// `counter1`과 `counter2`는 서로 다른 렉시컬 환경을 참조합니다.
// 클로저를 이용한 private 변수와 메서드
function createBankAccount(initialBalance) {
let balance = initialBalance; // private 변수 `balance`
return {
deposit(amount) {
balance += amount;
console.log(`Deposit ${amount}, new balance: ${balance}`);
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`Withdraw ${amount}, new balance: ${balance}`);
} else {
console.log('Insufficient funds');
}
}
};
}
const account = createBankAccount(100);
account.deposit(50); // 150
// 결과: "Deposit 50, new balance: 150"
account.withdraw(20); // 130
// 결과: "Withdraw 20, new balance: 130"
*렉시컬 환경의 동작 원리
렉시컬 환경은 함수가 호출될 때마다 생성됩니다. 함수의 렉시컬 환경은 함수가 정의된 시점의 외부 환경 참조를 포함하므로, 함수가 어디서 호출되었는지와 상관없이 함수의 렉시컬 스코프를 유지할 수 있습니다. 이는 클로저의 동작 기반을 형성합니다.
예제 코드
function outer() {
let count = 0; // outer 함수의 렉시컬 환경에 'count'가 포함됩니다.
function inner() {
// inner 함수의 렉시컬 환경은 outer 함수의 렉시컬 환경을 외부 환경으로 참조합니다.
count += 1;
console.log(count);
}
return inner;
}
const myFunction = outer();
myFunction(); // 출력: 1
myFunction(); // 출력: 2
설명
- outer 함수: outer 함수가 호출될 때마다 새로운 렉시컬 환경이 생성됩니다. count 변수는 이 렉시컬 환경에 저장됩니다.
- inner 함수: inner는 outer의 렉시컬 환경을 외부 환경으로 참조하는 클로저입니다. inner 함수가 호출될 때마다 outer의 count 변수에 접근하여 값을 변경하고 출력합니다. 이는 inner 함수의 렉시컬 환경이 outer의 렉시컬 환경을 참조하기 때문에 가능합니다.
렉시컬 환경은 스코프, 스코프 체인, 클로저, 호이스팅 등 JavaScript의 중요한 다양한 동작을 이해하는 데 핵심적인 개념입니다.
'클라우딩 어플리케이션 엔지니어링 TIL > 4주차' 카테고리의 다른 글
[파트06]데이터처리 > 객체와빌트인객체 (0) | 2024.05.02 |
---|---|
[파트05] 데이터 처리 (0) | 2024.04.30 |
[파트3] 데이터와 형태 (0) | 2024.04.29 |
[파트2] JavaScript 입문 (0) | 2024.04.29 |