본문 바로가기

FRONT-END/JAVASCRIPT

자바스크립트 상속(Object.create)

반응형

자바스크립트에서의 상속


기존의 상속 구현방법

function Person() {

    this.name = "anonymous";

    this.job = "none";

    this.sayHello = function() {

        alert("Hello, my name is " + this.name);

    };

}


function Unikys() {

    var obj = new Person();

    obj.name = "Unikys";

    obj.job = "Programmer";

    return obj;

}

var me = new Unikys();

me.sayHello(); //Hello, my name is Unikys


console.log(unikys instanceof Unikys); //false

console.log(unikys instanceof Person); //true


초창기 자바스크립트에서는 ECMAScript 표준의 처리 단계를 우회하여 기본적으로 반환되는 this 대신 새로운 obj를 반환하는 방식으로 상속을 구현했다.

치명적 단점으로는 unikys변수가 Unikys의 인스턴스가 아닌 Person의 인스턴스로만 인식한다는 점이다.

new Unikys()로 객체를 생성했는데, Unikys의 인스턴스로 인식 못하는것은 객체지향의 관점에서 매우 치명적이다.


따라서 이후 자바스크립트는 function에 기본으로 들어있는 프로로타입 속성을 새로운 객체로 설정하여 상속하는 방법을 채택했다.

새로운 객체로 선언하듯이, 상속하고자 하는 객체를 하위 객체의 프로토타입 속성으로 설정하면된다.

var person = {

    name: "anonymous",

    sayHello: function() {

        alert("Hello, my name is " + this.name);

    }

};


function Unikys() {

    this.name = "Unikys";

};

Unikys.prototype = person;


var unikys = new Unikys();

unikys.sayHello(); // === "Hello, my name is Unikys"; 

person.sayHello(); // === "Hello, my name is anonymous"; 

console.log(unikys instanceof Unikys); // === true;

person 변수를 객체 표현식으로 정의하여 객체로 생성하고 있다.

그리고 Unikys() 함수로 생성된 unikys 객체가 person의 함수를 상속하고 있다.

하지만 instanceof Unikys는 정상으로 true가 나오지만 unikys 변수가 person 변수를 상속했다는 것을 unikys instanceof person 과 같은 코드로 확인 할수 없다.

따라서 프로토타입을 설정할 때 new로 새로운 객체를 만들어서 Unikys.prototype으로 설정하는 방법으로 문제를 해결하고자 하였다.

function Person() {

    this.name = "anonymous";

    this.sayHello = function() {

        alert("Hello, my name is " + this.name);

    };

}


function Unikys() {

    this.name = "Unikys";

}


Unikys.prototype = new Person(); 


var unikys = new Unikys(); 

unikys.sayHello(); // Hello, my name is Unikys 

console.log(unikys instanceof Unikys); // === true 

console.log(unikys instanceof Person); // === true


Person이라는 새로운 생성자를 선언하여 Unikys.prototype에 new Person()으로 객체를 생성하여 선언하면, 일반적인 객체지향을 사용하듯이 unikys instanceof Unikys와 unikys instanceof Person 모두 true가 된다.



일반적인 객체지향 언어에서는 생성자를 기준으로 비교하여 상속을 확인한다면,

자바스크립트를 실행하는 경우 function Unikys의 프로토타입은 new Person()이고 unikys 변수에서 사용된 프로토타입 역시 new Person()으로 같다.

따라서 unikys instanceof Unikys는 true이다.


그리고 unikys intanceof Person은 unikys 변수의 프로토타입 체인을 따라가서new Person()의 프로토타입과  function Person()의 프로토타입을 비교하면 서로 같으므로 이 역시도 ture값을 반환한다.

내부적으로 생성자의 연결은 깨졌어도 instanceof가 프로토타입을 기준으로 비교하기 때문에 외부적으로 상속을 확인하는 동작은 정상으로 동작한다.



Object.create 함수

자바스크립트 개발자들 내부에서 생성자로 객체를 생성하면서 연결이 깨지는 것을 원하지 않았다.

그래서 만들어 낸것이 Object.create 함수이다.

내부적인 구조의 불완정성이외에도 new라는 키워드 자체가 "자바스크립트 답지 않다"는 의견이 많이 반영되면서 객체의 상속하여 생성할 수 있는 함수를 별도로 제공하게 된 것이다.

하지만 표준에는 약간 늦게 추가되어 현재 Object.create 함수는 익스플로러는 9번전 이상, 나머지 크롬, 파이어폭스, 사파리 5 이상의 브라우저에서 지원한다.


Object.create 함수

Object.create = function(o) {

function F() {}

F.prototype = o;

retrun new F();

}

내부함수 F 는 아무런 초기화도 하지 않는 기본 함수이다.

그리고 F의 프로토타입만 인자로 받은 객체로 수정하여 새로운 F객체를 생성하여 반환한다.

Object.create 함수의 인자를 생성자인 함수가 아니라 프로토타입으로 설정할 객체 또는 인스턴스라는 점이다.

function Person(name) {

    this.name = name;

}


Person.prototype = {

    yell: function() {

        alert("My name is " + this.name);

    }

};

var unikys = Object.create(Person.prototype); 

unikys.name = "Unikys";  

unikys.yell();

Object.create 함수의 인자로 넘겨준것이 Person 생성자가 아니라 프로토타입이라는 점이다.

이렇게 함수가 아닌 객체를 넘겨주는 것은 Object.create의 내부적인 형태를 보면 쉽게 이해할 수 있다.

Object.create 함수안에서 인자로 넘어온 o를  이용해서 그대로 임의의 기본 함수 F의 프로토타입으로 설정해주고 있다.

그리고 new 키워드와 생성자를 이용해서 객체를 생성하던 것과 비교해보면 unikys 변수에 직접 unikys.name과 같이 속성을 부여하고 있는 것을 볼수 있다.

이렇한 것이 어떻게 보면 개발자 관점에서 작업을 별도로 해야하는 불편함이 있을 수도 있지만, 전체적인 소스의 관점에서는 조금 더 직관적으로 볼수 있다.

또한 이러한 초기화 설정은 Object.create 함수의 두번째 인자를 통해서 할 수 있도록 표준에서 정의하고 있기도 하다.


이렇게 Object.create 함수를 통해서 객체를 생성하면 개발자가 new 키워드를 사용하지 않고 함수 호출로 객체가 생성되는 것을 확인할 수 있다.

 new 키워드를 사용할 떄와 달리 전체적으로 소스에 생성자 개념이 약해지고 객체의 인스턴스와 인스턴스 간의 상속을 강조하는 것이 Obejct.create 함수의 특징이라고 할 수 있다.


Object.create 함수의 상속여부 확인

자바스크립트 개발자들은 내부에서 위와 같이 생성자로 객체를 생성하면서 new 키워드를 사용하는 경우 상속여부를 instanceof를 통해 구분하였다.

Object.create는 인자로 생성자가 아닌 객체를 받으므로 이를 확인하는 것이 조금 다를 수 있을 거 같다.

instnaceof 의 내부 표준명세에는 생성자를 비교하는 것이 아니라 프로토타입을 비교하므로 new 키워드와 동일하게 확인 할수 있다.


초창기의 상속방법을 생각하면 생성자로 생성한 Person을 그대로 instanceof의 인자로 사용하지만, Obejct.create는 인자로 Person.prototype을 사용해서 생성한 뒤 Person으로 비교하는 것이 약간 직관성이 떨어진다.

var person = {

    yell: function() {

        alert("My name is " + this.name);

    }

};

var unikys = Object.create(person);

console.log(unikys instanceof person); // === TypeError!

Object.create는 객체와 객체간의 상속을 시켜주는 함수이다.

따라서 이렇게 표준에 명시되어 있듯이 Function을 인자로 받는 instanceof는 에러가 나고 적합하지 않다.

이럴 때 사용하기 위한 함수는 바로 Object.getPropertyOf이다.

console.log(Object.getPrototypeOf(unikys) === person); // === true 

console.log(Object.getPrototypeOf(person)); // === Object {}


조금 다른 관점으로 프로토타입 쪽에서 함수를 확인하고자 한다면 Obejct의 기본함수 isPropertyOf를 사용할 수 있다.

console.log(person.isPrototypeOf(unikys)); // === true; 

console.log(Object.prototype.isPrototypeOf(unikys)); // === true

person이 unikys의 프로토타입인지를 확인하는 함수 이므로 person.isPrototypeOf(unikys)를 호출해야한다.


Object.create 객체의 초기화

객체를 생성할 때 new 키워드를 사용하면 생성자 안에서 생성되는 객체를 초기화하는 작업을 수행할 수 있는데,  Object.create 함수의 구현 방법을 살펴보면 Object.create와 별도로 객체를 초기화하고 있는 것을 볼 수 있다.

이러한 과정을 하나의 함수로 묶어서 객체별로 간단한 상속 함수를  만들수도 있지만, 표준 Object.create 함수는 2번째 인자를 선택적으로 받아서 객체를 초기화하도록 되어 있다.

Object.create의 2번째 인자의 간단한 방법

function Person(name) {

    this.name = name;

};

Person.prototype = {

    yell: function() {

        alert("My name is " + this.name);

    }

};

var unikys = Object.create(Person.prototype, {

    name: {

        value: "Unikys"

    }

});

unikys.yell(); // "My name is Unikys" 

unikys.name = "Suniky";

unikys.yell();

설정할 속성의 이름과 값을 객체로 해당 정보를 넘겨서 생성할 수 있다.

unikys.name을 ":Unikys"라는 값으로 초기화 한다.

이후 unikys.name="Suniky"로 다시 설정한 다음 unikys.yell() 함수를 호출한다.

My name is Suniky가 나올것으로 예상하지만 My name is Unikys 라고 나오게 된다.


Object.create 함수를 통해서 값만 설정하면 읽기 전용 속성이 되어 값을 수정할 수 없게 된다.

따라서 값을 수정할 수 있게 하려면 해당 속성에 대하여 추가로 설정해야 한다.




접근자는 다른 속성들의 조합을 통해서 새로운 속성이 계산 또는 유추할 수 있을 때 편하게 사용하기 위해 활용된다.


접근자 활용

var unikys = Object.create(Person.prototype, {

    name: {

        value: "Unikys",

        configurable: true,

        enumerable: true,

        writable: true

    }

});



function Person(name) {

    this.name = name;

};

Person.prototype = {

    yell: function() {

        alert("My name is " + this.name);

    }

};


var unikys = Object.create(Person.prototype, {

    name: {

        value: "Unikys",

        configurable: true,

        enumerable: true,

        writable: true

    }

});


Object.defineProperties(unikys,{

firstName: {

value: "Sung-ihk",

writable: true

},

lastName: {

value: "Yang",

writable: true

},

fullName: {

get: function(){

return this.firstName + " " + this.lastName;

},

set: function(value){

var res = value.split(" ");

if(res.length > 1) {

this.firstName = res[0];

this.lastName = res[1];

}else{

alert("Wrong format!");

}

}

}

});


console.log(unikys.fullName);

unikys.fullName = "Hello world";

console.log(unikys.firstName);

console.log(unikys.lastName);

다른 속성들을 조합하여 하나의 새로운 속성을 만들 때와 반대로 설정을 다른 속성으로 투여하고자 할 때  get과  set 속성을 사용하면 편리하고 이용할 수 있다.

객체를 생성할때 Object.create를 사용하면 조금 더 다양하게 변수를 특징적으로 설정할수 있고 속성간의 관계도 편리하게 설계할 수 있다.


for-in을 사용할 때 현재 객체의 속성만 루프 돌기 위하여 obj.hasOwnProperty(key)와 같이 현재 객체의 속성인지 판단하였지만 enumerable을 false로 설정하면 for-in에서 제외할수도 있어서 프로그래밍상의 실수를 줄일 수 있다.,

또한  configurable이나 wirtable을 설정함으로써 읽기전용(readonly) 속성을 부여해서 라이브러리의 로직과 동작을 보호할 수 있다.


Object.create와  new 키워드 조합

Object.create 함수를 사용하는데 있어서 객체를 생성하는 것이 아니라, 기존과 같이 new 키워드를 통해서 생성자를 초기화하고 싶을 때도 많을것이다.

이럴때 Object.create와 new 키워드를 조합하여 사용하는 것도 유용하다.

function Person(name) {

    this.name = "anonymous";

}


function Unikys() {

    this.name = "Unikys";

}


Unikys.prototype = Object.create(Person.prototype,{

constructor: {

value: Unikys

}

});


var unikys = new Unikys();

console.log(unikys instanceof Unikys);

console.log(unikys instanceof Person);

console.log(unikys.constructor);

Object.create의 2번째 인자로 Unikys.prototype의 생성자 속성을 읽기 전용으로 설정하여 기존에 자바스크립트가 자체적으로 가지고 있었던, 생성자 연결이 깨졌던 현상을 보완하여 new  키워드를 통한 상속을 사용할 수 있다.

이렇게 내부적으로 잘못 동작하고 있는 부분을 인지하고 사용한다면 객체지향 개발 방법론을 적용하면서 조금 더 위험성이 적게 프로그래밍 할 수 있다.


class와 extends를 통한 상속


ECMAScript 6에서는 새로운 키워드인 class와 extends를 정의하였다.

기존의 new 키워드를 함수와 함께 사용할 때는 개발자의 실수로 인한 오동작은 일어나도 오류가 발생하지 않아서 오작동의 원인을 찾아내기가 어려웠다.

그래서 객체지향 프로그래밍처럼 new 키워드의 생성자와 함수 호출객체를 구분하여 오류가 발생하도록 보완하였다.


따라서 ECMAScript 6의 class 키워드가 호환되는 브라우저에서는 기존과 같이 new 키워드와 함수를 사용해도 되고, 새로 정의된 class 키워드를 사용해도 된다.

class Person {

constructor() {

this.name="anonymous";

}

}


class Unikys extends Person {

constructor() {

super();

this.name = "Unikys";

}

}


var unikys = new Unikys();

console.log(unikys instanceof  Unikys);

console.log(unikys instanceof Person);

console.log(unikys.constructor);

새로운 표준이 정의되어 이제 자바스크립트에서도 다른 객체지향 언어들과 유사하게 상속을 구현할 수 있게 되었다.]

그리고 class와 extends 키워드를 사용하더라도 프로토타입 상속을 한 것과 동일하게 instanceof가 동작하고 생성자 또한 프로토타입과 동일하게 출력되는 것을 확인할 수 있다.

이처럼 기존의 웹환경에서 객체지향이 아주 효율적이지 않음에도 표준에서 객체지향 키워드를 추가하고 있는 것은 자바스크립트가 이제 웹에서만 활용되는 것이 아니라 다양한 분야에서, 특히 객체지향으로 개발하면 효율적인 백엔드 등과 같은 부야에서도 점점 활용도가 높아지고 있다는 것을 보여주는 것이기도 하다.

앞으로 웹에서도 이러한 class와 extends 키워드를 사용할 수 있다.

다만 브라우저들의 지원현황을 검토하여 크로스브라우저 호완성에 대한 고민이 필요하다.


Object.create 함수 크로스 브라우저 호환성 보완

Object.create 함수를 지원하지 않는 브라우저 사용자들을 위해서 간단하게 Object.create 함수를 임의로 만들어서 사용하는 방법

(function(){

if(!Object.create){

Object.create = (function(){

function F() {};

return function(o) {

F.prototype = o;

return new F();


}

}());

}

}());

Object.create 함수를 위해 간단하게 같은 기능을 수행하는 함수로 생성해두면 편리하게 사용할 수 있다.

Object.create의 속성을 초기화하는 2번째 인자까지 수용할 수 있게 개발하려면 더 많은 소스코드가 필요하다.

그럴때는 추가적인 자바스크립트 소스를 활용하여 2번째 인자를 수용할지, 아니면 미지원 브라우저로 경고를 띄울 것인지를 결정한다.

이때는 프로그램의 주사용 고객층과 Object.create를 사용해야하는 전체 프로그램의 복잡성, 그리고 Object.create를 제대로 구현하는데 들어가는 비용을 생각하여 결정하면 좋을 것이다.



생성자와 Object.create 성능 비교

function Person(name, blog){

this.name = name;

this.blog = blog;

}

Person.prototype = {

yell: function(){

console.log();

}

}

var unikys = new Person("unikys", "http://unikys.tistory.com");



var unikys = Object.create(Person.prototype, {

name: {

value: "Unikys",

configurable: true,

enumerable: true,

writable: true

},

blog: {

value: "http://unikys.tistory.com",

configurable: true,

enumerable: true,

writable: true

}

});






정리)

Function을 생성할 때 기본적으로 프로토타입 속성이 생성된다.

이 프로토타입을 다른 객체로 설정함으로써 다른 객체의 속성들과 함수들을 공유 또는 상속할 수 있다.

객체는 프로토타입과 내부 링크로 연결되어 있어 프로토타입의 속성들을 자기의 속성인 것처럼 접근할 수 있다.

객체에서 직접 this.constructor.prototype으로 접근하지 않으면 프로토타입의 값은 수정되지 않고 현재 객체 내에 같은 이름의 속성이 설정되어 트포로타입의 설정값이 가려진다.

new로 객체를 생성할 때 프로토타입은 객체 간 공유되어 메모리 자원이 절약될 수 있다.

여러 개의 프로토타입 체인을 만들경우 속성 조회에 있어서 성능저하가 있을 수도 있다.

new는 생성자 기반 상속으로 사용되며, Object.create는 객체 기반 상속으로 사용된다.

객체지향을 위한 class와 extends 키워드가 정의되어있지만, 호환성 검토가 필요하다.

new와 생성자로 객체를 생성하는 것이 Object.create로 생성하는 것보다 성능상 유리하다.

성능 이슈는 객체를 다량으로 사용하는경우 조심하면 좋지만, 특별한 경우가 아니면 체감하기 힘들다.


출처-속깊은 자바스크립트 양성익 지음

반응형