자바스크립트의 프로토타입에 대해

객체지향 프로그래밍 언어하면 떠올리는 단어가 바로 “클래스”이고, 실제로 대부분의 객체 지향 성격을 갖는 언어들은 대부분 클래스를 지원한다. 그런데 자바스크립트는 클래스가 아닌 프로토타입 기반의 OOP를 사용한다. 오늘은 이 프로토타입에 대하여 살펴보도록 하겠다.

자바스크립트의 객체

어느 정도 고도화된 OOP 언어들은 클래스라는 객체의 청사진(blueprint)을 사용한다. 클래스는 어떤 객체가 갖추어야 할 요건들을 정의해놓은 설계도 같은 것이다. 객체를 생성한다는 것은 이 설계도에 따라 내부 구조를 구성하기 위한 메모리 공간을 할당하고, 정해진 방법에 따라 메모리 공간을 초기화하는 작업을 말한다. 그리고 이렇게 생성된 객체를 클래스와 대비하여 객체 인스턴스(instance)라 부른다.

클래스를 기반으로 생성되는 인스턴스들은 주로 클래스가 정의한 속성들에 의해 그 내부 구조가 고정되는 경향이 있다.1 그에 비해 자바 스크립트의 객체들은 미리 정해진 클래스에 의해 디자인된 구조를 갖기 보다는 키:값 쌍으로 정의되는 프로퍼티를 담고 다니는 가방과 비슷한 개념이다.

기본적으로 자바 스크립트에서 어떤 객체를 만드는 것은 객체 리터럴 문법에 의해 이루어진다. 객체 리터럴문법은 중괄호({ … }) 속에 콜론으로 구분된 프로퍼티 키와 값의 쌍을 콤마로 구분하여 기술한다. 예를 들면 다음과 같은 식이다.

var anObj = {
  name: "Unnamed",
  numberValue: 1.0
};

클래스를 사용하는 OOP언어들은 클래스명 자체를 인스턴스 생성자 함수처럼 호출하거나 하는 식으로 인스턴스를 생성하며, 이를 통해서 여러 내부 속성들은 간편하게 초기화할 수 있다. 그에 비해 자바스크립트의 객체 리터럴은 인스턴스를 생성한다기보다는 일종의 딕셔너리를 코딩한다는 느낌으로 객체를 만들고 있다. 만약, 일련의 공통된 속성 이름 세트를 갖는 많은 객체를 이렇게 일일이 리터럴로 생성하는 것은 매우 귀찮은 일이 될 것이다. 예를 들어 연락처 앱 같은 것을 만들기 위해서 이름과 나이, 성별, 전화번호와 같은 필드를 담고 있는 객체를 계속해서 리터럴 문법으로 생성하는 것은 매우 귀찮은 일이 될 것이다. 따라서 다음과 같은 함수를 하나 만들어서 객체를 생성하는 일을 조금 수월하게 할 수 있을지도 모르겠다.

function createPerson(name, telinfo) {
  var person = {
    name: name,
    telinfo: telinfo
  };
  return person;
}

이렇게 해서 객체의 생성자 비슷한 함수를 만들 수는 있지만, 여전히 부족한 것이 많다. 같은 클래스의 인스턴스인 객체들은 동종 타입과 같이 비슷한 속성을 자동으로 부여 받으며, 뭔가 공통적인 행동들을 할 수 있기 때문이다. 자바스크립트에서는 클래스 대신 프로토타입이라는 다른 개념이 존재한다. 그렇다면 프로토타입은 어떤 식으로 일반 객체 인스턴스들과 관계하는지 살펴보도록 하자.

프로토타입의 개념

클래스는 객체 인스턴스들의 타입과 비슷한 개념이라 볼 수 있다. 그리고 우리의 인식에서 개별 인스턴스와 그 타입은 같은 층위의 개념이 아니다. 예를 들어 우리 혹은 우리의 이웃이 키우는 반려견인 ‘해피’, ‘메리’, ‘똥개’는 실제 살아 숨쉬는 개개의 생명이지만, 이들의 타입이라 할 수 있는 ‘강아지’는 하나의 생명이 있는 개체라기 보다는 어떤 생명체의 유형을 말하는 개념인 셈이다.

프로토타입은 일종의 메타타입이다. 프로토타입은 객체 인스턴스가 공통적으로 어떤 속성을 갖고 있는지를 묘사하는 동시에 그 자체로 하나의 객체 인스턴스이다. 예를 들어서 앞의 예제에서 createPerson() 함수를 통해서 생성된 객체들의 타입을 Person이라고 정하기로 했다고 하자. 그러면 그 타입에 해당하는 프로토타입 객체가 인스턴스의 형태로 어딘가에 실재할 것이다. 그리고 그 프로토타입 역시 자바스크립트 객체이므로 동적으로 프로퍼티를 만들거나 추가하는 것이 가능할 것이다.

클래스를 기반으로 한 객체 인스턴스에 대해서, 우리는 그 클래스의 구조를 알고 있다면 해당 객체 인스턴스가 제공할 수 있는 프로퍼티의 이름들을 알고 있고, 각 인스턴스가 그 이름에 해당하는 값을 내놓으리라는 것을 기대할 수 있다. 자바스크립트 객체의 프로토타입도 이와 비슷한 조건을 만족시킨다.

어떤 프로토타입, (아직 어떻게 만들거나 접근해야하는지 모르지만) Friend 가 있다고 하자. 이 프로토타입은 nameage라는 두 프로퍼티를 가지고 있다. 이것은 단순한 청사진이 아니라 실제 객체의 프로퍼티이므로 임의대로 name='Unnamed', age=0 이라는 값을 가지고 있는 것으로 생각할 수 있다. 그리고 역시 아직 어떻게 생성하는지는 모르지만, Friend를 프로토타입으로 하는 두 객체인스턴스, tom, jane 이 있다고 가정하자.

tom.name = 'Tom';
jane.name = 'Jane';
jane.age = 14;

그리고 위와 같이 각각의 인스턴스에 대해서 속성값을 만들어주었다. 여기서 주목할 점은 프로토타입이 어찌되어 있든 간에 최초로 생성한 tom, jane은 각각 텅 비어있는 주머니이고, 여기에 nameage를 각각 부여했다는 것이다. 따라서 두 객체의 실제 내용 구성은 다음과 같을 것이다.

tom = { name: 'Tom' } --prototype--> Friend {name:'Unnamed', age: 0}
                                     ^
jane = { name: 'Jane', age: 14} -----|

만약 프로토타입을 생각하지 않는다면 tomjane에게 각각 age 값을 보여달라고 요청하면 다음과 같은 결과를 내보일 것이다.

console.log(jane.age)
/// 14
console.log(tom.age)
/// undefined

jane의 경우에는 프로퍼티 꾸러미 속에 age라는 키가 있고, 그 값은 14로 되어 있다. 그리고 tom 이라는 꾸러미에는 age라는 프로퍼티 키가 없기 때문에, “그런키는 정의되어 있지 않음”이라는 의미로 undefined가 출력된다.

하지만 프로토타입이 개입하면 결과가 약간 달라진다.

console.log(tom.age)
/// 0

왜 이런 결과가 나올까? tom이라는 꾸러미 내에는 age라는 키가 정의되어 있지 않다. tom.age 라는 구문을 평가하기 위해서 자바스크립트 런타임은 tom 이라는 꾸러미 속을 뒤져본다. 만약 jane 처럼 그 속에서 해당 키를 찾을 수 있다면 그 값을 가져오면 된다. 하지만 tom처럼 키를 찾을 수 없는 경우라면 자바스크립트 런타임은 tom의 프로토타입인 Friend2 를 찾을 것이다. 그리고 우리가 처음 정한 것 처럼 그 속에는 age라는 키가 정의되어 있고, 그 값은 0이다. 따라서 0이 출력됐다.

프로토타입과 객체 프로퍼티

따라서 우리는 프로토타입과 객체 인스턴스와의 관계를 다음과 같이 정리할 수 있다.

  1. 객체의 구조는 단순한 이름:키의 짝들로 구성되는 프로퍼티 꾸러미이며, 여기에는 그 이상의 내용은 없다.
  2. 프로토타입은 메타타입으로서 자신을 프로토타입으로 가지는 객체들이 ‘요구받을지 모를’ 값들의 디폴트값을 정의해놓을 수 있다.
  3. (우리가 tom 의 예에서 보듯) 프로토타입이 정의한 값들은 실제 객체 인스턴스의 프로퍼티에 의해 셰도우잉된다.
  4. 따라서 각 객체 인스턴스가 키와 값을 세팅받으면, 프로토타입이 아닌 그 인스턴스 내의 프로퍼티가 된다.

즉 프로토타입은 그냥 객체인 동시에, 자신을 타입으로(?) 간주하는 인스턴스들에게 특정한 이름의 프로퍼티를 빌려줄 수 있는 객체인 셈이다. 그러면 일반 객체와 프로토타입이 되는 객체는 어떻게 서로 관계를 맺을 수 있을까? 다시 createPerson 함수로 돌아가보자.

함수의 prototype

자바스크립트에서 함수는 객체이다. 함수 그 자체를 객체로 다룰 수 있는 언어는 많이 있으니 이것은 그리 특이한 개념은 안라 할 수 있다. 그런데 자바스크립트의 객체는 프로퍼티를 넣고 다니는 주머니같은 것이라 했다. 따라서 자바스크립트의 함수는 다른 일반적인 객체와 마찬가지로 임의의 속성을 붙여서 사용할 수 있다. 반대로 함수가 처음부터 같은 속성 같은 것들도 존재할 수 있다. 다음 코드에서 “객체” add는 두 수를 더하는 함수이지만, 앞서 예로 든 tom, jane 과 같이 임의의 이름의 인스턴스를 정의하는 것 역시 얼마든지 가능하다.

var add = function(x, y) { return x + y; };
add.description = "This is an add function. Return the sum of two given numbers.";

add(1, 2);
/// -> 3
console.log(add.description);
/// This is an add function. Return the sum of two given numbers.

그런데 모든 함수는 prototype이라는 속성을 가지고 있다. 이것은 어떤 프로토타입 객체로 자동으로 구성되는데 그 특성은 다음과 같다.

  1. 프로토타입의 이름은 일단 함수의 이름과 같다.
  2. 그리고 그 함수를 통해서 생성되는 객체가 있는 경우, 그 생성된 객체의 프로토타입이 된다.

여기서 뭔가 불행이 시작되는데, prototype이라는 속성은 그 함수의 프로토타입을 말하는 것이 아니다. 함수를 통해서 생성되는 객체의 프로토타입으로 지정될 객체를 말한다는 것이다. 이것이 어떻게 동작하는지를 보기 위해서 createPerson() 과 비슷한 Person()이라는 함수를 만들어보도록 하겠다. 그리고 여기서, 자바스크립트의 마법의 단어인 this가 등장한다.

function Person(name, age) {
  this.name = name;
  this.age = age;
  // 명시적인 리턴은 필요없다.
}

Person()은 특정한 객체를 생성해서 그 결과를 리턴하는 것이 아니라, 마치 어떤 타입의 초기화 함수처럼 동작한다. 즉 이 함수의 내부에서는 this는 새로 생성된 객체가 되며, 그 값들을 초기화하고 있다. 이 함수는 실제로 호출해보아도 아무것도 리턴하지 않기 때문에 암시적으로 undefined로 평가된다.

이런 모양의 함수들을 자바스크립트에서는 특별히 constructor라 부르고 있다. (보통 이러면 구축자(?) 정도로 직역해서 부르기도 하는데, **자 와 같은 이름을 개인적으로 좋아하지 않아서 따로 번역하지 않았다.) constructor 함수들은 일반 함수처럼 호출하는 것이 아니라 new 라는 연산자를 통해서 사용한다.

var james = new Person('James', 20);
var mary = new Person('Mary', 18);

이렇게 객체를 생성한 경우, jamesmary는 공통의 프로토타입을 갖는다. 즉, 같은 타입이라고 분류할 수 있다.

Constructor 와 Prototype 그리고 메소드

모든 함수가 prototype이라는 프로퍼티를 가지고 있다는 사실과 비슷하게, 모든 객체는 constructor라는 프로퍼티를 가지고 있다. 심지어 컨스트럭터 함수를 이용해서 생성하지 않고 객체 리터럴을 통해서 생성된 객체들도 이 프로퍼티를 가지고 있다.3 따라서 어떤 객체의 프로토타입에 액세스하기 위해서는 anObject.constructor.prototype 이라는 형태로 액세스할 수 있다.

프로토타입은 일반 객체와 동일하게 다룰 수 있다고 했다. 이 말은 프로토타입 역시 런타임에 어떤 속성을 추가하거나 변경할 수 있음을 뜻 한다.

만약 Person이라는 타입의 모든 인스턴스에 대해서 favoriteFood 라는 속성을 추가하고 싶다고 하자. 기존에 생성된 모든 인스턴스를 찾아서 해당 프로퍼티를 추가하기보다는 Person이라는 프로토타입을 이용하면 보다 손쉽게 해당 프로퍼티를 동적으로 추가할 수 있다.

참고로 위에서 만든 Person()의 프로토타입은 현재 텅 비어있는 프로퍼티 주머니이다. 여기에 새로운 공통 속성을 하나 추가해주자.

james.constructor.prototype['favoriteFood'] = 'Cheese';
james.favoriteFood
//-> 'Cheese'
mary.favoriteFood
//-> 'Cheese'

그렇게하면 james 뿐만 아니라 mary까지 같은 속성을 가질 수 있게 된다. 사실 런타임에 특정 타입의 속성을 수정하는 일이 많이 있겠냐만은 이 방법은 의외로 아주 중요한 문법이다. 왜냐하면 자바스크립트에서 특정한 타입의 메소드를 정의하는 방법이기 때문이다.

메소드를 정의하는 이상한 방법

한 편 다른 방법으로 메소드를 정의할 수도 있다. 우리가 객체 리터럴에서 익명함수 문법으로 객체에 “실행가능한” 속성을 추가할 수 있는 것과 비슷하게, 메소드를 정의할 수 있다. 바로 Person 함수 내에서 정의하는 것이다.

function Person(name, age) {
  this.name = name;
  this.age = age;
  // 이제 모든 Person 기반 객체는 favoriteFood 속성을 갖는다.
  this.favoriteFood = 'Cheese';
  this.greet = function(x) {
    console.log('Hello, ' + x + '. My name is ' + this.name);
  };
}

var james = new Person('James', 20);
james.greet('sooop');
/// Hello, sooop. My name is James.

 

이 코드는 별 문제없이 잘 동작하는 것 같다. 클래스 기반의 여러 OOP 언어들에서 클래스 정의 코드 내에서 메소드를 정의하는 것과도 언뜻 매우 비슷하게 보인다. 하지만 이 코드는 몇 가지 문제가 있다.

  1. 의도한 것과 다르게 메소드라고 지정해놓은 함수는 해당 인스턴스와 단단하게 바인딩되지 않는다. 사실 이것은 이 코드가 문제라기 보다는 자바스크립트 내의 this가 가리키는 대상이, 호출하는 시점에서 그 때 그 때 달라질 수 있기 때문이다. 따라서 이것은 문제라기 보다는 그냥 자바스크립트의 언어적 특성이라고도 볼 수 있다.
  2. 스트럭터가 호출될 때마다, 이 함수 내에서 인사말을 출력하는 익명함수 객체가 매번 생성된다. 만약 인스턴스 10만개를 만들었다고 하면 똑같은 일을 하는 함수 객체가 그만큼 중복으로 많이 만들어지고, 메모리 낭비가 발생한다.

앱의 라이프 사이클 내에서 수 개 혹은 수 십개의 인스턴스만 생성된다면 문제가 없지만, 여러 개의 메소드가 이런식으로 정의된다면 컨스트럭터 함수가 호출될 때마다 메모리를 점점 더 많이 사용해야 하는 부담이 생긴다. 특히 메소드의 경우에는 “모든 인스턴스가 공통적으로 동작할 것”이라는 것을 기대하게 되므로, 이런식으로 작성하지 않고 앞선 예와 같이 프로토타입을 이용하는 것이 바람직하다. 따라서 최종적으로는 다음과 같이 정의할 수 있는 것이다.

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.favoriteFood = 'Cheese';
}
Person.prototype.greet = function(x) {
  console.log('Hi, ' + x + '. My name is ' + this.name + '.');
};

var james = new Person('James', 20);
james.greet('sooop');
/// Hello, sooop. My name is James.

이 코드에서는 메소드의 정의가 컨스트럭터 외부에 있다.  따라서 타입을 디자인하는 코드가 흩어져 보인다는 어색함은 있지만, 개념적으로는 프로토타입으로부터 빌려온 실행가능한 프로퍼티를 사용하는 개념이므로 중복된 함수 객체를 생성할 필요 없이 깔끔하게 동작하며, 실제 OOP 언어들의 동작과도 거의 유사하다고 하겠다.

프로토타입에 대한 접근자, 그리고 프로토타입 체인

어떤 객체의 anObj의 프로토타입은 직접적인 접근자를 통하기 보다는 anObj.constructor.prototype 과 같은 식으로 해당 접근을 간접적으로 우회할 수만 있게 하고 있다. 사실 모든 객체는 내부적으로 [[prototype]] 이라는 외부에서는 액세스할 수 없는 프로퍼티를 가지고 있고, 이 프로퍼티에 의해 그 객체의 프로토타입을 참조할 수 있다.

모든 것을 런타임에 교체할 수 있는 자바스크립트의 특성상, 특정 객체 인스턴스의 프로토타입만큼은 런타임에 교체할 수 없게 하기 위한 디자인으로 보인다. 하지만 대부분의 브라우저 공급사들은 그러거나 말거나 해당 내부 프로퍼티에 접근할 수 있는 접근자를 만들어 두었으니, 그것은 바로 __proto__ 이다. 우리는 이 프로퍼티를 이용해서 특정한 객체의 프로토타입에 직접 접근할 수 있다. 굳이 이걸 사용할 필요는 없지만, 객체로부터 프로퍼티를 어떻게 lookup 하는지에 대한 과정을 조금 더 쉽게 따라가보기 위해서 사용할 것이다.

프로토타입은 자신을 프로토타입으로 연결하고 있는 객체들에게 프로퍼티를 빌려주는 역할을 한다고 했다. 그리고 각 인스턴스는 자기 자신의 주머니 내에 없는 이름이 들어오면 프로토타입에게 그 이름으로 속성값을 빌려오려고 한다. 여기까지는 이해했으리라고 생각된다.

그런데 프로토타입이 되는 james.__proto__ 역시 객체 인스턴스이고, 이 객체 역시 어딘가에 자신의 프로토타입을 가지고 있을 것이라는 점을 주목하자. 보통의 객체들은 Object() 를 생성자로 하고 있으니, 빈 Object의 인스턴스를 그 프로토타입으로 삼고 있을 것이다.

이러한 프로토타입으로의 참조는 연쇄되어 어디까지 가게 될까? 지금까지의 Person의 최종버전을 통해서 살펴보도록 하자.

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.favoriteFood = 'Cheese';
}
Person.prototype.greet = function(x) {
  console.log('Hi, ' + x + '. My name is ' + this.name + '.');
};

var james = new Person('James', 20);

james와 james의 프로토타입(james.__proto__)의 관계는 다음과 같다.

  1. james는 굳이 타임을 따지자면 Person 타입의 인스턴스로, name, age, favoriteFood의 세 프로퍼티 키를 가지고 있다.
  2. james의 프로토타입에 해당하는 객체는 (놀랍게도) 역시 Person의 인스턴스로, greet 속성만을 가지고 있다. 왜냐하면 이 프로토타입 객체는 컨스트럭터를 통해서 생성된 객체가 아니기 때문이다.

여기까지를 확인해보자. 다음은 Node REPL에서 찍어본 내용이다.

> james
Person { name: 'James', age: 20, favoriteFood: 'Cheese'}
> james.__proto__
Person { greet: [Function] }

여기서 더 거슬러가보자.

> james.__proto__.proto__ // 1
{}
/// 어라?
> james.__proto__.__proto__.constructor //2
[Function: Object]
> james.__proto__.__proto__.__proto__ // 3
null
  1. james.__proto__ 의 프로토타입 객체, 즉 james.__proto__.__proto__는 그냥 빈 객체이다.
  2. 이 빈 객체는 뭘로 만들어졌나를 보기위해서 constructor 속성을 확인해보면 Object()에 의해서 생성할 수 있음을 알 수 있다.
  3. jame.__proto__.__proto__.__proto__ 까지 거슬러 올라가면 이 값은 null 이다. nullundefined와는 달리 정말 아무것도 없다는 것을 나타내는 나타낸다

null은 특별한 자바스크립트 객체로 여기에는 어떤 프로퍼티도 세팅할 수가 없다. null.a = 1 과 같은 코드는 런타임 에러를 유발한다. 마찬가지로 null은 어떠한 프로퍼티도 갖지 않으므로 어떠한 값도 읽을 수 없으며, 이러한 시도는 모두 타입 에러가 된다.

앞에서 우리는 자바스크립트 객체에게 어떤 프로퍼티를 요구하면, 두 단계를 거쳐서 그 값을 얻게 된다고 했다.

  1. 객체 자신의 프로퍼티 중에서 해당 이름이 있으면 그 값을 리턴
  2. 해당 이름이 없으면 객체의 프로토타입(anObj.__proto__)에게 해당 이름의 프로퍼티를 얻어서 리턴

그런데 그 프로토타입 역시 어떤 객체라고 했다. 만약 james에게 bestFriend 라는 속성을 요청했다고 하자.(이미 우리는 그 값이 정의되지 않았다는 것을 알고 있다.) 그러면 그 값을 찾는 과정은 다음과 같이 정리된다.

  1. james.bestFriendjames의 고유한 프로퍼티 중에서 bestFriend 라는 이름을 찾는다.
  2. 이 이름이 없으므로 다시 james.__proto__.bestFriend 라는 속성으로 대체하여 찾게된다.
  3. james.__proto__ 역시 이 이름을 갖지 않으므로 james.__proto__.__proto__.bestFriend 를 찾게된다.
  4. 그 역시 이 이름을 갖지 않으므로 james.__proto__.__proto__.__proto__.bestFriend를 찾는다.
  5. 그런데 james.__proto__.__proto__.__proto__null 이므로 더 이상 이름을 찾을 수 없다.
  6. 그러면 Object는 그 디폴트 동작으로 undefined를 리턴한다. 이 값은 앞의 lookup 체인을 거슬러서 james에게 전달되고
  7. 다시 jamesundefined를 최종적으로 리턴한다.

정리

이상으로 자바스크립트의 객체와, 컨스트럭터 그리고 프로토타입의 상관관계에 대해서 살펴보았다. 내용을 몇 줄로 요약하자면 다음과 같다고 하겠다.

  1. 프로토타입은 어떤 객체와 동일한 타입의 객체로, 해당 타입의 다른 객체들에게 프로퍼티를 빌려주는 역할을 한다.
  2. 어떤 객체를 컨스트럭터를 통해서 생성하면, 컨스트럭터는 그 객체의 초기값을 세팅한 후, 생성된 객체에게 프로토타입 객체를 연결한다.
  3. 타입 내 모든 인스턴스가 공통적으로 가져야 하는 속성, 예를 들어 메소드와 같은 것은 컨스트럭터의 prototype을 통해서 정의하는 것이 좋다.
  4. 프로토타입 역시 객체 인스턴스이므로, 프로토타입의 프로토타입이 있을 수 있다. 이 연쇄를 따라 올라가면 최상위에 Object가 있고, 그것의 프로토타입은  null 이다.

참고로 최근의 자바스크립트 표준 규격에서 class 키워드를 사용하는 클래스 정의 문법이 새로이 추가되었다. 이는 기존의 클래스 기반의 OOP 언어에 익숙해진 개발자들을 위한 배려(?)로 보이기는 하는데, 실제로 클래스 개념이 도입된 것이 아니라, 내부적으로는 프로토타입+컨스트럭터 기반의 코드로 재해석되도록 동작하기 때문에, 그다지 좋은 결정인지는 모르겠다.

 

 

 


  1. 파이썬과 같이 클래스를 기반으로 인스턴스를 생성하더라도 인스턴스의 속성을 런타임에 추가하거나 제거할 수 있는 동적언어들도 존재하기는 한다. 
  2. 프로토타입의 실제 이름은 사실 존재하지 않는다. 여기서는 타입이름으로 대신해서 부른것일 뿐이며, 실제 두 객체 인스턴스의 프로토타입과 Friend라는 타입은 엄연히 다르다. 엄밀하게 이야기해서 프로토타입 역시 Friend 라는 타입의 어떤 다른 객체이다. 
  3. 객체 리터럴로 생성된 객체들은 내부적으로 Object라는 자바스크립트의 최상위 프로토타입을 기초로 생성된 것으로 간주된다.