이전 포스팅에서 객체 리터럴
에 대해 언급했다. 객체 생성 방식은 객체 리터럴 외에도 여러가지 방법이 존재한다.
const person = new Object();
person.name = 'Lee'
person.sayHello = function() {
console.log('Hi My name is '+ this.name);
};
console.log(person);
person.sayHello();
객체 생성자 함수로 하여금 객체를 생성하는 것은 리터럴 방식에 비해 사실 불편하다. 하지만 리터럴 방식의 단점인 여러 객체를 생성할 때 불편한 점을 해결할 수 있다.
function Circle(radius) {
this.radius = radius;
this.getDiameter = funtion () {
return 2 * this.radius;
};
}
const Circle1 = new Circle(5);
const Circle2 = new Circle(10);
다음처럼 프로퍼티가 같은 객체를 여러개 생성해야할 때 생성자방식은 유용하다. 여기서 중요하게 보아야할 점은 new의 존재이다. 만약 new를 지칭하지 않는다면 Circle1,2는 단순히 결과값만 가지게 될 것이다. 즉 new 연산자가 포함돼야 생성자 함수로 동작한다.
함수의 여러 프로퍼티 중 arguments프로퍼티는 전달된 인수들의 정보를 담은 유사 배열 객체이다. 함수 내부서 지역 변수처럼 사용된다. length 프로퍼티를 가지고 있다.
arguments의 length와는 다르게 함수 객체의 매개변수의 개수를 가리킨다. 말이 애매하다. 매개변수와 인자. 직접 console.log로 두 단어의 정의를 확실하게 하도록 하자. 매개변수는 즉 함수 설계도에 만들어낸 변수의 숫자를 말하는 것이고 인자는 실제로 들어온 것들의 수이다.
객체지향 프로그래밍에서 상속
이란 개념은 꽤 중요하다. 코딩을 더 효율적으로, 복잡하게 처리할 수록 우리는 객체를 여럿 생성하게 될 것이다. 프로토타입은 자바스크립트 객체에서의 상속의 역할을 한다.
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getArea = fucntion () {
return Math.PI * this.radius ** 2
}
// .prototype(프로퍼티)는 생성자 함수에서만 사용된다.
const circle1 = new Circle(1);
const circle2 = new Circle(2);
앞선 코드와 결과는 똑같지만 비용적으로 생각해보면 더 많은 메모리 절감을 이루어낸다. 만약 프로토 타입으로 선언하지않고 Circle안에 메서드로 getArea를 만들게되면, circle객체를 100개 생성하면 100개의 메서드가 따로 구축될 것이다. 그러나 프로토타입에 위와 같이 작성하게 되면 100개의 객체는 하나의 프로토타입 메서드를 참조하게 된다. radius는 변수로 받아야 하므로 프로토타입으로 만들 수 없지만, 모든 상황에서 원의 넓이를 구하는 과정은 동일하기에 하나만 만들어서 모두가 나눠쓸 수 있다. 모든 객체는 내부 슬롯으로 프로토타입을 가진다.
/////예시1///////
const obj = {};
const parent = { x: 1};
obj.__proto__ = parent;
console.log(obj.x) // 1
//////////예시2////////////////
const parent = {};
const child = {};
parent.__proto__ = child;
child.__proto__ = parent; // TypeError
특정 객체를 __proto__
를 통해 부모-자식 관계로 만들 수 있다. 첫번째 예시는 그 과정을 보여준다. 두번째 예시는 에러과정을 보여준다. 잘못된 프로토타입 체인으로 두 객체가 서로를 부모라고 말하는 이질적인 상황인 것이다. 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 한다. 정리하면 특정 객체의 프로토타입은 부모 객체가 된다.
function foo() {
x = 10;
}
foo();
console.log(x);
다음의 코드는 러프하게 보아도 문제가 있다는 것을 바로 눈치챌 수 있다. x는 함수내에서 정의된 변수인데, 이것을 밖에서 활용할 수는 없을 것이다. 실제로 자바스크립트 엔진은 x를 찾기 위해 여러 절차를 밟는다.(우선적으로는 함수의 스코프에서, 그 후에는 전역에서 찾게 될 것이다.) 위의 코드에서는 두 해당사항이 모두 없기에 에러를 반환할 것 같지만 그렇지는 않다. 자바스크립트는 위와 같은 상황일 때 전역 객체에 x 프로퍼티를 생성한다.(암묵적 전역)
암묵적 전역은 오류를 발생시킬 원인이 될 수 있기에 이러한 것을 해결하고 지원하기 위해 ES5부터 strict mode
가 추가된다. 이는 자바스크립트 언어의 문법을 좀 더 엄격히 적용하여 에러를 발생시킨다. 더하여서, ESLint와 같은 린트 도구들은 소스코드 실행전에 소스코드를 스캔하여 문법적 오류뿐만 아니라 잠재적 오류까지 찾고 오류의 원인까지 리포팅해준다.
책에서는 strict mode보다는 린트 도구의 사용을 선호한다.
자바스크립트의 객체는 크게 3개의 객체로 분류할 수 있다.
표준 빌트인 객체 : 애플리케이션 전역의 공통 기능을 제공한다. 언제나 사용가능하며 전역 객체의 프로퍼티로서 제공된다. 별도의 선언없이 전역변수처럼 언제나 참조가능하다.
호스트 객체 : 특정한 환경에서 추가로 제공하는 객체를 말하며, DOM, XMLHttpRequest, fetch등이 있다.
사용자 정의 객체 : 사용자가 직접 정의한 객체
40여 개의 표준 빌트인 객체가 존재하며 생성자 함수 객체이다. 각 객체들에 대해 프로토타입 메서드와 정적 메서드를 제공한다.
const numObj = new Number(1.5);
///1///
console.log(numObj.toFixed());
///2///
console.log(Number.isInterger(0.5));
1의 toFix()는 프로토타입 메서드이며, 2의 isInterger()는 정적메서드이다. 프로토타입 메서드는 인스턴스와의 관계가 필요하며 정적메서드는 객체에 대한 정보만 필요하다.
const str = 'hello'
console.log(str.length);
console.log(str.toUpperCase());
str변수에 'hello'라는 원시값(문자열)을 할당했다. str은 원시값임에도 불구하고 String객체의 프로토타입 메서드를 사용할 수 있다. 느낌상 가능해야 옳다지만, 구체적인 방법에는 래퍼 객체(wrapper Object)
개념이 들어간다. 자바스크립트 엔진이 암묵적으로 원시값과 연관된 객체를 일시적으로 생성할 때 이러한 객체를 래퍼객체라고 한다. 임시 래퍼객체의 행동이 끝이나면 다시 래퍼객체는 원시값으로 돌아온다.(기능해제-가비지 컬렉션의 대상이 된다.)
전역 객체(global object)
는 코드가 실행되기 이전, 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 최상위 객체이다. window, self, this, frames로 전역객체를 가리킬 수 있으며, Node.js환경에서는 global이다.
객체에 대해 더 자세히 알아보았다. 래퍼 객체같은 개념을 보면, 어떻게든 모든 것을 객체화 시키려는 엔진의 경향성이 보인다. 이렇기에 객체지향 언어일 것이다. 객체에 대한 개념이 헷갈리는 이유에 가장 크게 영향을 주는 것은 객체에 대한 필요성을 느끼지 못하기 때문이 가장 크다고 생각한다. 예를 들어 래퍼 객체의 역할을 이해하는 것에서 끝낼 것이 아니라, 만약 래퍼 객체의 과정이 없고 원시값 그대로만 유지된다면, 우리는 위 코드 예시처럼 str = 'string' -> str.length 같은 메서드 사용이 불가능할 것이다. 객체에 대한 이해를 위해 객체에 대한 필요성을 느껴보고, 객체의 필요성을 느끼기기 위해 객체화가 안되어있을 때를 가정해서 불편함을 상상하면 객체에 대한 단어 정의같은 것은 볼 필요가 없다고 생각한다. 사실 그냥 코딩 여러번 해보면 스스로도 편리함과 일시적 유지보수를 위해 중복되는 것들을 합치거나, 아주 작은 영역에서의 자동화라던지 프로그래밍적 논리를 사용하고 있는 자신을 볼 수 있다. 그러한 비슷한 개념으로 객체지향이 효율적이라는 것을 느낄 수 있다.