이 글은 책을 정리한 내용입니다.
ECMA-262는 객체를 ‘프로퍼티의 순서 없는 컬렉션이며 각 프로퍼티는 원시 값이나 객체, 메서드를 포함한다.’고 정의합니다.
이 책의 저자는 자바스크립트의 객체를 해시 테이블에 비유해서 설명했으며 그게 가장 잘 설명한 것 같습니다.
제약사항
ECMAScript 5판의 메서드는 인터넷 익스플로러 9 이상, 파이어폭스 4 이상, 사파리 5 이상, 오페라 12이상, 크롬에서 사용가능합니다.
객체에 대한 이해
자바스크립트에서는 클래스라는 개념이 없습니다.
그래서 function으로 객체를 만드는 특이한 방법을 사용합니다.
// name, age, job을 프로퍼티로 가지고 sayName이라는 메서드를 가지고 있는 객체
var person = new Object ();
person . name = "Nicholas" ;
person . age = 29 ;
person . job = "Software Engineer" ;
person . sayName = function () {
console . log ( this . name );
};
// 위와 동일합니다.
var person = {
name : "Nicholas" ,
age : 29 ,
job : "Software Engineer" ,
sayName : function () {
console . log ( this . name );
}
}
이 예제에서의 메서드도 객체의 프로퍼티 중 일부입니다.
프로퍼티 속성
ECMA-262 5판에서는 프로퍼티의 특징을 내부적으로만 유효한 속성에 따라 설명합니다.
이를 기본 프로퍼티라고 부르고 [[Enumberable]]처럼 대괄호 두개로 감쌉니다.
사용되는 메서드
- ECMAScript5판 Object.defineProperty(): 기본 프로퍼티의 속성을 변경함
이 메소드의 기본값은 false이므로 주의하여 사용해야 합니다.
var person = {};
Object . defineProperty ( person , // 프로퍼티 추가 혹은 수정할 객체
"name" , // 프로퍼티 이름
{ // 서술자
Writable : false , // 기본값이 false여서, [[Configurable]]도 false가 됨
value : "Nicholas"
});
console . log ( person . name ); // Nicholas
person . name = "Greg" ;
console . log ( person . name ); // Nicholas
[[Writable]]이 false이면 무시됨. 단, strict모드에서는 에러
[[Configurable]] 속성이 false일 때
var person = {};
Object . defineProperty ( person ,
"name" ,
{
Configurable : false ,
value : "Nicholas"
});
console . log ( person . name ); // Nicholas
delete person . name ;
console . log ( person . name ); // Nicholas
[[Configurable]]이 false이면 delete도 무시됨. 단, strict 모드에서는 에러.
[[Configurable]]이 false이면 [[Writable]]만 수정가능.
[[Configurable]]이 false일 때 Ver.2
var person = {};
Object . defineProperty ( person ,
"name" ,
{
Configurable : false ,
value : "Nicholas"
});
// 에러 발생
Object . defineProperty ( person ,
"name" ,
{
Configurable : true ,
value : "Nicholas"
});
데이터 프로퍼티
[[Configurable]] - 프로퍼티가 delete로 삭제하거나 속성변경, 접근자 프로퍼티로 변환할 수 있음을 나타냅니다.
[[Enumerable]] - for-in 루프에서 해당 프로퍼티를 반환할 수 있음을 나타냅니다.
[[Writable]] - 프로퍼티의 값을 변경할 수 있음을 나타냅니다.
[[Value]] - 프로퍼티의 실제 데이터 값이나 위치를 나타냅니다. 기본값은 undefined입니다.
접근자 프로퍼티
var book = {
_year : 2004 , // _가 앞에 있으면 객체 메서드를 통해서만 접근할 것이라는 의도를 나타내는 표기법
edition : 1
};
Object . defineProperty ( book ,
"year" ,
{
get : function () {
return this . _year ;
},
set : function ( newValue ) {
this . _year = newValue ;
this . edition += newValue - 2004 ;
}
});
book . year = 2005 ;
console . log ( book . edition ); // 2
사용되는 메서드
- ECMAScript 5판 이전의 비표준 defineGetter (), defineSetter (): getter와 setter편집
var book = {
_year : 2004 ,
edition : 1
}
book . __defineGetter__ ( "year" , function () {
return this . _year ;
});
book . __defineSetter__ ( "year" , function ( newValue ) {
this . _year = newValue ;
this . edition += newValue - 2004 ;
});
book . year = 2005 ;
console . log ( book . edition ); // 2
getter와 setter는 필수는 아닙니다. 만약 없다면 해당 메서드호출시에 무시되고 스트릭트 모드에서는 에러가 발생합니다.
사용되는 메서드
- ECMAScript 5판 Object.defineProperties(): 다중 프로퍼티 지원 메서드
- ECMAScript 5판 Object.getOwnPropertyDescriptor(): 프로퍼티 속성 반환 메서드
var book = {};
Object . defineProperties ( book , {
_year : {
value : 2004
},
edition : {
value : 1
},
year : {
get : function () {
return this . _year ;
},
set : function ( newValue ) {
this . _year = newValue ;
this . edition += newValue - 2004 ;
}
}
});
var descriptor = Object . getOwnPropertyDescriptor ( book , "_year" );
console . log ( descriptor . value ); // 2004
console . log ( descriptor . configurable ); // false
console . log ( typeof descriptor . get ); // undefined
var descriptor = Object . getOwnPropertyDescriptor ( book , "year" );
console . log ( descriptor . value ); // undefined
console . log ( descriptor . configurable ); // false
console . log ( typeof descriptor . get ); // function
객체 생성
팩토리 패턴
목적: Object생성시 중복된 코드를 줄이기
방법: 객체 생성과정을 추상화
function createPerson ( name , age , job ) {
var o = new Object ();
o . name = name ;
o . age = age ;
o . job = job ;
o . sayName = function () {
console . log ( this . name );
};
return o ;
}
// new 연산자를 사용하지 않습니다.
var person1 = createPerson ( "Nicholas" , 29 , "Software Engineer" );
var person2 = createPerson ( "Greg" , 27 , "Doctor" );
문제점: 생성한 객체가 어떤 타입인지 알 수 없다는 문제점이 있음
생성자 패턴
목적: 위의 문제점을 해결
방법: 커스텀 생성자를 만들어서 원하는 타입의 객체에 필요한 프로퍼티와 메서드 정의
function Person ( name , age , job ) {
this . name = name ;
this . age = age ;
this . job = job ;
this . sayName = function () {
console . log ( this . name );
};
}
var person1 = new Person ( "Nicholas" , 29 , "Software Engineer" );
var person2 = new Person ( "Greg" , 27 , "Doctor" );
console . log ( person1 . constructor === Person ); // true
console . log ( person2 . constructor === Person ); // true
console . log ( person1 instanceof Object ); // true
console . log ( person1 instanceof Person ); // true
console . log ( person2 instanceof Object ); // true
console . log ( person2 instanceof Person ); // true
console . log ( person1 . sayName == person2 . sayName ); // false - 인스턴스마다 새 메서드 생성
Person메서드의 특징
명시적으로 객체를 생성하지 않음
프로퍼티와 메서드는 this 객체에 직접적으로 할당
return문이 없음
메서드의 첫글자가 대문자(생성자 함수 표기법)
new 연산자 호출시 순서도
객체를 생성
생성자의 this값에 새 객체를 할당, this는 새 객체를 가리킴
생성자 내부코드 실행
새 객체 반환
생성자 함수와 다른함수의 차이는 new 연산자와 함께 호출하는 것 입니다.
var person = new Person ( "Nicholas" , 29 , "Software Engineer" );
person . sayName (); // Nicholas
Person ( "Greg" , 27 , "Doctor" ); // window에 추가
window . sayName (); // Greg
var o = new Object ();
Person . call ( o , "Kristen" , 25 , "Nurse" );
o . sayName (); // Kristen
문제점: 인스턴스마다 메서드가 생성됨
function Person ( name , age , job ) {
this . name = name ;
this . age = age ;
this . job = job ;
this . sayName = new Function ( "console.log(this.name)" ); // 논리적으로 동등
}
// 우회방법
function Person ( name , age , job ) {
this . name = name ;
this . age = age ;
this . job = job ;
this . sayName = sayName ;
}
function sayName () {
console . log ( this . name );
}
var person1 = new Person ( "Nicholas" , 29 , "Software Engineer" );
var person2 = new Person ( "Greg" , 27 , "Doctor" );
console . log ( person1 . sayName == person2 . sayName ); // true
console . log ( person1 . sayName ()); // Nicholas
우회방법의 문제점: 일부 객체에서만 쓰는 함수를 전역에 놓음으로써 전역 스코프를 어지럽힘
프로토타입 패턴
목적: 위의 문제점 해결
방법: 모든 함수에서 가지고 있는 prototype 프로퍼티 사용
function Person () {
}
Person . prototype . name = "Nicholas" ;
Person . prototype . age = 29 ;
Person . prototype . job = "Software Engineer" ;
Person . prototype . sayName = function () {
console . log ( this . name );
};
var person1 = new Person ();
person1 . sayName (); // Nicholas
var person2 = new Person ();
person2 . sayName (); // Nicholas
console . log ( person1 . sayName === person2 . sayName ); // true
console . log ( Person . prototype . isPrototypeOf ( person1 )); // true
console . log ( Person . prototype . isPrototypeOf ( person2 )); // true
// getPrototypeOf는 ECMAScript 5판 스펙
console . log ( Object . getPrototypeOf ( person1 ) == Person . prototype ); // true
console . log ( Object . getPrototypeOf ( person1 ). name ); // Nicholas
프로토타입 동작
모든 프로토타입은 자동으로 constructor 프로퍼티를 가짐 - 소속된 함수를 가리킴
커스텀 생성자를 호출하여 인스턴스를 만들면 인스턴스 내부에 [[prototype]] 포인터가 생성됩니다.
[[prototype]]에 접근하는 표준은 없지만 파이어폭스, 사파리, 크롬에서는 __proto__라는 프로퍼티를 지원합니다.
검색을 할 때 객체 인스턴스에서 찾지못하면 프로토타입에서 검색합니다.
즉, 생성자로 만들어진 객체들은 같은 prototype을 공유하고, 프로퍼티와 메서드를 공유합니다.
사용되는 메서드
- hasOwnProperty: 프로퍼티가 어디에 존재하는지 확인
- hasPrototype: 책에서 소개된 이 메서드는 object의 인스턴스 혹은 메서드의 caller에 따라서 다르게 동작할 수 있습니다.(일반적이지 않음)
function Person () {
}
Person . prototype . name = "Nicholas" ;
Person . prototype . age = 29 ;
Person . prototype . job = "Software Engineer" ;
Person . prototype . sayName = function () {
console . log ( this . name );
};
var person1 = new Person ();
var person2 = new Person ();
console . log ( person1 . hasOwnProperty ( "name" )); // false
console . log ( "name" in person1 ); // true
console . log ( "toString" in person1 ); // true
person1 . name = "Greg" ;
console . log ( person1 . name ); // Greg - 인스턴스에서
console . log ( person1 . hasOwnProperty ( "name" )); // true
//console.log(person1.prototype.hasOwnProperty("name")); // 에러발생
a
console . log ( "name" in person1 ); // true
console . log ( person2 . name ); // Nicholas - 프로토타입에서
console . log ( person2 . hasOwnProperty ( "name" )); // false
console . log ( "name" in person2 ); // true
delete person1 . name ;
console . log ( person1 . name ); // Nicholas - 프로토타입에서 검색
console . log ( person1 . hasOwnProperty ( "name" )); // false
console . log ( "name" in person1 ); // true
in 연산자: for in구문이 아닌 자체 in이 사용되면 인스턴스에 존재하든 프로토타입에 존재하든 모두 true를 반환합니다.
- in 연산자의 경우 스코프 체인을 따라가면서 프로퍼티가 존재하는지 확인
- 인스턴스에서 프로토타입에 [[Enumerable]] 속성이 false인 프로퍼티를 덮어쓰면 in 루프에서 찾을 수 있습니다.(IE8 이전에서는 버그)
var o = {
toString : function () {
return "My Object" ;
}
};
for ( var prop in o ) {
if ( prop == "toString" ) {
console . log ( "Found toString" );
}
}
사용되는 메서드
- ECMAScript 5판 Object.keys(): 객체 인스턴스에서 나열가능한 프로퍼티 전체 목록을 가져옴
- ECMAScript 5판 Object.getOwnPropertyNames(): [[Enumerable]] 속성 상관없이 목록을 얻을 수 있는 메서드
function Person () {
}
Person . prototype . name = "Nicholas" ;
Person . prototype . age = 29 ;
Person . prototype . job = "Software Engineer" ;
Person . prototype . sayName = function () {
console . log ( this . name );
};
var keys = Object . keys ( Person . prototype );
console . log ( keys ); // name,age,job,sayName
var p1 = new Person ();
p1 . name = "Rob" ;
p1 . age = 31 ;
var p1keys = Object . keys ( p1 );
console . log ( p1keys ); // name, age
var keys = Object . getOwnPropertyNames ( Person . prototype );
console . log ( keys ); // constructor,name,age,job,sayName
프로토타입의 대체 문법
function Person () {
}
Person . prototype = {
name : "Nicholas" ,
age : 29 ,
job : "Software Engineer" ,
sayName : function () {
console . log ( this . name );
}
};
var friend = new Person ();
console . log ( friend instanceof Object ); // true
console . log ( friend instanceof Person ); // true
console . log ( friend . constructor == Person ); // false
console . log ( friend . constructor == Object ); // true
constructor가 사라지는 문제발생
Person . prototype = {
constructor : Person , // [[Enumerable]] 속성이 true가 됨 - 이걸 해결하려면 ECMAScript5판의 defineProperty로 해결가능
name : "Nicholas" ,
age : 29 ,
job : "Software Engineer" ,
sayName : function () {
console . log ( this . name );
}
};
var friend = new Person ();
console . log ( friend instanceof Object ); // true
console . log ( friend instanceof Person ); // true
console . log ( friend . constructor == Person ); // true
console . log ( friend . constructor == Object ); // false
프로토타입의 동적성질
프로토타입의 값을 찾는것은 런타임시이므로 프로토타입을 변경하면 그 변경은 바로 반영됩니다.
인스턴스가 생성될 때 생성자에서 프로토타입을 가리키는 [[Prototype]] 포인터가 할당됩니다.
하지만 프로토타입의 객체 자체를 다른 값으로 변경하면 변경이 반영이 안됩니다.
function Person () {
}
var friend = new Person ();
Person . prototype . sayHi = function () {
console . log ( "hi" );
};
friend . sayHi (); // hi동작함
Person . prototype = {
constructor : Person ,
name : "Nicholas" ,
age : 29 ,
job : "Software Engineer" ,
sayName : function () {
console . log ( this . name );
}
}
friend . sayName (); // 에러
네이티브 객체 프로토타입
네이티브 참조 타입도 프로토타입 패턴으로 구현되었습니다.
그래서 네이티브 참조 타입도 새 메서드를 추가, 수정할 수 있습니다.
하지만 배포하는 코드에서는 가급적 피하길 권장합니다. (충돌, 기본메서드 덮어쓰기 문제가 발생할 수 있음)
console . log ( typeof Array . prototype . sort ); // function
console . log ( typeof String . prototype . substring ); // function
String . prototype . startsWith = function ( text ) {
return this . indexOf ( text ) == 0 ;
}
var msg = "Hello world!" ;
console . log ( msg . startsWith ( "Hello" ));
프로토타입의 문제점
공유라는 성질이 주요 문제점입니다.
function Person () {
}
Person . prototype = {
constructor : Person ,
name : "Nicholas" ,
age : 29 ,
job : "Software Engineer" ,
friends : [ "Shelby" , "Court" ],
sayName : function () {
console . log ( this . name );
}
};
var person1 = new Person ();
var person2 = new Person ();
person1 . friends . push ( "Van" );
console . log ( person1 . friends ); // Shelby,Court,Van
console . log ( person2 . friends ); // Shelby,Court,Van
console . log ( person1 . friends === person2 . friends ); // true
생성자 패턴과 프로토타임 패턴의 조합
목적: 프로토타입의 공유문제점 해결
방법: 프로토타입에는 공유하는 프로퍼티만 넣고 그 외는 생성자에 넣습니다.
function Person () {
this . name = name ;
this . age = age ;
this . job = job ;
this . friends = [ "Shelby" , "Court" ];
/*
//동적 프로토타입 패턴
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
console.log(this.name);
};
}
*/
}
Person . prototype = {
constructor : Person ,
sayName : function () {
console . log ( this . name );
}
};
var person1 = new Person ( "Nicholas" , 29 , "Software Engineer" );
var person2 = new Person ( "Greg" , 27 , "Doctor" );
person1 . friends . push ( "Van" );
console . log ( person1 . friends ); // Shelby,Court,Van
console . log ( person2 . friends ); // Shelby,Court
console . log ( person1 . friends === person2 . friends ); // false
console . log ( person1 . sayName === person2 . sayName ); // true
기생 생성자 패턴
목적: 다른 패턴이 실패할 때 폴백으로 사용하는 패턴
방법: Wrapper function을 만듬
function Person ( name , age , job ) {
var o = new Object ();
o . name = name ;
o . job = job ;
o . sayName = function () {
console . log ( this . name );
};
return o ;
}
var friend = new Person ( "Nicholas" , 29 , "Software Engineer" );
friend . sayName (); // Nicholas
추가: 객체생성자를 만들 수 있습니다.
function SpecialArray () {
var values = new Array ();
values . push . apply ( values , arguments );
values . toPipedString = function () {
return this . join ( "|" );
};
return values ;
}
var colors = new SpecialArray ( "red" , "blue" , "green" );
console . log ( colors . toPipedString ()); // red|blue|green
방탄 생성자 패턴
목적: 공용프로퍼티가 없고 메서드가 this를 참조하지 않는 객체(durable 객체)를 만드는 생성자
방법: 클로저를 사용하며 생성자를 호출시에 new로 호출하지 않습니다.
function Person ( name , age , job ) {
var o = new Object ();
o . sayName = function () {
console . log ( name );
};
return o ;
}
var friend = Person ( "Nicholas" , 29 , "Software Engineer" );
friend . sayName (); // Nicholas
상속
프로토타입 체인
목적: 객체를 상속하기 위해 사용
방법: prototype에 supertype의 객체를 생성
function SuperType () {
this . property = true ;
}
SuperType . prototype . getSuperValue = function () {
return this . property ;
};
function SubType () {
this . subproperty = false ;
}
SubType . prototype = new SuperType ();
SubType . prototype . getSubValue = function () {
return this . subproperty ;
};
var instance = new SubType ();
console . log ( instance . getSuperValue ()); // true
console . log ( instance instanceof Object ); // true
console . log ( instance instanceof SuperType ); // true
console . log ( instance instanceof SubType ); // true
console . log ( Object . prototype . isPrototypeOf ( instance )); // true
console . log ( SuperType . prototype . isPrototypeOf ( instance )); // true
console . log ( SubType . prototype . isPrototypeOf ( instance )); // true
// 기존 메서드를 오버라이드
SubType . prototype . getSuperValue = function () {
return false ;
};
console . log ( instance . getSuperValue ()); // false
SubType . prototype = {
getSubValue : function () {
return this . subproperty ;
},
someOtherMethod : function () {
return false ;
}
};
instance = new SubType ();
console . log ( instance . getSuperValue ()); // error
문제점: 프로토타입 프로퍼티는 모든 객체에서 공유함
function SuperType () {
this . colors = [ "red" , "blue" , "green" ];
}
function SubType () {
}
SubType . prototype = new SuperType ();
var instance1 = new SubType ();
instance1 . colors . push ( "black" );
console . log ( instance1 . colors ); // red,blue,green,black
var instance2 = new SubType ();
console . log ( instance2 . colors ); // red,blue,green,black
생성자 훔치기
목적: 위의 문제점을 해결함
방법: 생성자를 SubType생성자에서 호출함(this변경)
/*
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // red,blue,green,black
var instance2 = new SubType();
console.log(instance2.colors); // red,blue,green
*/
// 매개변수도 넘길 수 있음
function SuperType ( name ) {
this . name = name ;
}
function SubType () {
SuperType . call ( this , "Nicholas" );
this . age = 29 ;
}
var instance = new SubType ();
console . log ( instance . name ); // Nicholas
console . log ( instance . age ); // 29
문제: 함수 재사용이 불가능해짐
조합상속
목적: 함수도 재사용함
방법: 생성자 훔치기 패턴과 프로토타입 체인 패턴의 장점 취함
function SuperType ( name ) {
this . name = name ;
this . colors = [ "red" , "blue" , "green" ];
}
SuperType . prototype . sayName = function () {
console . log ( this . name );
};
function SubType ( name , age ) {
SuperType . call ( this , name );
this . age = age ;
}
SubType . prototype = new SuperType ();
SubType . prototype . sayAge = function () {
console . log ( this . age );
};
var instance1 = new SubType ( "Nicholas" , 29 );
instance1 . colors . push ( "black" );
console . log ( instance1 . colors ); // red,blue,green,black
instance1 . sayName (); // Nicholas
instance1 . sayAge (); // 29
var instance2 = new SubType ( "Greg" , 27 );
console . log ( instance2 . colors ); // red,blue,green
instance2 . sayName (); // Greg
instance2 . sayAge (); // 27
프로토타입 상속
더글러스 크록포드가 소개한 엄격히 정의된 생성자를 쓰지 않고도 상속 구현하는 방법
function object ( o ) {
function F () {}
F . prototype = o ;
return new F ();
}
//위의 함수를 사용하여 상속 구현
var person = {
name : "Nicholas" ,
friends : [ "Shelby" , "Court" , "Van" ]
};
var anotherPerson = Object ( person );
anotherPerson . name = "Greg" ;
anotherPerson . friends . push ( "Rob" );
var yetAnotherPerson = object ( person );
yetAnotherPerson . name = "Linda" ;
yetAnotherPerson . friends . push ( "Barbie" );
console . log ( person . friends ); // Shelby,Court,Van,Rob,Barbie
// ECMAScript 5판에는 Object.create()메서드를 추가했습니다.
// create 메서드의 두번째 매개변수는 defineProperties와 같은 형식입니다.
var person = {
name : "Nicholas" ,
friends : [ "Shelby" , "Court" , "Van" ]
};
var anotherPerson = Object . create ( person );
anotherPerson . name = "Greg" ;
anotherPerson . friends . push ( "Rob" );
var yetAnotherPerson = Object . create ( person );
yetAnotherPerson . name = "Linda" ;
yetAnotherPerson . friends . push ( "Barbie" );
console . log ( person . friends ); // Shelby,Court,Van,Rob,Barbie
기생상속
목적: 더글라스 크락포드가 만든 개념으로 상속을 담당할 함수를 만들고 객체를 확장해서 반환함
function createAnother ( original ) {
var clone = object ( original );
clone . sayHi = function () {
console . log ( "hi" );
};
return clone ;
}
// 사용
var person = {
name : "Nicholas" ,
friends : [ "Shelby" , "Court" , "Van" ]
};
var anotherPerson = createAnother ( person );
anotherPerson . sayHi (); // hi
기생 조합 상속
조합 상속은 자주 쓰이는 상속 패턴이지만 비효율적이게 상위 타입생성자가 2번 호출됩니다.
이를 고치는 방법으로 기생 조합 상속이 나왔습니다.
function SuperType ( name ) {
this . name = name ;
this . colors = [ "red" , "blue" , "green" ];
}
SuperType . prototype . sayName = function () {
console . log ( this . name );
};
function SubType ( name , age ) {
SuperType . call ( this , name ); // SuperType 생성자 호출
this . age = age ;
}
SubType . prototype = new SuperType (); // SuperType 생성자 호출
SubType . prototype . constructor = Subtype ;
SubType . prototype . sayAge = function () {
console . log ( this . age );
};
function inheritPrototype ( subType , superType ) {
var prototype = object ( superType . prototype ); // 객체 생성
prototype . constructor = subType ; // 객체 확장
prototype . super = superType . prototype ; // super 프로퍼티를 통하여 super type의 메서드를 명시적으로 호출가능
subType . prototype = prototype ; // 객체 할당
}
function SuperType ( name ) {
this . name = name ;
this . colors = [ "red" , "blue" , "green" ];
}
SuperType . prototype . sayName = function () {
console . log ( this . name );
};
function SubType ( name , age ) {
SuperType . call ( this , name );
this . age = age ;
}
inheritPrototype ( SubType , SuperType );
SubType . prototype . sayAge = function () {
console . log ( this . age );
};
var instance = new SubType ( "김부승" , 27 );
이해를 위해서 위의 메서드를 호출할시에 생성되는 객체 그래프 입니다.
장애환경 인포그래픽
##참고자료
Nicholas C. Zakas. (2013). 프론트엔드 개발자를 위한 자바스크립트 프로그래밍, (한선용 옮김). 인사이트
Comments