writed by wnsdlf25

프로토타입 체인

  • me 객체의 프로토타입은 Person.prototype이고, Person.prototype의 프로토타입은 Object.prototype이다.
  • 프로토타입의 프로토타입은 언제나 Object.prototype이다.
function Person(name) {
  this.name = name;
}

const me = new Person(`Lee`);

// hasOwnProperty 는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty(`name`)); // true;

Object.getPrototypeOf(me) === Person.prototype; // -> true

Object.getPrototypeOf(Person.prototype) === Object.prototype; // -> true
  • 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라 한다.
  • 프로토타입 체인은 자바스크립트가 객체 지향 프로그래밍의 상속을 구현하는 메커니즘이다.
  • object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined를 반환

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

  • 오버라이딩은 프로토타입에 메서드가 존재하는데, 인스턴스에 같은 메서드를 추가해 재정의하여 사용하는 방식이다.
  • 인스턴스에서 오버라이딩하여 사용하면 프로토타입 메서드는 가려지게 되는데 이러한 현상을 섀도잉 이라고 한다.
  • 프로퍼티를 삭제하는 경우 delete를 사용한다.
const Person = (function () {
    // 생성자 함수
    function Person(name) {
      this.name = name;
    }
  
    // 프로토타입 메서드
    Person.prototype.sayHello = function () {
      console.log(`Hi! My name is ${this.name}`);
    };
  
    // 생성자 함수를 반환
    return Person;
  }());
  
  const me = new Person('Lee');
  
  // 인스턴스 메서드
  me.sayHello = function () {
    console.log(`Hey! My name is ${this.name}`);
  };
  
 // 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
 me.sayHello(); // Hey! My name is Lee

---------------------------------------------------------------------------------------

// 인스턴스 메서드를 삭제한다.
delete me.sayHello;

// 인스턴스에는 sayHello 메서드가 없으므로 프로토타입 메서드가 호출된다.
me.sayHello(); // Hi! My name is Lee

// 프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello(); // TypeError: me.sayHello is not a function

프로토타입의 교체

  • 프로토타입은 임의의 다른 객체로 변경할 수 있다. 이러한 특징을 활용하여 객체 간의 상속 관계를 동적으로 변경할 수 있다.
const Person = (function () {
    function Person(name) {
      this.name = name;
    }
  
    // ① 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
    Person.prototype = {
      sayHello() {
        console.log(`Hi! My name is ${this.name}`);
      }
    };
  
    return Person;
  }());
  
  const me = new Person('Lee');
  • Person.prototype에 객체 리터럴을 할당했다. 이는 Person 생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체한 것이다.

  • 프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없다. 따라서 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.

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

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

  // ① 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체 -> 
// Person.prototype에 객체 리터럴을 할당했다.

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

  return Person;
}());

const me = new Person('Lee');

// 객체 리터럴에는 constructor프로퍼티가 없다. 
// 따라서 me객체의 생성자함수constructor를 검색하면 Person이 아니라 Object가 나온다.

// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true

// 이러면 constructor프로퍼티와 생성자 함수간의 연결이 파괴된다. 
// 그러므로 아래와 같이 ②의 constructor: Person,을 추가해 줘 연결을 되살린다.

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

  // 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
  Person.prototype = {
    // ② constructor 프로퍼티와 생성자 함수 간의 연결을 설정
    constructor: Person,
    sayHello() {
      console.log(`Hi! My name is ${this.name}`);
    }
  };

  return Person;
}());

const me = new Person('Lee');

// constructor 프로퍼티가 생성자 함수를 가리킨다.
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false

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

  • 프로토타입은 생성자 함수의 prototype프로퍼티 뿐만 아니라 인스턴스의 __proto__접근자 프로퍼티를 통해서도 접근할수 있다.
  • 생성할때 변경하는 것이 아니고 이미 생성된 객체의 프로토타입을 교체한다.
function Person(name) {
    this.name = name;
  }
  
  const me = new Person('Lee');
  
  // 프로토타입으로 교체할 객체
  const parent = {
    sayHello() {
      console.log(`Hi! My name is ${this.name}`);
    }
  };
  
  // ① me 객체의 프로토타입을 parent 객체로 교체한다.
  Object.setPrototypeOf(me, parent);
  // 위 코드는 아래의 코드와 동일하게 동작한다.
  // me.__proto__ = parent;
  
  me.sayHello(); // Hi! My name is Lee

// 교체한 객체에는 constructor프로퍼티가 없어 생성자 함수간의 연결이 파괴된다.

// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true
function Person(name) {
    this.name = name;
  }
  
  const me = new Person('Lee');
  
  // 프로토타입으로 교체할 객체
  const parent = {
    // ① constructor 프로퍼티와 생성자 함수 간의 연결을 설정
    constructor: Person,
    sayHello() {
      console.log(`Hi! My name is ${this.name}`);
    }
  };
  
  // ② 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 설정
  Person.prototype = parent;
  
  // ③ me 객체의 프로토타입을 parent 객체로 교체한다.
  Object.setPrototypeOf(me, parent);
  // 위 코드는 아래의 코드와 동일하게 동작한다.
  // me.__proto__ = parent;
  
  me.sayHello(); // Hi! My name is Lee
  
  // constructor 프로퍼티가 생성자 함수를 가리킨다.
  console.log(me.constructor === Person); // true
  console.log(me.constructor === Object); // false
  
  // 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨다.
  console.log(Person.prototype === Object.getPrototypeOf(me)); // true
  1. parent객체에 constructor프로퍼티와 Person생성자 함수를 연결
  2. Person생성자 함수의 prototype프로퍼티를 바꿀 프로토타입으로 연결
  3. me인스턴스의 프로토타입을 parent로 변경

결론 : ES6에서 도입된 클래스를 사용해 간편하고 직관적으로 상속관계를 구현하는 편이 낫다.

instanceof 연산자

  • instanceof 연산자는 이항 연산자로 좌변에 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받는다.
// 생성자 함수
function Person(name) {
    this.name = name;
  }
  
  const me = new Person('Lee');
  
  // Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
  console.log(me instanceof Person); // true
  
  // Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
  console.log(me instanceof Object); // true

직접상속

Object.create에 의한 직접 상속

  • Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다.
  • new연산자 없이도 객체 생성
  • 프로토타입을 지정하면서 객체 생성
  • 객체 리터럴에 의해 생성된 객체도 상속 가능
// 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치한다.
// obj → null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true
// Object.prototype을 상속받지 못한다.
console.log(obj.toString()); // TypeError: obj.toString is not a function

// obj → Object.prototype → null
// obj = {};와 동일하다.
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

// obj → Object.prototype → null
// obj = { x: 1 };와 동일하다.
obj = Object.create(Object.prototype, {
  x: { value: 1, writable: true, enumerable: true, configurable: true }
});
// 위 코드는 다음과 동일하다.
// obj = Object.create(Object.prototype);
// obj.x = 1;
console.log(obj.x); // 1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

const myProto = { x: 10 };
// 임의의 객체를 직접 상속받는다.
// obj → myProto → Object.prototype → null
obj = Object.create(myProto);
console.log(obj.x); // 10
console.log(Object.getPrototypeOf(obj) === myProto); // true

// 생성자 함수
function Person(name) {
  this.name = name;
}

// obj → Person.prototype → Object.prototype → null
// obj = new Person('Lee')와 동일하다.
obj = Object.create(Person.prototype);
obj.name = 'Lee';
console.log(obj.name); // Lee
console.log(Object.getPrototypeOf(obj) === Person.prototype); // true
  • Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않는다.
  • Object.create메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있기 때문이다.
// 프로토타입이 null인 객체, 즉 프로토타입 체인의 종점에 위치하는 객체를 생성한다.
const obj = Object.create(null);
obj.a = 1;

console.log(Object.getPrototypeOf(obj) === null); // true

// obj는 Object.prototype의 빌트인 메서드를 사용할 수 없다.
console.log(obj.hasOwnProperty('a')); // TypeError: obj.hasOwnProperty is not a function
  • 객체가 직접 호출하지 않고 Object.prototype.hasOwnProperty.call()메서드를 활용해 호출한다.
// 프로토타입이 null인 객체를 생성한다.
const obj = Object.create(null);
obj.a = 1;

// console.log(obj.hasOwnProperty('a')); // TypeError: obj.hasOwnProperty is not a function

// Object.prototype의 빌트인 메서드는 객체로 직접 호출하지 않는다.
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true

정적 프로퍼티/메서드

  • 정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드이다.

// 생성자 함수
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');
  };
  
  const me = new Person('Lee');
  
  // 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출한다.
  Person.staticMethod(); // staticMethod
  
  // 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
  // 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
  me.staticMethod(); // TypeError: me.staticMethod is not a function
  • 정적 프로퍼티/메서드는 생성자 함수가 아닌 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다. 그 이유는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니기 때문이다.

프로퍼티 존재 확인

in 연산자

  • in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다.
const person = {
    name: 'Lee',
    address: 'Seoul'
  };
  
  // person 객체에 name 프로퍼티가 존재한다.
  console.log('name' in person);    // true
  // person 객체에 address 프로퍼티가 존재한다.
  console.log('address' in person); // true
  // person 객체에 age 프로퍼티가 존재하지 않는다.
  console.log('age' in person);     // false

// in대신 ES6에서 도입된 Reflect.has메서드도 사용할 수 있다. in과 동일하게 동작한다

const person = { name: 'Lee' };

console.log(Reflect.has(person, 'name'));     // true
console.log(Reflect.has(person, 'toString')); // true

Object.prototype.hasOwnProperty 메서드

  • Object.prototype.hasOwnProperty 메서드를 사용해 객체에 특정 프로퍼티가 존재하는지 확인할 수 있다.
  • in 연산자와 차이점은 in 연산자는 그 객체가 속한 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티도 확인하지만 Object.prototype.hasOwnProperty는 객체 고유의 프로퍼티 키가 아닌 상속받은 프로토타입의 프로퍼티 키인경우 false를 반환
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age'));  // false
console.log(person.hasOwnProperty('toString')); // false

프로퍼티 열거

for…in

객체의 모든 프로퍼티를 순회하며 열거 하려면 for…in 문을 사용한다.

  • 프로퍼티 키를 순회한다.
  • 프로토타입 체인을 모두 순회한다.
  • 열거 값을 참조한다.
  • 심벌을 열거하지 않는다.
const person = {
    name: 'Lee',
    address: 'Seoul'
  };
  
  // for...in 문의 변수 prop에 person 객체의 프로퍼티 키가 할당된다.
  for (const key in person) {
    console.log(key + ': ' + person[key]);
  }
  // name: Lee
  // address: Seoul
  • for..in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로퍼티 중에서 프로퍼티 어트리뷰트[[Enumerable]]값이 true인 프로퍼티만 열거(enumeration)한다.
  • tostring 같은 경우 프로토타입 체인 상에 존재하지만 [[Enumerable]]값이 false이기 때문에 for…in문에서 열거되지 않는다.
  • 또한 for…in 문은 프로퍼티가 심벌인 경우에도 열거하지 않는다.
  • 만약 본인 것만 출력하려면 hasOwnProperty 메서드를 활용한다.

Object.keys/values/entries 메서드

  • for...in문은 객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거한다.
  • 고유의 프로퍼티만 열거하기 위해서는 Object.keys, Object.values, Object.entries 메서드 사용을 권장한다.
  • Object.keys
  • 객체 자신의 열거 가능한 enumerable 프로퍼티키를 배열로 반환
const person = {
    name: 'Lee',
    address: 'Seoul',
    __proto__: { age: 20 }
  };
  
  console.log(Object.keys(person)); // ["name", "address"]
  • Object.values
  • ES8에서 도입되었다.
  • 객체 자신의 열거 가능한프로퍼티 값을 배열로 반환한다.
console.log(Object.values(person)); // ["Lee", "Seoul"]
  • Object.entries
  • ES8에서 도입되었다
  • 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.
profile
Whether you're doing well or not, just keep going👨🏻‍💻🔥

0개의 댓글