모던 자바스크립트 Deep Dive 19장-프로토타입

HustleKang·2022년 4월 5일

객체지향 프로그래밍

독립적인 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
객체 : 상태를 나타내는 데이터(데이터 프로퍼티)와, 상태를 조작할 수 있는 동작(메서드)을 하나의 단위로 묶은 자료구조

상속과 프로토타입

일반적인 생성자 함수로 여러 객체를 만들면 동일한 메서드가 계속 만들어짐

function Person(name){
	this.name = name;
    this.hi = function(){
    	console.log(`hi i'm ${this.name}`);
    };
}

const person1 = new Person('John');
const person2 = new Person('AJ');

person1.hi===person2.hi //false

이를 프로토타입 기반으로 하면 해결 가능
생성자 함수에 의해 만들어진 인스턴스는 생성자 함수의 프로토타입의 모든 프로퍼티와 메서드를 상속받는다

function Person(name){
	this.name = name;
}

Person.prototype.hi = function(){
    	console.log(`hi i'm ${this.name}`);
};

const person1 = new Person('John');
const person2 = new Person('AJ');

person1.hi===person2.hi //true

프로토타입 객체

모든 객체는 [[Prototype]] 내부 슬롯을 갖고 [[Prototype]]의 값은 프로토타입 객체의 참조이다
[[Prototype]] 에 저장되는 프로토타입 객체는 객체 생성 방식에 의해 결정
객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장
객체 리터럴에 의해 생성된 객체 : Object.prototype
생성자 함수에 의해 생성된 객체 : 생성자함수의 prototype

객체, 프로토타입, 생성자 함수는 서로 연결되어 접근 가능
객체는 _ proto _ 접근자 프로퍼티로 프로토타입에 간접 접근 가능
프로토타입은 자신의 constructor프로퍼티로 생성자 함수에 접근
생성자함수는 자신의 prototype프로퍼티로 프로토타입에 접근

_ proto _ 접근자 프로퍼티

모든 객체는 _ proto _ 로 자신의 프로토타입에 간접 접근 가능
객체가 직접 갖고 있는 것은 아니고 Object.protype의 프로퍼티인데 상속받아 사용

직접 상속을 통해 Object.prototype을 상속 받지 않는 객체는 _ proto _ 못씀
프로토 타입의 참조를 원하면 다르게

const obj = {};
const parent = {x:1};
obj.__proto__ = parent; //값을 할당 했으니 setter함수 실행
obj.x; // 1

Object.getPrototypeOf(obj); // obj.__proto__ 와 같음
Object.setPrototypeOf(obj,parent);

함수 객체의 prototype 프로퍼티

constructor의 prototype프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가르킴

function Person(name){
	this.name = name;
}

const me = new Person('AJ');

Person.prototype === me.__proto__ //true
Person.prototype.constructor === Person //true
Object.getPrototypeOf(me).constructor == Person //true

리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

객체 리터럴에 의해 생성한 객체도 Object 생성자 함수와 constructor로 연결되있다
{리터럴로만든객체}.constructor ===Object //true 이지만 Object가 이 객체를 만든건 아님

함수의 경우 Function 생성자로 만들면 렉시컬스코프,클로저가 안만들어지는데
일반적인 함수 선언문으로 만든 함수의 constructor는 Function 생성자를 가르킴

리터럴 표기법으로 만든 객체도 프로토타입이 필요하기 때문에 가상적인 생성자 함수를 갖는다

프로토타입과 생성자 함수는 항상 쌍으로 존재

프로토타입의 생성 시점

프로토타입은 생성자 함수가 생성되는 그 시점에 같이 생성됨

사용자 정의 생성자 함수와 프로토타입 생성 시점

함수가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 같이 생성됨
함수 선언문은 런타임 이전에 실행되므로 이 때 함수 객체와 프로토타입이 만들어진다
생성된 프로토타입도 객체이므로 프로토타입이 있다
생송된 프로토타입의 프로토타입은 Object.prototype

빌트인 생성자 함수와 프로토타입 생성 시점

모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성됨
이 때 빌트인 생성자 함수의 프로토타입이 생성

객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재함
생성자 함수나 리터럴로 객체를 생성하면 이 때 생성된 객체의 [[Prototype]]에 할당된다
이로써 새롭게 만들어진 객체는 프로토타입을 상속받는다

객체 생성 방식과 프로토타입의 결정

객체는 여러가지 방법으로 만들 수 있다

  • 객체 리터럴
  • Object 생성자 함수
  • 사용자가 정의한 생성자 함수
  • Object.create 메서드
  • 클래스

모든 방식은 결국 추상 연산 OrdinaryObjectCreate에 의해 객체를 생성
OrdinaryObjectCreate는 생성할 객체의 프로토타입을 인수로 무조건 받음
빈 객체를 생성 후 그 객체의 [[Prototype]]에 전달받은 인수를 할당
객체 생성 방식에 따라 다른 인수로 다른 프로토타입을 넘김

객체 리터럴에 의해 생성된 객체의 프로토타입

객체 리터럴을 평가하여 객체를 생성할 때 Object.prototype이 인수로 전달
즉 객체 리터럴로 생성한 객체는 Object.prototype이 자신의 프로토타입이기 때문에 Object.prototype의 모든 프로퍼티와 메서드를 상속받아 사용 가능

Object 생성자 함수에 의해 생성된 객체의 프로토타입

추상 연산 OrdinaryObjectCreate에 Object.prototype이 전달
객체 리터럴과 같다

생성자 함수에 의해 생성된 객체의 프로토타입

추상 연산 OrdinaryObjectCreate에 생성자 함수의 prototype에 바인딩된 객체를 전달

사용자 정의 생성자 함수와 함께 생성된 프로토타입은 프로퍼티로 constructor밖에 없다.
하지만 Person.prototype의 prototype이 Object.prototype이기 때문에 여러 메서드 사용이 가능한 것

프로토타입 체인

객체의 프로퍼티에 접근할 때 해당 객체에 그 프로퍼티가 없으면 [[Prototype]] 내부 슬롯 참조를 따라 프로토타입의 프로퍼티를 순차적으로 검색
이런 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘이다
이 프로토타입 체인의 종점은 Object.prototype이다
Object.prototype의 [[Prototype]] 은 null

객체가 갖고있지 않은 프로퍼티여도 에러가 나지 않고 undefined 반환
프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이다

오버라이딩과 프로퍼티 섀도잉

생성자 함수로 생성한 인스턴스 객체에 프로토타입이 갖고있는 프로퍼티와 동일한 이름의 프로퍼티를 추가하면 그 인스턴스 프로퍼티가 프로토타입의 프로퍼티를 가려버림 = 프로퍼티 섀도잉

하위 객체를 통해 상위의 프로토타입의 프로퍼티 변경,삭제는 불가


me.sayHello() 는 인스턴스에 새롭게 추가한 인스턴스 프로퍼티가 실행됨

프로토타입의 교체

생성자 함수에 의한 프로토타입의 교체

const Person = (function(){
    function Person(name){
        this.name = name;
    }

    Person.prototype = {
    	// constructor : Person, 연결을 해줘야함
        say(){
            console.log('hi');
        }
    };
    
    return Person;
}());

const me = new Person('AJ');


프로토타입을 교체하면 생성자 함수와 constructor의 연결이 끊김

인스턴스에 의한 프로토타입 교체

const parent = {
	say(){
    	console.log('hi');
    }
};

Object.setPrototypeOf(me,parent);

새롭게 설정한 프로토타입에 constructor가 없어서 생성자 함수와의 연결이 끊어짐

여기서는 Person.prototype이 새로운 프로토타입을 안가르킴

instanceof 연산자

객체 instanceof 생성자 함수
객체의 프로토타입 체인 상에 생성자 함수의 prototype에 바인딩된 객체가 존재하면 true
앞에서 인스턴스에 의해 프로토타입을 교체하면 생성자함수의 prototype 프로퍼티가 교체된 프로토타입 객체를 가르키지 않으므로 false
생성자 함수에 의한 교체는 연결이 안끊어져서 새롭게 프로토타입을 교체해도 true

직접 상속

Object.create에 의한 직접 상속

첫번째 인수로 프로토타입으로 지정할 객체를 전달해준다


let obj = Object.create(null); // Object.prototype의 메서드 사용 불가


obj = Object.create(Object.prototype);


// obi={x:1};과 동일
obj = Object.create(Object.prototype,
{
x: { value: 1, writable: true, enumerable: true, configurable: true }
});

Object.prototype 메서드는 간접 호출하는 것이 좋다

객체 리터럴 내부에서 _ proto _ 에 의한 직접 상속

const Proto={x:10};
const obj = {
	y = 10,
    __proto__ : Proto
};

정적 프로퍼티/메서드

생성자 함수 자체가 갖는 프로퍼티,메서드
인스턴스 생성없이 호출이 가능

function Person(name) {
	this.name = name;
}

Person.prototype.sayHello = function () {
	console.log( Hi! My name is ${this.name} );
}

// 정적 프로퍼티
Person.staticProp = 'static prop'; 

//정적 메서드
Person.staticMethod = function () {
console.log('staticMethod');
J;

const me = new Person ('Lee' );

Person.staticMethod(); // 인스턴스 없이 바로 호출 가능
me.staticMethod(); // TypeError: me.staticMethod is not a function

인스턴스의 프로토타입 체인에 정적 메서드는 없기 때문에 당연히 호출이 불가

프로퍼티 존재 확인

객체에 프로퍼티가 있나 없나

in 연산자

해당 객체의 프로토타입 체인을 다 검색
직접적인 프로퍼티가 아니라 상속받은 거여도 true 나옴

const person = {
	name : 'aj',
    age : 20
};

'name' in person; // true
'height' in person; // false
'hasOwnProperty' in person; // true 

Reflect.has(person,'name'); //true ES6

Object.prototype.hasOwnProperty

해당 객체 고유의 프로퍼티일때만 true
상속받은 프로퍼티면 false

person.hasOwnProperty('hasOwnProperty'); // false
person.hasOwnProperty('age'); // true

프로퍼티 열거

for ... in 문

해당 객체의 프로토타입 체인에 존재하는 모든 프로퍼티중 [[Enumerable]]이 true인 프로퍼티의 키를 열거
프로퍼티 키가 심벌인 프로퍼티는 제외
상속받은 프로퍼티가 아닌 고유의 프로퍼티인지 따질려면 hasOwnproperty해줘야

const person = {
	name : 'aj',
    age : 20
};

for(const key in person){
	console.log(key); //name,age
}

배열의 성분에 접근할 때 for ... in 을 사용하면,
배열도 객체기 때문에 프로퍼티를 갖을 수 있는데 얘가 포함된다
배열 내장 함수 다 튀어나옴
for ... in 쓰지말길

const arr = [1,2,3,4];
arr.x = 5;
for (const i in arr){
	console.log(arr[i]); // 1 2 3 4 5
}

Object.keys/values/entries 메서드

  • Object.keys : 객체 고유의 열거가능한 프로퍼티의 키를 배열로 반환
  • Object.values : 객체 고유의 열거 가능한 프로퍼티 값을 배열로 반환
  • Object.entries : 프로퍼티의 키와 값을 배열에 담은 이차원 배열 반환
const person = {
	name : 'aj',
    age : 20
};

Object.keys(person); // ['name','age']
Object.values(person); // ['aj',20]
Object.entries(person); // [['name','aj'],['age',20]]

이웅모, 『모던 자바스크립트 Deep Dive』, 위키북스(2021)

profile
grindin'

0개의 댓글