JavaScript

[Javascript] 클래스의 요소와 메소드

Gray Park 2020. 8. 15. 22:41
728x90
반응형

지금까지 배우던 메소드는 결국 컨스트럭터를 가진 클래스의 함수이다.

클래스는, 일종의 템플릿인데 변수에 새로운 클래스를 할당받으면 클래스에 선언해둔 값을 사용할 수 있고 함수를 메소드처럼 적용할 수 있다. 당연히 Array()는 일종의 클래스이고, 그렇기에 배열을 선언하면 각종 메소드를 자유롭게 사용할 수 있다.

오늘은 이전에 작성해둔 메소드를 이용해 나만의 짭배열, Garray() 클래스를 만들어보았다

 

클래스 ( Class )

클래스의 개념부터 시작하기에 앞서, 아래의 예시를 보자. 배열을 생성하는 Array()는 클래스이며, 클래스이기에 메소드를 추가하여 사용할 수 있다.

 

Array.prototype.lalala = function() {
  console.log('lalala');
}

let arr = new Array(1, 2, 3);
console.log(arr);
// [1, 2, 3]
arr.lalala();
// 'lalala'

신기하지 않은가?

우리가 MDN에서 검색하기 바쁘던 그 메소드를 내가 원하는 대로 추가해 사용할 수 있다!

예를 들어서, 어떤 배열이든 3보다 큰 값만 요소에 남기려는 필터를 사용하고 싶다면, 기존에는 filter()함수 안에 3보다 큰 지를 확인하는 ( x > 3 을 true/false로 리턴하는 ) 함수가 반드시 들어가야 한다. 이처럼 함수를 인자로 받는 함수를 고차함수, 함수에 인자로 들어가는 ( 3보다 큰 지를 확인하는 ) 함수는 콜백함수라고 한다. 나중에 설명할거니까 마저하자. 만약에, 내가 배열을 입력받을 때, 항상 숫자로 이루어져있고 3보다 큰 수를 가지고 있는 배열만 입력받아야하는 상황이라면 어떨까. 당연히 고차함수와 콜백함수 다른방법으로 해결해도 되겠지만, 나라면 이 기능만 계속해서 해주는 함수를 만들어서 쓰려고 할 것이다. 세상에 filter()라는 함수가 없다면, 아래와 같은 함수가 탄생할 수 있다.

// 로직만 이해하자. 안돌아갈거다.
// constructor로 선언된 게 뭔지 모르기때문에 임의로 this.array라고 넣었다
Array.prototype.filterLargerThanThree = function (el) {
  let result = [];
  for ( let value of this.array ) {
    if ( typeof(value) === 'number' && value > 3 ) {
      result.push(value);
    }
  }
  return result;
}

/**
 * 기존 filter 함수는 아래와 같다
 * Array.prototype.filter(callback(el)) {
 *   return callback(el);
 * }
 */

나는 filterLargerThanThree 라는 메소드를 통해, 함수를 다른 함수에 파라미터로 넘겨주는 ( 기둥 뒤에 공간있는 ) 복잡한 과정을 하나의 함수가 해주도록 하였다. 이런 방식으로 만들어 준 함수들 중에서 사람들이 많이 쓸 법한, 그리고 쓸모있는 함수들만 모아 Array()라는 클래스를 만들며 메소드로 지정해 준 것이다.

 

인스턴스 ( instance )

그럼 인스턴스는 뭘까? 인스턴스는 우리가 모르면서도 막쓰고 있던 거다. 그러니 이해가 더 쉽다. 아래의 예제를 보자.

let a = new Array(1, 2, 3); // let a = [1, 2, 3]; 과 동일한 코드

위의 예제에서 Array 클래스를 복붙하면서, 인자로 1, 2, 3 을 넣어주었다. 이것을 앞에 new라는 키워드를 통해 신상이라고 알려주면서 a에 담았다. 여기서 a가 인스턴스이다. 너무 이해하기 쉽다. 우리는 배열을 선언하는 과정에서 인스턴스라는 개념을 몰랐지만 이미 충분히 많이 사용했다. 앞으로는 있어보이게, '배열을 선언하는 것은 새로운 인스턴스를 만들어주는 과정'이라고 설명하자.

 

 

상속 ( inheritance )

우리는 벌써 클래스와 인스턴스라는 두 가지 개념을 이해했다. 이제 클래스에서 다루는 기본 3개념 중 마지막이다. 얘도 우리가 이미 충분히. 아주아주아주 충분히 사용했다. 단지 과정을 간단한 단어로 표현하지 못했을 뿐이다. 위의 예제를 가져와보자.

let a = new Array(1, 2, 3); // let a = [1, 2, 3]; 과 동일한 코드

우리는 a라는 변수를 선언하면서 동시에, 클래스 Array()에 배열의 요소로 만들어줬으면 하는 마음으로 넘겨준 "1, 2, 3"이 있다 ( 문자열 아니다. 대괄호[] 쓰기엔 너무 배열을 넣어주는 것 같아 보여서 이렇게 쓴거다 )

 

여기서 1, 2, 3 은 배열이 요소로 들어갈 것이 분명하고, 변수 a는 클래스 Array()의 인스턴스가 될 것임을 우리는 알고 있다. 그렇다면 남은 건 하나다. 신상임을 알려주는 'new' 키워드이다. 이 키워드는 새로운 인스턴스를 생성할 때 클래스 앞에 붙으면서 "이 인스턴스가 클래스로부터 상속받고 있어요"라고 알려준다. 쉽게 말하면, Array()라는 클래스를 'new'라는 키워드로 상속하겠다는 뜻을 밝힌 뒤에 변수 a에 할당하였고, 클래스를 상속받은 a는 더 이상 단순한 변수가 아닌 인스턴스가 되는 것이다.

( 일반 회사원 a씨는 어느날 거대한 유산 ( class ) 을 상속받았다. 더 이상 일반 회사원 a씨라고 불릴 수가 없게 됐다! )

 

아래는 지난 과정속에서 작성해둔 메소드를 넣어 새로 작성한 나만의 클래스, Garray이다.

/**
 * Author: Gyuha Park
 * Nickname: Gray
 * Start Date: 2020-08-06 20:30
 */

/**
 * 이전까지 메소드를 설명하면서 각각의 기능을 구현해보았습니다
 * https://dev-gp.tistory.com/category/JavaScript
 *
 * 초보 개발자의 객기일지도 모르겠지만,
 * mdn에서 수없이 찾아본 Array() 메소드를 정복하기 위해서
 * Garray() 클래스를 생성해 보았습니다.
 *
 */

class Garray {
	constructor(...elements) {
		let obj = {};
		let arr = [];
		let length = 0;
		if (elements.length === 1 && typeof elements[0] === 'number') {
			for (let i = 0; i < elements[0]; i++) {
				arr[i] = null;
			}
		} else {
			for (let i = 0; i < elements.length; i++) {
				obj[`${i}`] = elements[i];
				length++;
			}
			for (let j = 0; j < length; j++) {
				arr[j] = obj[j];
			}
		}
		this.value = arr;
		this.length = length;
	}

	getArr() {
		return this.value;
	}

	printInfo(something) {
		if (something !== undefined) {
			console.log(
				`Your input is ${something}, this value is [${this.value}], this length is ${this.length}`
			);
		} else {
			console.log(
				`The value is [${this.value}], and the length is ${this.length}`
			);
		}
	}

	myPush(element) {
		let length = this.length;
		this.value[this.value.length] = element;
		this.length++;
	}

	myPop() {
		let length = this.length;
		this.value.length = this.value.length - 1;
		this.length--;
	}

	myShift() {
		let length = this.length;
		let tmpArr = [];
		for (let i = 1; i < length; i++) {
			tmpArr[i - 1] = this.value[i];
		}
		for (let i = 0; i < length - 1; i++) {
			this.value[i] = tmpArr[i];
		}
		this.value.length--;
		this.length--;
	}

	myUnshift(element) {
		let length = this.length;
		let tmpArr = [];
		tmpArr[0] = element;
		for (let i = 0; i <= length; i++) {
			tmpArr[i + 1] = this.value[i];
		}
		this.value.length++;
		this.length++;
		for (let i = 0; i < this.length; i++) {
			this.value[i] = tmpArr[i];
		}
	}

	myJoin(str) {
		let insert = '';
		let result = '';

		if (str === undefined) {
			insert += ',';
		} else {
			insert += str;
		}
		for (let i = 0; i < this.length; i++) {
			result += String(this.value[i]);
			if (i !== this.length - 1) {
				result += insert;
			}
		}
		return result;
	}

	myConcat(...arr) {
		let tmpArr = [];
		for (let index in this.value) {
			tmpArr[index] = this.value[index];
		}
		for (let i = this.length; i < this.length + arr.length; i++) {
			tmpArr[i] = arr[i - this.length];
		}
		return tmpArr;
	}

	myFilter(fn) {
		let result = new Garray(0);

		for (let value of this.value) {
			if (fn(value)) {
				result.myPush(value);
			}
		}

		return result;
	}

	myMap(fn) {
		let result = new Garray(0);
		for (let value of this.value) {
			result.myPush(fn(value));
		}
		return result;
	}

	myReduce(reducer, initialValue) {
		if (typeof reducer !== 'function') {
			return 'TYPEERROR: First parameter must be a function.\n';
		}
		if (initialValue === undefined) {
			initialValue = this.value[0];
		}

		let result;
		let accumulator = initialValue;

		for (let currentValue of this.value) {
			result = reducer(accumulator, currentValue);
			accumulator = reducer(accumulator, currentValue);
		}
		return result;
	}
	mySlice(start, end) {
		if (end === undefined) {
			end = this.length;
		}
		if (!isNaN(Number(start)) || !isNaN(Number(end))) {
			if (start >= this.length) {
				return [];
			}

			let newArr = [];
			for (let i = 0; i < this.length; i++) {
				if (start <= i && i < end) {
					newArr[i - start] = this.value[i];
				}
			}
			return newArr;
		}
		return this.value;
	}
	mySplice(index, removeNumbers, ...replacement) {
		if (removeNumbers === undefined || isNaN(Number(removeNumbers))) {
			removeNumbers = 0;
		}
		if (replacement.length < 1) {
			replacement[0] = '';
		}
		let newArr = new Garray(...this.value);
		let previousArr = newArr.mySlice(0, index);
		let lastArr = newArr.mySlice(index + removeNumbers);
		for (let value of replacement) {
			previousArr.push(value);
		}
		return previousArr.concat(lastArr);
	}
}

// 1. 클래스를 이용해서 G-array() 만들고, 기존 Array() 메소드 실행해보기
let myArray = new Garray(1, 2, 3);
console.log('Using Array.prototype.method()');
myArray.printInfo();
myArray.value.push(4);
myArray.printInfo();
myArray.value.unshift(0);
myArray.printInfo();
myArray.value.pop();
myArray.printInfo();
myArray.value.shift();
myArray.printInfo();
let myArrayConcat = myArray.value.concat(4, 5, 6);
console.log(myArrayConcat);
myArray.printInfo();
let myArrayJoin = myArray.value.join(' , ');
console.log(myArrayJoin);
myArray.printInfo();
console.log(myArray);
console.log(' ');
/**
 * class가 선언될 때 constructor에서 length를 잡아줬습니다.
 * 이후 제 class method를 사용하지 않고, Array()에 선언된 메소드를 사용하면
 * 길이가 변하지 않습니다.
 */

// 2. 클래스를 이용해서 G-array() 만들고, 내가 만든 메소드 실행해보기

let myMethod = new Garray(1, 2, 3);
console.log('Using Garray.prototype.method()');
myMethod.printInfo();
myMethod.myPush(4);
myMethod.printInfo();
myMethod.myUnshift(0);
myMethod.printInfo();
myMethod.myPop();
myMethod.printInfo();
myMethod.myShift();
myMethod.printInfo();
let myMethodMyConcat = myMethod.myConcat(4, 5, 6);
console.log(
	`Run a function (myConcat(4, 5, 6)), result is : ${myMethodMyConcat}`
);
myMethod.printInfo();
let myMethodMyJoin = myMethod.myJoin(' , ');
console.log(`Run a function (myJoin(' , ')), result is : ${myMethodMyJoin}`);
myMethod.printInfo();
let a = myMethod.getArr();
console.log(`Run a function (getArr), result is : ${a}`);
let b = myMethod.myFilter((x) => x > 2);
console.log(`Run a function (myFilter((x) => x > 2)), result is : ${b.value}`);
myMethod.printInfo();
let c = myMethod.myMap((x) => x + 2);
console.log(`Run a function (myMap((x) => x + 2)), result is : ${c.value}`);
myMethod.printInfo();
let d = myMethod.myReduce((acc, cur) => Math.max(acc, cur));
console.log(
	`Run a function (myReduce((acc, cur) => Math.max(acc, cur))), result is : ${d}`
);
myMethod.printInfo();
let e = myMethod.mySlice(2, 3);
console.log(`Run a function (mySlice(2, 3)), result is : ${e}`);
myMethod.myPush(4);
myMethod.myPush(5);
myMethod.myPush(6);
let f = myMethod.mySplice(2, 2, 1);
console.log(`Run a function (mySplice(2, 2, 1)), result is : ${f}`);

/**
 * 클래스에서 선언한 class method를 사용하면
 * 길이가 변하는 것을 확인할 수 있습니다.
 */

 

728x90
반응형