5. Javascript 메서드, 생성자 함수
목차 |
1. 피보나치 수열 1.1. 풀이 1.2. 메모이제이션(Memoization) |
2. 메서드(Method) 2.1. this |
3. 생성자 함수 3.1.생성자 함수 생성하기 3.2.생성자 함수 생성 규칙 3.3.인스턴스 |
4. getter와 setter |
1. 피보나치 수열
피보나치라는 수열을 중, 고등학교 때 수학을 공부 했다면, 대표적인 수열문제로 한 번씩 접해봤을 것이다.
피보나치 수열이란, 앞의 두 숫자를 더 한 값을 계속 반복해 나가는 수열이다.
1 1 2 3 5 8 13 21 34 55 89 144 ....
위 처럼 진행되는 피보나치 수열을 function을 이용해 구현해보자.
1.1 풀이
코드로 구현하기에 앞서 피보나치에 대해 생각을 해보자면,
F(x) = F(x-1) + F(x - 2)로 F(x)를 정의할 수 있다.
이를 이용해서 코드로 진행시켜보자
function fibo(x) {
if (x <= 2) {
return 1
}
return fibo(x-1) + fibonacci(x-2) // = fibonacci(x)
}
console.log( fibo(10)) // 55
10을 대입해서 결과 값을 도출해보니 10번째에 있는 55가 산출된다.
여기에 100을 넣어보자. 브라우저에 콘솔 값이 출력되지 않는다. 왜 이런일이 발생하는 걸까?
피보나치의 식을 파헤쳐보면, F(x)를 구하기 위해 F(x-1)과 F(x-2)를 구해 더해야한다. 낮은 숫자를 넣어 식을 펼쳐보자면,
F(5) = F(4) + F(3) = {F(3) + F(2)} + {F(2) + F(1)} = [{F(2) + F(1)} + {F(1) +F(1)}] + ....
계산해야 하는 횟수가 2^x번 생기게 되어 100을 넣게되면 2^100번이 되게된다. 했던 계산을 반복하기 떄문에 컴퓨터가 다 처리할 수 없다.
1 + 1은 2다
이걸 식으로 써서 계산하는 사람은 없다. 1 + 1 이 2라는 건 간단하고 이미 해온 계산이니까. 하지만, 컴퓨터는 시키면 시키는 대로 하기 때문에, 이를 사용자가 조절해 줄 필요가 있다.
1.2 메모이제이션(memoization)
메오이제이션을 검색하면 다음과 같이 정의되어있다.
메모이제이션은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
(출처 : 위키백과, 2022년 11월 04일https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98)
이미 계산한 걸 저장해두고 가져옴으로써 연산의 횟수를 줄이는 방법이다. 2^n의 연산횟수를 n번으로 줄여 실행 속도가 빨라지는 것이다. 수치적으로만 봐도 효율성이 올라가는 것을 볼 수 있다.
코드를 한 번 보면서 이해해보자.
let memoi = {} // 1. 객체를 생성한다.
function fibo(n) {
let result // 3. let으로 result 선언
if(n in memoi) { // memoi 객체에 매개변수(n)이 있다면 값을 반환(boolean)
result = memoi[n] // memoi[n] 객체를 변수 result에 대입
} else {
if (n <= 2) {// 피보나치 start
result = 1
} else {
result = fibo(n - 1) + fibo(n - 2)
}// 피보나치 end
memoi[n] = result
}
return result
} // 2. fibo함수 선언
console.log(fibo(100)) // 354224848179262000000
- 객체를 생성한다.
- 함수 안에 변수를 선언하고 그 변수를 리턴 뒤에 배치한다.
- if문으로 memoi안에 n이라는 속성을 찾고난 뒤 속성이 존재한다면 result값으로 속성을 대입하고 넘어간다
- n이라는 속성이 없다면 1번째, 2번째 result값으로 콜스택을 쌓는다.
- 그 결과 값을 memoi의 n 속성으로 result값을 넣는다.
- 이 과정을 반복하면, 연산 횟수가 O(2^n)에서 O(n)으로 줄어든다.
※. return 대신에 result를 쓰는 이유는 return 값으로 가버리면 memo에 객체로 담기가 어려워서 잠시 result를 선언하고 result라는변수에 담는다.
※. 배열을 쓰지 않는 이유는 배열은 순서대로 배치가 되는데, 어떤 숫ㄱ자가 완성될지 몰라 번거로우므로 객체를 사용하는게 훨씬유리하다.
코드가 작동하는 원리를 말로 풀어보면
console.log(fibo(100))을 찍었을 때, 정의된 함수 fibo에 100을 대입한다.
함수 fibo 안에서는 result라는 변수를 선언하고,
if 문으로 넘어가 100이 memoi 객체에 있어 true가 나오면, result에 memoi[100]이 들어가고,
false라면, if 내부의 if문으로 들어가 피보나치 수열을 진행한다.
n이 2보다 작거나 같아 true를 반환하면 result는 1이되고,
false라면 result에 fibo(99)와 fibo(98)를 대입하고 if문을 나와 result값을 memoi[n]에 대입한다.
대입한 memoi[n] 값을 return 하여 값을 추출한다.
이렇게 되면 길어보지만 2번의 if문으로 연산횟수를 2^n에서 n으로 줄일 수 있다.
2. 메서드(Method)
객체 안에서 정의 되어있는 함수를 메서드라 하지만, 일반적으로 함수를 메서드라 불러도 다 말은 통한다. 그래도, 메서드의 정확한 의미는 알고 있도록 하자.
코드를 통해 메서드의 생김새를 알아보자.
const Product = {
name: "Mac"
sum: function(a, b) {
return a + b
}
}
console.log(Product.sum(1, 2)) // 3
객체 Product 안의 두 번째 속성의 값 부분에 정의 된 함수 형태를 메서드라고 한다.
(console.log()도 console이라는 객체안의 메서드 log함수를 호출하는 것이다.)
즉, 일반적인 함수와 다르게 키가 함수명, 값이 함수의 기능을 한다.
(JavaScript를 잘하기 위해서는 메서드를 많이 알아두는 것이 좋다.)
2.1 this
this객체 안에서 쓰이는 특별한 역할을 한다. 코드를 보면서 이해해보자.
const Product = {
name: "Mac",
sayBuy: function(){
console.log(this.name + " 계산해주세요.")
}
}
Product.sayBuy()// "Mac 계산해주세요."
정의된 메서드 안의 console부분을 살펴보자. this.name을 보면 this가 마치 Product와 같은 역할을 하고있다.
this를 사용하게되면 this가 속해있는 값으로 정의된 함수의 객체를 가르킨다. 그래서 여기선 this가 Product를 가르킨다.
this는 객체를 대신한다. 하지만, this의 값은 런타임에 결정된다.
let user = {
name: "Mac",
}
let admin = {
name: "Book",
}
function abcd(){
console.log(this.name)
}
user.a = abcd;
admin.b = abcd;
user.a() // "Mac"
admin.b() // "Book"
결과값을 보면, abcd()에 this를 사용해 코드를 작성하고, 각각의 개체에 넣었을 때, 출력값이 다른걸 확인할 수 있다.
user.에 들어간 this는 user를 가르키고, admin에 들어간 this는 admin을 가르킨다.
3. 생성자 함수(Constructor Function)
지난번에는 객체를 만들어 내기 위해서, 객체 리터럴을 사용했다. 이번에는 생산자 함수(Constructor Function)을 사용해서, 객체를 만들어보자.
생성자 함수는 객체들을 보다 쉽게 만들어 내기 위해서 만들어졌다. 객체는 실제로 존재하는 것의 정보를 담아낸다. 실제로 존재하는 물건들을 담을 때 같은 부류의 것들도 만들어야 한다. 예를 들어, 노트북들의 정보를 담다보면, 애플, 삼성, Hp 등의 회사정보를 나타내기도 해야하고, 가격이랑 정보를 담을 때에도 형태는 같고 숫자만 달라지기 때문에 이를 객체 리터럴롤 만들어낸다면, 1개 2개 정도는 괜찮겠지만, 무수히 많은 코드들을 작성하기엔 많은 시간이 걸릴것이다.
생성자 함수는 유사한 형태의 객체를 쉽게 만들기 위해 만들어졌다.
(생성자 함수는 일종의 설계도라고 이해하면 편하다.)
3.1 생성자 함수 생성하기
function User(name, age) {
this.name = name
this.age = age
}
function와 this를 이용해 User라는 함수를 만들었다, parameter 값으로 name과 age를 설정하여, argument에 (charley, 5)라고 적으면 정의가 되지 않는다. 함수가 생성은 되지만 데이터를 담을 수 있는 공간이 정의 되지 않았기 떄문이다.
이럴때에는 데이터를 담아줄 변수를 정의하고, argument에 값을 넣어 각각의 객체를 생성해 주어야한다.
function User(name, age) {
this.name = name
this.age = age
}
const A = new User("A", 20)
console.log(A) // User {name: 'A', age: 20}
const B = new User("B", 30)
console.log(B) // User {name: 'B', age: 30}
const를 이용해 A, B라는 변수를 생성하고 그 안에 인자값을 이용해 객체 데이터 형태로 담았다.
생성자 함수를 만들어 사용할 때는 new 사용해야한다. new를 사용하면 다음과 같은 과정이 암묵적으로 실행된다.
- 빈 객체를 만들고, this를 이에 할당한다.
- this로 정의된 이 객체에 코드의 내용을 속성으로 추가한다.
- this를 반환한다.
new User()를 사용해, 위의 과정을 거치나 변수를 정의하여 담지 않으면 어딘가에 저장되지 않기 때문에 반드시 변수와 함꼐 써야한다.
3.2 생성자 함수 생성 규칙
생성자 함수의 문법은 다음과 같이 쓸 수 있다.
- function 사용
- class 사용
주의할 점은 함수명을 사용할 때는 대문자로 시작하고, 함수명 앞에는 new를 사용해 변수에 담아주어야 한다.
// function 사용시
function User(name, age) {
this.name = name
this.age = age
}
// class 사용시
class User{
constructor(name, age){
this.name = name
this.age = age
}
}
class를 사용하는 이유는 javascript가 객체지향문법을 지향하고 있기 때문이다. 객체지향언어에서는 주로 class를 중요하게 사용한다. 이는 javascript가 추구하는 방향성으로 삼고있다.
생성자 함수의 사용방법을 순서대로 나열해보자면,
- function / class.constructor 를 이용해 함수를 생성(대문자)
- const로 변수를 정의
- new를 사용해 변수에 생성자 함수를 담아준다
3.3 인스턴스
인스턴스란 new를 사용해 생성자 함수로 만들어진 객체를 의미한다.
4. getter와 setter
객체내에는 속성이 존재한다. 우리는 속성의 키와 값에 데이터를 할당하여 사용해왔고, 이를 데이터 속성이라고 한다.
객체의 속성에는 데이터 속성말고 접근자 속성이라는 속성이 존재한다.
접근자 속성에는 getter와 setter가 있다.
접근자 속성은 기본적으로 함수이며, get 함수와 set함수를 이용해서 데이터를 직접 건드리지않고, 조작하기 위함이다.
get과 set은 가상의 속성을 만들어주며, get만 쓸 경우에는 읽기는 가능하나 그 안에 값을 할당할 수 없고,
set은 할당할 수 있으나 get과 쓰지 않으면 할당할 수 없다.
get과 set을 활용해보자면 아래와 같이 쓸 수 있다.
const user = {
name: "", //
lastName: "", // 성 // "고"
firstName: "", // 이름 //"길동"
get _name() {
return this.firstName + " " + this.lastName //_name: "길동 고" 할당
},
set _name(value) {
const arr = value.split("") // arr = ["고", "길", "동"]
this.name = value // name: "고길동" 할당
this.lastName = arr[0] // lastname: "고" 할당
this.firstName = arr[1] + arr[2] // firstname: "길동" 할당
},
}
user._name = "고길동" // value = "고길동"
console.log(user) // {name: '고길동', lastName: '고', firstName: '길동'}
console.log(user.name) // 고길동
console.log(user._name) // 길동 고
get으로 가상의 프로퍼티인 _name을 설정하고 this를 이용해 [이름 + " " + 성]을 읽히고,
set을 이용해 할당할 값들은 지정해준다.
그리고 아래에서 user._name에 "고길동" 넣어주면, set으로 정의된 _name 함수의 인자값으로 들어간다.
set 안에서는 arr를 정의하여 split을 이용해 배열로 데이터를 저장한다.
그리고 user 객체의 name에 value 값인 "고길동"을 대입하고, lastName과 firstName에 arr을 이용하여 "고"와 "길동"을 할당해준다.
결론적으로 봤을 때, set과 get을 이용해 설계를 해두면 한줄의 코드로 다양한 속성을 추가할 수 있다.
또한, _name에 할당 된 "길동 고"는 가상의 속성에 존재하므로 아래의 그림 처럼 표현된다.
요약해보면, getter와 setter는 함께 써야하고, 가상의 속성을 생성하여 값을 넣어준다.
외부코드로 객체 내부의 정보를 건드리지 않고 조작이 가능할 수 있게 해주는 역할을 한다고 보면된다.