이렇게 다른 속성들을 조합하여 하나의 새로운 속성을 만들 때와 반대로 설정을 다른 속성으로 투영하고자 할 때 get과 set속성을 사용하면 편리하게 이용할 수 있다. 이처럼 객체를 생성할 때 Object.create를 사용하면 조금 더 다양하게 변수를 특징적으로 설정할 수 있고 속성 간의 관계도 편리하게 설계할 수 있다.
자바스크립트 개발자들은 내부에서 생성자로 객체를 생성하면서 연결이 깨지는 것을 원하지 않았다. 그래서 만들어낸 것이 Object.create함수이다.
내부적인 구조의 불완전성 이외에도 new라는 키워드 자체가 '자바스크립트답지 않다'는 의견이 많이 반영되면서 객체를 상속하여 생성할 수 있는 함수를 별도로 제공하게 되었다.
더글라스 크락포드(Douglas Crockford)가 주장했던 Object.create함수의 형태
초기 Object.create함수
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
}
함수 안을 살펴보면 내부 함수 F는 아무런 초기화도 하지 않은 기본 함수이다. 그리고 F의 프로토타입만 인자로 받는 객체로 수정하여 새로운 F객체를 생성하여 반환한다.
여기서 주의점은 Object.create함수의 인자는 생성자인 함수가 아니라 프로토타입으로 설정할 객체 또는 인스턴스라는 점이다. 따라서 위의 함수를 토대로 앞서 나온 상속의 예를 간단하게 구현하면 아래와 같다.
Object.create함수를 이용한 상속 예
function Person(name) {
this.name = name;
}
Person.prototype = {
yell: function() {
alert("my name is " + this.name);
}
};
var shiro = Object.create(Person.prototype);
shiro.name = "Shrio";
shiro.yell(); // my name is Shrio
여기서 주의점은 Object.create함수의 인자로 넘겨준 것이 Person생성자가 아니라, 프로토타입이라는 점이다. 이렇게 함수가 아닌 객체를 넘겨주는 것은 Object.create의 내부적인 형태를 보면 쉽게 이해할 수 있다. Object.create함수 안에서는 인자로 넘어온 o를 이용해서 그대로 임의의 기본 함수 F의 프로토타입으로 설정해주고 있다.
그리고 new키워드와 생성자를 이용하여 객체를 생성하던 것과 비교해보면 shiro변수에 직접 shiro.name과 같이 속성을 부여하고 있는 것을 볼 수 있다. 이러한 것이 어떻게 보면 개발자 관점에서 작업을 별도로 해야 하는 불편함이 있을 수도 있지만, 전체적인 코드의 관점에서는 조금 더 직관적으로 보일 수 있기도 하다. 또한, 이러한 초기화 설정은 Object.create함수의 두 번째 인자를 통해 할 수 있도록 표준에서 정의하고 있기도 하다.
이렇게 Object.create함수를 통해서 객체를 생성하면, 개발자가 new키워드를 사용하지 않고 함수 호출로 객체가 생성되는 것을 확인할 수 있다. new키워드를 사용할 때와는 달리 전체적으로 코드에 생성자의 개념이 약해지고 객체의 인스턴스와 인스턴스 간의 상속을 강조하는 것이 Object.create함수의 특징이라고 볼 수 있다.
function Person() {
this.name = 'shiro';
this.job = 'none';
this.hello = function() {
alert('Hello, my name is ' + this.name);
};
}
function Shiro() {
var obj = new Person();
obj.name = 'shiro';
obj.job = 'Programmer';
return obj;
}
var shiro = new Shiro();
shiro.hello();
이처럼 초창기 자바스크립트에서 ECMAScript표준의 처리 단계를 우회하여 기본적으로 반환되는 this대신 새로운 obj를 반환하는 방식으로 상속을 구현하였다. 이런 방법은 나름 직관적으로 보이긴 하지만, 치명적 단점이 있다. 바로 shiro변수가 Shiro의 인스턴스가 아닌 Person의 인스턴스로만 인식한다는 점이다.
일반적으로 자바스크립트에서 instanceof를 사용하는 것이 흔한 일은 아니라 불편함을 느끼지는 못할 수도 있지만, 객체지향을 구현하는 정석이 아닌 우회하는 느낌이 강하다. 왜냐하면 new Shiro()로 객체를 생성하였는데, Shiro의 인스턴스로 인식 못하는 것은 객체지향의 관점에서 매우 치명적인 단점이 될 수 있다.
따라서 이후 자바스크립트는 function에 기본으로 들어 있는 프로토타입 속성을 새로운 객체로 설정하여 상속하는 방법을 채택하였다. 이 방법은 앞에서 설명한 프로토타입을 새로운 객체로 선언하듯, 상속하고자 하는 객체를 하위 객체의 프로토타입 속성으로 설정하면 된다.
객체로 프로토타입을 수정한 자바스크립트 상속 구현
var person = {
name: 'new',
hello: function() {
alert('hello, my name is ' + this.name);
}
};
function Shiro() {
this.name = 'Shiro';
}
Shiro.prototype = person;
var shiro = new Shiro();
shiro.hello(); // hello my name is Shiro
person.hello(); // hello my name is new
console.log(shiro instanceof Shiro); // true
코드를 보면 person변수를 객체 표혆식으로 정의하여 객체로 생성하고 있다. 그리고 Shiro()함수로 생성된 shiro객체가 person의 함수를 상속하고 있다.
하지만 이때 instanceof Shiro는 정상으로 true가 나오지만, shiro변수가 person변수를 상속했다는 것을 shiro instanceof person과 같은 코드로 확인 할 수 없다. 따라서 프로토타입을 설정할 때 new로 새로운 객체를 만들어서 Shiro.prototype으로 설정하는 방법으로 이러한 문제를 해결하고자 하였다.
프로토타입을 이용한 자바스크립트 상속 구현 방법
function Person() {
this.name = 'new';
this.hello = function() {
alert('hello, my name is ' + this.name);
};
}
function Shiro() {
this.name = 'Shiro';
}
Shiro.prototype = new Person();
var shiro = new Shiro();
shiro.hello(); // hello, my name is Shiro
console.log(shiro instanceof Shiro); // true
console.log(shiro instanceof Person); // true
이처럼 Person이라는 새로운 생성자를 선언하여 Shiro.prototype에 new Person()으로 객체를 생성하여 선언하면, 일반적인 객체지향을 사용하듯이 shiro instanceof Shiro와 shiro instanceof Person모두 true가 된다. 이는 바로 앞의 상속 방법들과 비교하여 객체지향적인 개발 방법론을 선호하는 개발자들이 만족할만한 결과일 것이다.
stranger.gender = "female";
console.log(stranger.gender); // female
console.log(shiro.gender); // male
console.log(Person.prototype.gender) // male
이전 'prototype에 정의된 변수를 수정하는 예'에서 구조를 보면 prototype에서 참조하던 gender값이 바뀌는게 아니라, stranger객체에 gender속성이 추가되어 저장되는 것이다. 따라서 stranger에서 gender를 조회하면 'female'을 출력한다. 그리고 'Person.prototype.gender'에 gender값은 그대로 'male'값을 그대로 유지하므로 shiro에서는 그대로 prototype에 있는 gender를 참조하여 'male'을 출력한다.
만약 모든 Person객체에 있는 gender를 공통으로 static변수인 것처럼 바꾸고 싶다면 prototype에 있는 gender를 바꿔주면 된다.
function Person1() {};
Person1.prototype.gender = "male";
var shiro1 = new Person1(),
stranger1 = new Person1();
console.log(shiro1.gender); // male
console.log(stranger1.gender); // male
Person1.prototype.gender = "female";
console.log(shiro1.gender); // female
console.log(stranger1.gender); // female
function Person(name, blog) {
this.name = name;
this.blog = blog;
}
Person.prototype.getName = function() {
return this.name;
};
Person.prototype.getBlog = function() {
return this.blog;
};
var shiro = new Person("shiro", "shiro21.tistory.com");
var stranger = new Person("stranger", "google.com");
console.log(shiro.getName()); // shiro
console.log(shiro.getBlog()); // shiro21.tistory.com
console.log(stranger.getName()); // stranger
console.log(stranger.getBlog()); // google.com
Person.prototype 코드부분은 constructor.prototype코드로 접근할 수 있다.
Person이라는 생성자의 prototype속성을 설정하고 있는 것이다.
이후 새로 생성된 shiro와 stranger객체는 내부적으로 이 prototype객체를 참고하여 prototype객체가 가지고 있는 getName()과 getBlog()함수를 사용할 수 있다.
이는 '생성자를 통해서 생성한 객체들이 prototype을 공유한다'라고 생각하면 된다.
객체 생성 후 prototype의 수정 예
function Person(name, blog) {
this.name = name;
this.blog = blog;
}
Person.prototype.getName = function() {
return this.name;
};
Person.prototype.getBlog = function() {
return this.blog;
};
var shiro = new Person("shiro", "shiro21.tistory.com");
var stranger = new Person("stranger", "google.com");
Person.prototype.introduce = function() {
console.log("Hi!, my name is " + this.name + ", plase visit my blog " + this.blog);
};
shiro.introduce(); // Hi!, my name is shiro, plase visit my blog shiro21.tistory.com
이렇게 이미 생성자를 통해 생성된 객체라도 나중에 생성자의 프로토타입에 새로운 속성을 추가할 수 있고, 이렇게 추가된 속성 또한 모든 객체가 공유한다.
Person.prototype.gender = "male";
console.log(shiro.gender); // male
console.log(stranger.gender); // male
프로토타입에 함수가 아닌 변수도 추가하여 공유가 가능하다.
prototype에 정의된 변수를 수정하는 예
stranger.gender = "female";
console.log(stranger.gender); // female
console.log(shiro.gender); // male
console.log(Person.prototype.gender) // male
위 코드를 실행하면 stranger객체의 gender값은 female로 바뀌었지만, shiro객체의 gender값은 여전히 male을 출력한다. 그런데 특이한 점은 stranger.gender였던 Person.prototype.gender값이 female로 바뀌지 않고 여전히 male로 유지되고 있다는 점이다.
프로토타입은 사전적 의미로 '원형'이다. 이 말을 그대로 자바스크립트에 투영해서 무엇의 원형을 나타내는지 생각하면 된다.
Javascript에서의 객체 생성
자바스크립트가 채택하고 있는 자바의 몇 가지 문법 중 대표적인 것은 new키워드이다. new키워드는 ECMAScript6 이전에 class키워드가 없었던 자바스크립트 문법에는 적합하지 않아서, 자바와 조금 다른 방식으로 채택하고있다. 자바에서는 class를 정의하지만, 자바스크립트에서는 function을 정의한다.
Function을 통한 new키워드 활용
function Person(name, blog) {
this.name = name;
this.blog = blog;
}
var shiro = new Person("shiro21", "shiro21.tistory.com");
alert(shiro.name);
위 코드를 객체지향 관점에서 봤을때, 자바스크립트에서 function은 자바의 class와 생성자를 합쳐놓은 개념이다.
객체를 생성할 때 function을 사용하는 방식은 다른 언어와 많은 차이가 있다. 따라서 ECMAScript6에서는 다른 언어와의 이질감을 줄이고, 객체지향을 조금이라도 더 지원하기 위해 class키워드를 새로 만들기도 했다.
class를 이용한 new키워드 활용
class Person {
constructor(name, blog) {
this.name = name;
this.blog = blog;
}
}
var shiro = new Person("shiro21", "shiro21.tistory.com");
alert(shiro.name);
class키워드를 이용하면 자바 등의 다른 객체지향 언어와 유사하게 클래스를 정의할 수 있다. 그런데 class키워드를 사용하면 내부적으로 특수한 function으로 정의한다는 점이다.
아래를보면 class Person의 내부 정의를 function Person정의와 다르게 하고 있는 것을 확인할 수 있다.
class키워드가 function과 다른 점은 함수처럼 바로 실행하면 에러가 발생한다는 점이다. 그리고 함수 정의와는 다르게 현재 스코프에 진입했을 때 바로 사용할 수 있는 것이 아니라, 해당 class키워드로 클래스를 선언하는 소스가 실행되고 난 이후에 객체를 생성할 수 있다.
this에 대하여
앞 코드에서 function을 이용한 new키워드를 보면 생성자 함수 안에 this를 통해 객체를 초기화하고 있는 것을 확인할 수 있다. 일반적인 객체지향 프로그래밍 언어의 관점에서 보면 당연한 표현이지만, 자바스크립트에서의 this는 조금 다르게 동작하는 경우가 많다.
함수를 호출하는 방법
일반 함수로의 호출
멤버함수로의 호출
call()함수를 이용한 호출
apply()함수를 이용한 호출
일반적인 함수 호출 예
function say(something) {
alert(something);
}
say("Hello World");
멤버함수 호출 예 :: 객체의 속성으로 함수를 호출 ( 함수가 객체의 멤버변수로 설정되었을때 호출하는 방법 )