--- title: 객체 모델의 세부 사항 slug: Web/JavaScript/Guide/Details_of_the_Object_Model translation_of: Web/JavaScript/Guide/Details_of_the_Object_Model original_slug: Web/JavaScript/Guide/객체_모델의_세부사항 ---
자바스크립트는 클래스 기반이 아닌 prototype에 기초한 객체 기반 언어 입니다. 이런 차이점으로 인해, 객체들의 계층 구조의 생성과 속성 및 속성 값의 상속을 어떻게 구현해야 하는지에 대한 부분이 덜 분명할 수 있습니다. 이번 장에서는 이런 상황을 명확하게 하고자 합니다.
이번 장에선 이미 자바스크립트를 어느 정도 알고 있고, 간단한 객체를 생성하는 함수들을 사용해보았다는 가정하에 진행합니다.
Java와 C++같은 클래스 기반의 언어들은 두개의 구별되는 개념에 기반을 두고 있습니다: 그건 바로 클래스와 인스턴스입니다.
직원
클래스는 직원들을 대표할 수 있습니다.빅토리아
는 특정 직원 개인을 나타내는 직원
클래스의 인스턴스가 될 수 있습니다. 인스턴스는 부모 클래스의 속성과 동일한 속성들을 가집니다.자바스크립트같은 프로토타입기반의 언어들은 위와 같은 클래스와 인스턴스의 차이를 두지 않습니다. 간단하게 객체들을 가질 뿐입니다. prototype기반의 언어는 원형(프로토타입)의 객체 개념을 가지고 있습니다. 하나의 객체는 새로운 객체를 생성했을 때 초기 속성을 가질 수 있도록 하는 형판(template)으로 사용됩니다. 객체는 생성될 때 혹은 실행 시에 자기 자신의 속성을 명시할 수 있습니다. 추가적으로, 객체들은 또 다른 객체를 생성하기 위한 프로토타입으로 연관지어 질 수 있으며 프로토타입으로부터 생성된 두번째 객체가 프로토타입인 첫번째 객체의 속성을 공유(혹은 접근)하는 것을 허용합니다.
클래스 기반의 언어들에서, 별도의 클래스를 생성하고 그 안에서 해당 클래스를 정의 할 수 있습니다. 해당 정의에서 클래스의 인스턴스를 생성할 수 있는 생성자라고하는 특별한 메서드를 명시할 수 있습니다. 생성자는 해당 인스턴스의 초기 속성 값을 지정할 수 있고, 생성 시점에, 다른 적절한 처리를 수행 할 수 있습니다. 클래스의 인스턴스를 생성하기 위해서 new 연산자와 함께 생성자를 호출해야 합니다.
자바스크립트는 위와 비슷한 방법을 취합니다. 하지만 생성자이외에 따로 클래스 정의를 가지고 있지는 않습니다. 대신, 특정 속성및 속성값들을 가지고 객체를 생성하는 생성자 함수를 정의할 수 있습니다. 특정 자바스크립트 함수는 생성자로 사용 될 수 있습니다. 새로운 객체를 생성할려면 new연산자와 함께 생성자 함수를 사용해야 합니다.
ECMAScript 2015에 클래스 선언이 새롭게 소개되었습니다.
ECMAScript 2015에서 소개된 자바스크립트 클래스는 주로 문법적 설탕으로 기존 자바스크립트 프로토타입 기반 상속에 읽기 좋은 형식으로 바뀌었습니다. 이 클래스 문법이 자바스크립트에 새로운 객체 중심 상속 모델을 소개한 것은 아닙니다.
클래스 기반 언어에서는 클래스 정의를 통해 클래스 계층구조를 생성합니다. 클래스를 정의할 때 이미 존재하는 클래스의 하위 클래스를 새로운 클래스로 지정할 수 있습니다. 이 하위 클래스는 부모 클래스의 모든 속성을 상속받으며 추가로 새로운 속성을 추가하거나 상속받은 속성을 수정할 수 있습니다. 예를 들어 이름(name
)과 부서(dept
)을 가진 직원(Employee
) 클래스와 그 하위 클래스에 보고(reports
) 속성을 추가한 관리자(Manager
) 클래스가 있다고 해봅시다. 이 경우 관리자(Manager
)의 인스턴스는 다음과 같이 세가지 속성을 모두 가질 수 있습니다 - 이름(name
), 부서(dept
), 보고(reports
).
자바스크립트는 생성자 함수와 프로토타입 객체를 연결해 상속을 구현합니다. 이런 식으로 직원(Employee
) — 관리자(Manager
) 예제를 똑같이 구현할 수 있지만 조금 다른 * 사용합니다. 먼저, 이름(name
), 부서(dept
) 속성을 명시하여 직원(Employee
) 생성자 함수를 정의합니다. 그런 다음, 직원(Employee
)의 생성자를 호출한 후 보고(reports
) 속성을 명시해 관리자(Manager
) 생성자 함수를 정의합니다. 마지막으로 Employee.prototype
에서 파생된 새로운 객체를 관리자(Manager
) 생성자 함수의 프로토타입으로 지정합니다. 그런 다음 새로운 관리자(Manager
)를 만들면 관리자(Manager
) 객체를 직원(Employee
) 객체로부터 이름(name
), 부서(dept
) 속성을 상속받습니다.
클래스 기반의 언어들에서는, 일반적으로 컴파일 시점에 클래스를 생성한 후에 컴파일 시점 혹은 실행 시에 해당 클래스의 인스턴스를 생성합니다. 클래스가 한번 정의된 후에 클래스를 다시 컴파일 하지 않는다면, 속성의 갯수나 형식을 변경할 수 없습니다. 하지만 자바스크립트에서느 실행 시에 객체의 속성을 추가 혹은 삭제 할 수 있습니다. 만약 객체군의 프로토타입으로 사용되는 객체에 속성을 추가하면, 프로토타입이 되는 객체들에도 새로운 속성이 추가가 됩니다.
다음 표는 이런 차이점들에 대한 간략한 요약을 포함하고 있습니다. 이번 장의 다음 내용들은 객체의 계층 구조를 생성하기 위한 자바스크립트 생성자와 프로토타입들의 사용에 대한 세부 사항에 대해 기술합니다. 그리고 동일한 작업을 자바에서 어떻게 처리해야 하는지도 비교해서 살펴보겠습니다.
클래스 기반(자바) | 원형 기반(자바스크립트) |
---|---|
클래스와 인스턴스는 별개입니다. | 모든 객체는 다른 객체로부터 상속을 받습니다. |
클래스 정의를 가지고 클래스를 생성하고 생성자 메서드로 인스턴스를 생성합니다. | 생성자 함수를 가지고 객체군을 정의 및 생성합니다. |
new 연산자로 하나의 객체(인스턴스)를 생성합니다. | 동일합니다. |
이미 존재하는 클래스에 대한 하위 클래스를 정의함으로써 객체의 계층구조를 생성합니다. | 하나의 객체를 생성자 함수와 결합된 프로토타입에 할당함으로써 객체의 계층구조를 생성 합니다. |
클래스의 상속 구조에 따라 속성을 상속 받습니다. | 프로토타입 체인에 따라 속성을 상속 받습니다. |
클래스 정의는 모든 인스턴스의 모든 속성을 명시합니다. 실행시에 동적으로 속성을 추가할 수 없습니다. | 생성자 함수 혹은 프로토타입은 초기 속성들을 명시합니다. 개별 객체 혹은 전체 객체군에 동적으로 속성을 추가 삭제할 수 있습니다. |
이장의 나머지 부분에서는 다음과 같은 직원 객체의 계층구조를 사용합니다.
직원 객체의 계층 구조.
Employee
) 객체는 빈 문자열을 기본값으로 가지는 이름(name
) 그리고 "일반(general)"을 기본 값으로 가지는 부서(dept
)를 속성으로 가집니다.Manager
)객체는 직원(Employee
) 객체를 근간으로 하며, 직원 객체들을 포함하기 위한 빈 배열을 기본 값으로 하는 보고(reports
)속성을 가지고 있습니다.WorkerBee
)객체 또한 직원(Employee
) 객체를 근간으로 하며, 문자열들을 포함하기 위한 빈 배열을 기본 값으로 하는 프로젝트(projects
)속성을 가집니다.SalesPerson
)객체는 또한 근로자(WorkerBee
) 객체를 근간으로 하며, 100을 기본값으로 하는 할당량(quota
)를 속성으로 가집니다. 또한 같은 부서내의 모든 영업사원을 지칭하기 위한 부서 속성을 "판매부서"로 재정의 합니다. Engineer
)객체도 근로자(WorkerBee
) 객체를 근간으로 하며, 빈 문자열을 기본값으로 가지는 장비(machine
) 속성을 가집니다.그리고 엔지니어링(engineering)이라는 값으로 부서 속성을 재정의 합니다.직원 계층 구조를 구현하기 위한 적절한 생성자 함수를 정의하는 방법에는 여러가지가 있습니다. 개발하려고 하는 어플리케이션에 따라 생성자 함수를 정의 하는 방법은 달라질 수 있습니다.
이번 절에서는 상속을 구현하기 위한 간단한 (비교적 유연하지는 않은) 정의 방법을 보여 줄 것입니다. 이런 정의 방법을 사용하게되면, 객체를 생성할 때 어떤 속성 값도 지정을 할 수 없습니다. 새로이 생성된 객체들은 기본값들을 가지고 있으며, 나중에 해당 속성 값들을 변경할 수 있습니다.
실제 어플리케이션에서는, 객체를 생성할때, 해당 객체가 가져야할 속성을 인자로 받는 생성자를 정의 할수 있습니다.(보다 자세한 사항은 다음을 참조하세요. More flexible constructors). 지금 당장은, 상속이 어떻게 작동하는지를 보여주기 위한 간단한 예제를 사용합니다.
다음의 자바와 자바스크립트로 작성된 직원 정의는 비슷합니다. 차이점은 자바언어에서는 개별 속성에 대한 타입(type)을 일일이 지정을 해야 하지만 자바스크립트에서는 일일이 개별 속성에 대한 타입(type)을 지정할 필요가 없다는 것입니다.(이런 이유로 자바스크립트가 약하게 형식화된 언어로 불리는 반면 자바는 강력하게 형식화된 언어로 불립니다.)
자바스크립트function Employee() { this.name = ""; this.dept = "general"; } |
자바public class Employee { public String name = ""; public String dept = "general"; } |
관리자와 근로자 정의는 계층 구조상에서 상위에 위치하는 객체를 어떻게 표시하는지에 대한 차이점을 보여 줍니다. 자바스크립트에서는 생성자 함수 정의 이후에 언제든 생성자 함수의 프로토타입(prototype)
속성의 값으로 프로토타입 인스턴스를 추가할 수 있습니다. 자바에서는 클래스 정의에 상위 클래스를 명시해야 합니다. 클래스 정의 이후에는 상위 클래스를 변경할 수 없습니다.
function Manager() { Employee.call(this); this.reports = []; } Manager.prototype = Object.create(Employee.prototype); function WorkerBee() { Employee.call(this); this.projects = []; } WorkerBee.prototype = Object.create(Employee.prototype);
public class Manager extends Employee { public Employee[] reports = new Employee[0]; } public class WorkerBee extends Employee { public String[] projects = new String[0]; }
엔지니어와 영업사원 정의들은 객체들을 생성합니다. 생성된 객체는 근로자 객체의 하위 객체이고 따라서 직원 객체의 하위 객체가 됩니다. 상속 관계에 따라 엔지니어와 영업사원 객체들은 근로자와 직원객체의 속성을 가지게 됩니다. 게다가, 상속받은 부서 속성은 엔지니어와 영업사원에서 재정되어 새로운 값을 가지게 됩니다.
function SalesPerson() { WorkerBee.call(this); this.dept = "sales"; this.quota = 100; } SalesPerson.prototype = Object.create(WorkerBee.prototype); function Engineer() { WorkerBee.call(this); this.dept = "engineering"; this.machine = ""; } Engineer.prototype = Object.create(WorkerBee.prototype);
public class SalesPerson extends WorkerBee { public double quota; public dept = "sales"; public quota = 100.0; } public class Engineer extends WorkerBee { public String machine; public dept = "engineering"; public machine = ""; }
이런 정의 방법을 통해, 기본값을 가지는 각각의 속성을 포함하는 객체의 인스턴스를 생성할 수 있습니다. 다음 그림은 새로운 객체를 생성하고 새로운 객체에 대한 속성값들을 보여 표시하기 위한 자바스크립트의 정의들을 보여 줍니다.
유의사항: 클래스 기반 언어들에서 인스턴스라는 용어는 특정한 기술적 의미를 가지고 있습니다. 이러한 언어들에서, 하나의 인스턴스란 하나의 클래스의 개별적인 실체이며 클래스와는 근본적으로 다릅니다. 자바스크립트에서는 클래스와 인스턴스 간의 차이가 없기 때문에, "인스턴스"가 이런 기술적 의미를 갖지 않습니다. 하지만, 자바스크립트에 대해서 얘기하자면, 비공식적으로 "인스턴스"는 특정한 생성자 함수를 이용하여 생성된 오브젝트를 의미합니다. 그래서 이번 예제에서는 jane
이 Engineer
의 인스턴스라고 할 수 있습니다. 이와 유사하게, 부모, 자식, 상위, 하위의 용어들은 자바스크립트에서 공식적인 의미를 갖지 않습니다; 다만 프로토타입 체인 상의 상위 또는 하위 객체를 지칭하기 위해서 비공식적으로 사용할 수 있습니다.
오른쪽에 보이는 코드로 생성된 객체의 계층 구조는 아래와 같습니다.
var jim = new Employee; // jim.name is '' // jim.dept is 'general' var sally = new Manager; // sally.name is '' // sally.dept is 'general' // sally.reports is [] var mark = new WorkerBee; // mark.name is '' // mark.dept is 'general' // mark.projects is [] var fred = new SalesPerson; // fred.name is '' // fred.dept is 'sales' // fred.projects is [] // fred.quota is 100 var jane = new Engineer; // jane.name is '' // jane.dept is 'engineering' // jane.projects is [] // jane.machine is ''
이번 장에서는 객체가 프로토타입체인 상의 다른 객체로부터 특성을 상속받는 방법과 런타임 상에서 프로퍼티를 추가하면 무슨 일이 일어나는 지 살펴봅니다.
아래 구문처럼 WorkerBee
생성자로 mark
객체를 생성했다고 가정합니다.
var mark = new WorkerBee;
new 연산자를 만나면, 자바스크립트는 새로운 일반 객체를 생성하고 암묵적으로 내부의 [[Prototype]](__proto__
) 속성의 값을 WorkerBee.prototype
의 값으로 할당하며, 해당 객체를 this
키워드의 값으로써 생성자 함수 WorkerBee
에 전달합니다. 내부의 [[Prototype]] 속성은 속성값을 반환하기 위해 사용할 프로토타입 체인을 결정합니다. 이런 속성들이 할당되면, 자바스크립트는 새 객체를 반환하고, 할당 구문에 의해 변수 mark
를 객체에 할당합니다.
이러한 절차는 mark
가 프로토타입 체인으로부터 상속받는 속성의 값을 mark
객체 내부에(local values) 명시적으로 부여하진 않습니다. 당신이 속성의 값을 요청하면, 자바스크립트는 먼저 해당 객체에 값이 존재하는지 확인합니다. 존재한다면, 해당 값이 반환됩니다. 만약 해당 객체에 값이 없다면, 프로토타입 체인을 (내장 [[Prototype]] 속성;__proto__
을 이용하여)확인합니다. 체인 상의 어떤 객체가 해당 속성의 값을 가지고 있다면 그 값이 반환됩니다. 그런 속성이 발견되지 않는다면, 자바스크립트는 객체가 속성을 가지고있지 않다고 할 것입니다. 이런 식으로, mark
객체는 다음의 속성과 값을 가집니다.
mark.name = ""; mark.dept = "general"; mark.projects = [];
mark
객체는 mark.__proto__
로 연결되어 있는 원형의 객체로부터 이름(name
)과 부서(dept
)에 대한 값을 상속 받습니다. 근로자(WorkerBee
) 생성자로부터 projects
속성에 대한 값을 할당을 받습니다.이것들이 자바스크립트내에서 속성과 속성 값의 상속입니다. 이런 과정의 몇몇 세부 사항들은 Property inheritance revisited에서 다룹니다.
이런 생성자들은 당신이 직접 인스턴스에만 해당 하는 값을 설정하도록 하지 않기때문에, 객체에 대한 이런 정보들은 일반적으로 적용됩니다. 근로자(WorkerBee
)로부터 생성된 모든 새로운 객체들은 기본값이 적용된 속성 값들을 가지게 됩니다. 물론, 속성 값들을 변경할 수 있습니다. 아래처럼 특정 인스턴스에만 해당하는 값을 설정할 수 있습니다.
mark.name = "Doe, Mark"; mark.dept = "admin"; mark.projects = ["navigator"];
자바스크립트에선, 실행 시점에 특정 객체에 속성들을 추가 할 수 있습니다.생성자 함수가 제공하는 속성외에 다른 속성을 추가할 수 있습니다. 특정 단일 객체에 속성을 추가하기 위해선, 다음과 같이 해당 객체에 값을 할당 하면 됩니다:
mark.bonus = 3000;
이렇게 하면 mark
객체는 보너스(bonus
)속성을 가지게 됩니다. 하지만 mark객체를 제외한 나머지 근로자(WorkerBee)
객체들은 보너스 속성을 가지지 않습니다.
만약 생성자 함수의 원형으로 사용되는 객체에 새로운 속성을 추가한다면, 해당 프로토타입 객체(prototype)의 속성을 상속받는 모든 객체에 해당 속성이 추가됩니다. 예를 들면, 전문분야(specialty
)속성을 모든 직원 객체에 다음과 같은 구문으로 추가할 수 있습니다:
Employee.prototype.specialty = "none";
위의 구문을 실행한 직후, mark
객체는 "none"
이라는 값을 가지는 전문분야(specialty)
속성을 가지게 됩니다.아래의 그림들은 해당 속성을 추가한 후 엔지니어(Engineer)
프로토타입에 대해 해당 속성을 재정의 했을 경우 각 객체에 미치는 영향을 보여줍니다.
속성의 추가
지금까지 살펴 본 생성자 함수들은 인스턴스를 생성하면서 동시에 속성값을 지정할 수 없었습니다. 자바의 경우, 인스턴스를 생성 시 생성자에 인자들을 넘겨주어 인스턴스의 속성들을 초기화 할 수 있습니다. 다음의 예제 그림들은 자바처럼 인스턴스 생성 시 속성값을 설정하는 방법을 보여줍니다.
Specifying properties in a constructor, take 1
다음의 표는 자바와 자바스크립트 각각의 생성자와 객체에 대한 정의를 보여 줍니다.
function Employee (name, dept) { this.name = name || ""; this.dept = dept || "general"; }
public class Employee { public String name; public String dept; public Employee () { this("", "general"); } public Employee (String name) { this(name, "general"); } public Employee (String name, String dept) { this.name = name; this.dept = dept; } }
function WorkerBee (projs) { this.projects = projs || []; } WorkerBee.prototype = new Employee;
public class WorkerBee extends Employee { public String[] projects; public WorkerBee () { this(new String[0]); } public WorkerBee (String[] projs) { projects = projs; } }
function Engineer (mach) { this.dept = "engineering"; this.machine = mach || ""; } Engineer.prototype = new WorkerBee;
public class Engineer extends WorkerBee { public String machine; public Engineer () { dept = "engineering"; machine = ""; } public Engineer (String mach) { dept = "engineering"; machine = mach; } }
자바스크립트의 속성값을 설정하는 방법은 기본값을 설정하기 위한 관용구를 사용합니다.
this.name = name || "";
자바스크립트의 OR 논리 연산자(||
) 첫번째 인자를 평가합니다. 첫번째 인자가 참이면 첫번째 인자를 반환하고 그렇지 않은 경우는 두번째 인자를 반환합니다. 그러므로, 위의 코드는 name
인자가 name
속성에 사용 가능한 값을 가지고 있는지 확인합니다. 확인 결과 name
속성에 사용가능한 값을 가지고 있을 경우, 해당 값을 this.name
에 설정하게 됩니다. 반대로 그렇지 않은 경우는 빈 문자열을 this.name
에 설정합니다.얼핏 보면 헛갈리지만 보다 짧은 관용구를 사용하였습니다.
주의: 만약 인자로 false와 빈 문자열 값을 줄 경우 해당 구문은 예상한 대로 작동하지 않을 수 있습니다.
이런 정의들을 가지고, 객체의 인스턴스를 생성할때, 객체 자신만의 속성에 대한 값을 지정할 수 있습니다. 새로운 Engineer
를 생성하기 위해서 다음과 같은 구문을 사용할 수 있습니다:
var jane = new Engineer("belau");
Jane
의 속성들은 다음과 같습니다:
jane.name == ""; jane.dept == "engineering"; jane.projects == []; jane.machine == "belau"
위와 같은 코드 구문으로는 name
과 같이 상속받은 속성에 대한 초기값을 지정할 수 없습니다.만약 상속 받은 속성의 초기값을 설정하고자 한다면, 생성자 함수의 코드를 변경해야 합니다.
지금까지, 원형 객체를 생성한 후, 그 새로운 객체 자신의 속성과 속성 값을 지정하는 것을 살펴 보았습니다. 프로토타입 체인상에서 해당 생성자가 상위 객체에 대한 생성자를 직접 호출 함으로써 더 많은 속성을 추가하도록 할 수 있습니다. 다음의 그림은 새로운 정의 방법을 보여 줍니다.
생성자내에서 속성들 정의, 그림 2
그럼 좀 더 상세하게 생성자 내에서 속성들을 정의하는 것을 살펴 보겠습니다. 다음은 엔지니어(Engineer)
생성자의 새로운 정의입니다:
function Engineer (name, projs, mach) { this.base = WorkerBee; this.base(name, "engineering", projs); this.machine = mach || ""; }
다음과 같이 새로운 엔지니어(Engineer)
객체를 생성할 수 있습니다:
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
다음과 같은 순서에 따라 객체를 생성하고 속성을 정의하게 됩니다:
new
연산자는 프로토타입 객체를 생성하고 생성된 객체의 __proto__
속성을 Engineer.prototype
으로 설정합니다.new
연산자는 새로이 생성된 객체를 엔지니어(Engineer)
생성자에 this 키워드의 값으로 전달합니다.base
라는 새로운 속성을 생성하고 근로자(WorkerBee)
생성자의 값을 base 속성에 할당합니다. 이런 과정은 근로자(WorkerBee)
생성자를 엔지니어(Engineer)
객체의 메서드로 만듭니다. base
속성의 이름은 그리 특별하지 않습니다. 다른 어떤 속성명을 사용해도 무방합니다. base
속성명은 단지 해당 속성의 목적을 환기시키기 위한 것입니다.생성자는 base
메서드에 필요한 인자들 ("Doe, Jane"
and ["navigator", "javascript"]
)을 주어 호출합니다.명시적으로 생성자에서 사용한 "engineering"
은 모든 엔지니어(Engineer)
객체들이 상속받은 부서 속성에 대한 동일한 값을 가지며, 직원(Employee)
으로부터 상속받은 값을 재정의 하는 것을 나타냅니다.
base
가 엔지니어(Engineer)
의 메서드이기때문에 base
메서드 내에서 this
키워드를 스텝1에서 생성한 객체를 지칭하도록 해줍니다. 따라서, 근로자(WorkerBee)
함수는 차례대로 "Doe, Jane"
과 "engineering"
인자를 직원(Employee)
생성자에 전달합니다. 직원(Employee)
생성자로부터 반환 시, 근로자(WorkerBee)
함수는 남은 인자들을 프로젝트(projects)
속성을 설정하기 위해 사용합니다. base
메서드로부터 반환 시, 엔지니어(Engineer)
생성자는 해당 객체의 장비(machine)
속성을 "belau"로 초기화 합니다.jane
변수에 할당 합니다.엔지니어(Engineer
) 생성자내에서 근로자(WorkerBee
)생성자를 호출하면, 엔지니어(Engineer
)에 대한 상송을 적절하게 설정할 수 도 있을 것이라고 생각할 수 있을 것입니다.하지만 그렇지 않습니다. 근로자(WorkerBee
)생성자를 호출하는 것은 엔지니어(Engineer)
객체로 하여금 호출되는 모든 생성자 함수내에서 열거된 속성들을 가지고도록 보장합니다.그러나, 나중에 직원(Employee)
혹은 근로자(WorkerBee)
원형에 속성을 추가한다면, 엔지니어(Engineer)
객체에 의해 추가된 속성들은 상속이 되지 않습니다. 예를 들어, 아래와 같은 구문을 작성하였다고 가정합니다:
function Engineer (name, projs, mach) { this.base = WorkerBee; this.base(name, "engineering", projs); this.machine = mach || ""; } var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau"); Employee.prototype.specialty = "none";
jane
객체는 전문분야(specialty)
속성을 상속받지 않습니다.
function Engineer (name, projs, mach) { this.base = WorkerBee; this.base(name, "engineering", projs); this.machine = mach || ""; } Engineer.prototype = new WorkerBee; var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau"); Employee.prototype.specialty = "none";
이제 jane
객체의 전문분야(specialty)
속성은 "none"이 되었습니다.
call()
혹은 apply()
메서드를 사용는 것은 상속을 구현하는 또 다른 방법입니다. 다음의 두 예제는 동일한 결과를 보여줍니다.
function Engineer (name, projs, mach) { this.base = WorkerBee; this.base(name, "engineering", projs); this.machine = mach || ""; }
function Engineer (name, projs, mach) { WorkerBee.call(this, name, "engineering", projs); this.machine = mach || ""; }
base
를 사용하지 않고 구현을 하기 때문에, call()
메서드를 사용하여 상속을 구현하는 것이 보다 깔끔합니다.
이전 절에서 자바스크립트의 생성자와 원형(prototype)이 어떤 방식으로 상속과 객체의 계층 구조를 제공하는지를 살펴 보았습니다. 이번장에서는 이전 절에서 반드시 명백하게 짚고 넘어가지 않은 일부 미묘한 점들에 대해 살펴보겠습니다.
이번 장에서 이미 설명된 것 처럼, 객체의 속성에 접근할 때, 자바스크립트는 아래와 같은 절차를 따릅니다.
__proto__
속성을 사용하여 프로토타입 체인을 확인한다.이런 단계들의 결과는 생성자 및 프로토타입 체인등의 것들을 어떻게 정의 하느냐에 따라 달라집니다. 아래와 같은 원래의 예제는 이런 정의들을 가지고 있습니다:
function Employee () { this.name = ""; this.dept = "general"; } function WorkerBee () { this.projects = []; } WorkerBee.prototype = new Employee;
이런 정의들을 가지고, amy
라는 근로자(WorkerBee)
인스턴스를 아래와 같이 생성하였다고 가정합니다.
var amy = new WorkerBee;
amy
객체는 프로젝트라는 자신만의 속성을 가집니다.이름과 부서 속성들은 amy
자신의 속성이 아닌 amy
객체의 __proto__
속성을 통해 가지고 온 속성들입니다. 따라서 amy
는 이런 속성들의 값을 가지게 됩니다.
amy.name == ""; amy.dept == "general"; amy.projects == [];
직원(Employee)
과 연관되어 있는 프로토타입내의 이름 속성의 값을 아래와 같이 변경하였다고 가정합니다.
Employee.prototype.name = "Unknown"
얼핏보기에, 새로운 값이 모든 직원 인스턴스에 적용이 될것으로 예상하겠지만 그렇지 않습니다.
직원 객체의 인스턴스를 생성할때, 해당 인스턴스는 이름 속성에 대해 자신이 가지고 있는 값(빈 문자열)을 취하게 됩니다.이것이 의미하는 것은 새로운 직원 객체를 생성하여 근로자(WorkerBee)
의 프로토타입에 설정을 할때, WorkerBee.prototype
이 이름 속성에 대한 자신만의 값을 가지고 있다는 것입니다.그러므로, amy
객체(근로자 인스턴스)의 이름 속성에 대해 검색할때, WorkerBee.prototype
내에서 이름 속성에 대한 amy
객체 자신의 값을 찾게됩니다. 그렇기때문에 Employee.prototype
까지의 프로토타입 체인을 검색하지 않게 됩니다.
실행시에 객체의 속성 값을 변경하고 새로운 값이 모든 하위 객체들에게도 적용되도록 할려면, 객체의 생성자함수에서는 속성을 정의할 수 없습니다. 대신에, 생성자와 연결된 프로토타입에 추가할 수 있습니다. 예를 들어, 이전의 코드를 아래와 같이 변경하였다고 가정합니다:
function Employee () { this.dept = "general"; } Employee.prototype.name = ""; function WorkerBee () { this.projects = []; } WorkerBee.prototype = new Employee; var amy = new WorkerBee; Employee.prototype.name = "Unknown";
이 경우 amy
객체의 이름 속성의 값은 "Unknown"이 됩니다.
위의 예제에서처럼, 객체 생성 시에 객체의 속성에 대한 기본 값을 설정하고 실행 시에 해당 속성의 값을 변경하기를 원한다면, 해당 속성들을 생성자 함수 자체안에가 아닌 생성자의 프로토타입에 설정 하여야 합니다.
자바스크립트에서의 속성 검색은 객체 자신의 속성들을 먼저 살펴보고 해당 속성명을 찾지 못할 경우, 객체의 특별한 속성인 __proto__
내에서 찾게 됩니다. 이런 검색은 재귀적으로 진행되며, 이런 과정을 "프로토타입 체인에서의 검색"이라고 합니다.
특별한 속성인 __proto__
객체가 생성이 될때 설정이 됩니다. __proto__
속성은 생성자의 프로토타입 속성의 값으로 설정이 됩니다. 따라서 new Foo()
표현식은 __proto__ ==
Foo.prototype
인
객체를 생성합니다. 결과적으로 Foo.prototype
의 속성들에 대한 변경은
new Foo()
표현식으로 생성한 모든 객체에 대한 속성 검색을 변경하게 됩니다.
모든 객체는 __proto__
라는 객체 속성을 가집니다.(예외: Object
). 모든 함수들은 prototype
이라는 객체 속성을 가집니다. 따라서 객체들은 '프로토타입 상속'에 의해 다른 객체들과의 관계를 가지게 됩니다.객체의 __proto__
속성과 함수의 prototype
객체를 비교하여 상속을 테스트 해볼 수 있습니다. 자바스크립트는 특정 객체가 함수 prototype
으로부터 상속 받는 객체일 경우 참(true)
를 반환하는 instanceof
라는 연산자를 제공합니다. 예를 들면,
var f = new Foo(); var isTrue = (f instanceof Foo);
Inheriting properties에 나오는 예제와 동일한 정의들을 작성해 놓았을 경우, 보다 상세한 예제는 아래와 같습니다.엔지니어(Engineer)
객체를 아래와 같이 생성합니다:
var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");
생성된 객체를 가지고 아래와 같은 구문을 실행할 경우 각 구문에 대한 결과는 모두 참(true)입니다.
chris.__proto__ == Engineer.prototype; chris.__proto__.__proto__ == WorkerBee.prototype; chris.__proto__.__proto__.__proto__ == Employee.prototype; chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype; chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;
주어진 이런 상황에서, instanceOf
를 다음과 같이 직접 작성할 수 있을 것입니다:
function instanceOf(object, constructor) { object = object.__proto__; while (object != null) { if (object == constructor.prototype) return true; if (typeof object == 'xml') { return constructor.prototype == XML.prototype; } object = object.__proto__; } return false; }
주의: 위의 구현내용은 최신 버전의 자바스크립트에서 XML객체들이 표현되는 방법의 특질을 해결하기 위해 해당 객체의 타입이 "xml"인지 확인합니다. 핵심적인 세부 사항을 확인하려면 {{ bug(634150) }}를 참조하세요.
위의 instanceOf함수를 사용하면 아래의 표현들은 모두 참(true)입니다:
instanceOf (chris, Engineer) instanceOf (chris, WorkerBee) instanceOf (chris, Employee) instanceOf (chris, Object)
하지만 아래의 표현식은 거짓(false)가 됩니다:
instanceOf (chris, SalesPerson)
생성자를 생성할때, 생성자내에서 전역 정보를 설정할 경우, 주의를 해야 합니다. 예를 들어, 각각의 새로운 직원에게 자동으로 고유한 ID값을 할당하기를 원한다고 했을 때, 다음과 같은 직원(Employee
) 정의를 사용할 수 있을 것입니다:
var idCounter = 1; function Employee (name, dept) { this.name = name || ""; this.dept = dept || "general"; this.id = idCounter++; }
이런 정의 내용을 가지고, 새로운 직원을 생성했을 때, 생성자는 다음의 고유한 ID값을 새로운 직원객체에 할당하고 전역 ID 카운터를 증가 시킵니다. 따라서 다음과 같은 구문으로 각각의 객체를 생성한다면, 결과는 victoria.id
는 1 그리고 harry.id
는 2가 됩니다:
var victoria = new Employee("Pigbert, Victoria", "pubs") var harry = new Employee("Tschopik, Harry", "sales")
얼핏보면 괜찮아 보입니다. 하지만 이유를 불문하고 직원 객체가 생성될때마다 idCounter
는 증가분을 가지게 됩니다.이번장에서 나온 예제에서처럼 전체 직원(Employee)
객체의 계층 구조를 생성하였다면, 프로토타입을 설정할때마다 직원 생성자는 매번 호출 됩니다.다음과 같은 코드를 작성하였다고 가정합니다:
var idCounter = 1; function Employee (name, dept) { this.name = name || ""; this.dept = dept || "general"; this.id = idCounter++; } function Manager (name, dept, reports) {...} Manager.prototype = new Employee; function WorkerBee (name, dept, projs) {...} WorkerBee.prototype = new Employee; function Engineer (name, projs, mach) {...} Engineer.prototype = new WorkerBee; function SalesPerson (name, projs, quota) {...} SalesPerson.prototype = new WorkerBee; var mac = new Engineer("Wood, Mac");
여기서 생략된 정의가 base
속성을 가지고 해당 생성자를 프로토타입 체인내의 상위 생성자들을 호출한닥고 좀 더 가정하면, 이런 경우, 생성된 mac
객체의 id
값은 5가 됩니다.
어플리케이셔네 따라, 카운터가 이렇게 추가적으로 증가된 것은 문제가 될 수도 그렇지 않을 수 도 있습니다. 카운터에 정확한 값이 설정되기를 원한다면, 사용가능한 해결적은 아래와 같은 생성자를 대신 사용하는 것입니다:
function Employee (name, dept) { this.name = name || ""; this.dept = dept || "general"; if (name) this.id = idCounter++; }
prototype으로 사용할 직원 인스턴스를 생성할 때, 생성자에 인자들을 주어선 안됩니다. 이 생성자 정의를 사용하여, 인자들을 주지 않을 경우, 생성자는 id에 값을 할당하지 않으며 카운터를 갱신하지 않습니다. 따라서, id값을 가지는 직원 객체에 대해, 반드시 해당 직원의 이름을 명시해야 합니다. 이 예제의 경우 mac
인스턴스의 id
값은 1이 됩니다.
몇몇 객체 지향언어들은 다중 상속을 허용합니다. 그것은, 관련이 없는 부모 객체들로 부터 속성들과 값들을 상속 받을 수 있는 것을 말합니다. 자바스크립트는 다중 상속을 지원하지 않습니다.
속성 값의 상속은 속성에 대한 값을 찾기 위한 프로토타입 체인을 검색에 의해 실행 시점에 이루어 집니다. 하나의 객체는 오로지 하나의 결합된 prototype만을 가지기 때문에, 자바스크립트는 동적으로 하나 이상의 프로토타입 체인으로 부터 상속을 할 수 없습니다.
자바스크립트에서, 하나 이상의 다른 생성자 함수를 호출하는 생성자를 사용할 수 있습니다. 이것은 다중 상속처럼 보여질 수 있습니다. 예를 들어, 다음과 같은 구문들을 살펴보세요:
function Hobbyist (hobby) { this.hobby = hobby || "scuba"; } function Engineer (name, projs, mach, hobby) { this.base1 = WorkerBee; this.base1(name, "engineering", projs); this.base2 = Hobbyist; this.base2(hobby); this.machine = mach || ""; } Engineer.prototype = new WorkerBee; var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")
WorkerBee
의 정의는 이번 장의 이전에 사용된 것과 동일하다고 가정합니다.이런 경우, dennis
객체는 다음과 같은 속성들을 가지게 됩니다:
dennis.name == "Doe, Dennis" dennis.dept == "engineering" dennis.projects == ["collabra"] dennis.machine == "hugo" dennis.hobby == "scuba"
따라서 dennis
객체는 Hobbyist
생성자로부터 취미(hobby)
속성을 받아 오지 않습니다. 그런데 Hobbyist
생성자의 프로토타입에 속성을 추가 했다고 가정하면
Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]
dennis
객체는 새로이 추가된 속성을 상속받지 않습니다.