[JavaScript] Property

soyeon·2022년 7월 7일
0

Property

: key와 value의 쌍으로 이루어져 있다.

JavaScript의 객체는 내부 slot과 내부 method를 가진다.

내부 slot -> [[...]]
내부 method -> [[...]]
-> 이것도 property이다. 개발자가 직접적으로 사용할 수 없다. JavaScript engine에 의해 사용한다.

var obj = {
    name: '홍길동'
};
console.dir(obj);

내부 슬롯 확인하기

obj . __Proto__ -> [[prototype]]

Property의 Key가 될 수 있는 것

: 문자열, symbol

Property의 Value가 될 수 있는 것

: JavaScript에서 value로 인식되는 모든 것
ex) 문자열, symbol, 숫자 ...
함수도 value가 될 수 있다. -> method

Property의 상세(Property Attribute)

: property를 생성할 때 해당 property의 상세를 나타내는 값. 기본적으로 정의된다. 내부 슬롯이라서 직접적으로 접근할 수 있는 방법이 없다. 대신 간접적인 사용은 가능하다.

  1. property의 값 [[Value]]

  2. property 값을 수정할 수 있는지 여부 [[Writable]]
    : 고정한다는 것은 key에 대한 value를 고정한다는 것이다.

  3. 해당 property가 열거될 수 있는지 여부 [[Enumerable]]
    : 열거될 수 있다면 for문을 돌릴 수 있다.

  4. property attribute를 재정의 할 수 있는지 여부 [[Configurable]]

property attribute 확인하기

: Object.getOwnPropertyDescriptor(), Object.getOwnPropertyDescriptors()

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

// property 한 개 가지고 올 때
console.log(Object.getOwnPropertyDescriptor(person, 'name'));

// property 모두를 가지고 올 때
console.log(Object.getOwnPropertyDescriptors(person));

실행 결과 : { value: 'Lee', writable: true, enumerable: true, configurable: true }
		   {
             name: {
             	value: 'Lee',
                writable: true,
                enumerable: true,
                configurable: true
             },
             age: { value: 20, writable: true, enumerable: true, configurable: true }
           }

Property define

: Object.defineProperty()

const person = {};

Object.defineProperty(person, 'name', {
    value: '홍길동',
    writable: false,
    enumerable: false,
    configurable: true
});

console.log(person);
console.log(Object.getOwnPropertyDescriptor(person, 'name'));

person.name = '아이유';  // writable이 false이기 때문에 바뀌지 않는다.

console.log(Object.keys(person));  // enumerable이 false이기 때문에 key들이 열거되지 않는다.

실행 결과 : {}  // 브라우저에서 확인 가능
           {
              value: '홍길동',
              writable: false,
              enumerable: false,
              configurable: true
           }

함수 객체의 property

: 일반 객체에 비해서 더 가지고 있는 property가 존재한다.

  • 함수 객체의 property 확인하기
function squre(number) {
    return number * number;
}

console.dir(squre);
  1. arguments property
    : arguments 유사배열 객체를 property의 value로 가지고 있다.
    함수 내에서 지역 변수처럼 사용한다.

  2. caller property
    : 함수 자신을 호출한 함수에 대한 reference를 가지고 있다.
    비표준. ECMA에는 표준으로 지정되어 있지 않다. 가능한 사용하지 않는 것이 좋다.

function foo(f) {  // 고차 함수
    return f();  // 콜백 함수
}

function bar() {
    return 'caller: ' + bar.caller;  // 자기 함수 안이기 때문에 함수 이름을 사용할 수 있다.
}

console.log(bar());

실행 결과 : caller: null
		   caller: function foo(f) {  // 고차 함수
    					return f();  // 콜백 함수
					}

// 브라우저에서 확인 가능하다.
  1. length property
    : 매개변수(parameter)의 개수이다.

  2. name property
    : 함수의 이름이다.

  3. prototype property
    : 해당 함수(생성자 함수)가 생성하는 instance가 내부 슬롯 [[Prototype]]으로 참조하는 객체를 가리킨다.
    constructor(함수 선언문, 함수 표현식, class)만 가질 수 있는 property이다.
    따라서 일반 객체(객체 literal), non-constructor(arrow function, ES6의 method)는 prototype property를 가지지 않는다.

생성자 함수, 상위 prototype 객체, instance

prototype

  • constructor 함수 객체에 여러 property가 존재한다.(arguments, caller, ...) 가장 마지막에 prototype property가 있다.

  • 생성자 함수이기 때문에 new를 사용하면 생성자 함수의 instance가 만들어진다.

  • 모든 객체는 [[Prototype]] 내부 슬롯을 가진다.
    [[Prototype]] 내부 슬롯은 자신의 상위 prototype 객체를 가리킨다.

  • 상위 prototype 객체는 생성자 함수 객체의 prototype property가 가리키는 객체이다.

  • [[Prototype]] 내부 슬롯은 script engine이 사용하는 것으로 개발자가 사용할 수 없다. 따라서 사용할 수 있게 접근자 property(__proto__)가 존재한다. 이 property도 상위 prototype 객체를 가리키고 있다.

전체그림

  • 접근자 property(__proto__)는 상속이 되었기 때문에 사용이 가능한 것이다.
    -> Object에서 상속. Object라는 생성자 함수에도 prototype 객체가 있다. (Object.prototype.__proto__)

  • 생성자 함수의 prototype 객체의 [[Prototype]] 내부 슬롯이 Object의 prototype 객체를 가리키고 있다. Object의 prototype 객체의 constructor는 Object 생성자 함수를 가리킨다.

  • 생성자 함수의 [[Prototype]] 내부 슬롯은 Function.prototype 객체를 가리킨다. Function.prototype 객체의 constructor는 Function이라는 생성자 함수를 가리킨다.

  • Object의 prototype 객체의 [[Prototype]]에는 null이 저장되어 있다.(prototype chain의 최상위)

prototype chain
: 내가 사용하려는 property를 chain을 따라서 찾아 가는 것
prototype chain이 생성자 함수 기준, 인스턴스 기준으로 2개가 존재하게 된다. Person -> Function -> Object
instace -> Person -> Object

__proto__ (접근자 property)

: [[Prototype]] 내부 슬롯의 참조를 따라갈 수 있다. Object prototype이 가지고 있는 property.

그런데, "__proto__" 표현이 code에 직접 나오는 것은 권장하지 않는다.

  • 상속 관계 바꾸기
const obj = {};

const parent = { x: 1 };

obj.__proto__ = parent;  // parent를 상속받을 수 있게 된다.

console.log(obj.x);

실행 결과 : 1
  • 상위 prototype 객체의 생성자 함수 이름 알아내기
const obj = {};  // 객체 literal로 만든 객체

console.log(obj.__proto__.constructor.name);

실행 결과 : Object

__proto__ property vs prototype property

소유의미
__proto__ property모든 객체prototype 객체의 참조
prototype propertyconstructorprototype 객체의 참조

객체 literal로 생성된 Object의 상위 prototype 객체

var obj = { name: '홍길동' };

객체구조

  • 객체가 생성되고 key와 value name과 '홍길동'이 들어간다.

  • 객체의 [[Prototype]] 을 따라가면 Object.prototype 객체가 있다.

  • Object.prototype의 constructor에는 Object 생성자 함수가 있다.

var obj = new Object()
obj.name = '홍길동';

: 두 방식으로 만들어진 객체는 결론적으로는 똑같이 만들어지지만 메카니즘이 다르기 때문에 똑같다고 할 수 없다.

  • 예제
// 함수 표현식
var foo = function() {};

console.log(foo.__proto__ === Function.prototype);

// foo에는 prototype이 없으므로 function의 prototype으로 간다.
console.log(foo.prototype.__proto__ === Object.prototype);

console.log(Object.prototype.__proto__);

console.log(foo.constructor === Function);

실행 결과 : true
		   true
		   null
		   true

상위 prototype / 생성자 함수

  • {} 객체 리터럴 - Object prototype / Object
  • 함수 리터럴 - Function prototype / Function
  • [1, 2, 3] 배열 리터럴 - Array prototype / Array

알아두어야 할 생성자 함수의 prototype 객체가 가지고 있는 property

  • constructor property
    : 생성자 함수 객체를 가리킨다.
    constructor_property

prototype 객체는 언제 생성될까?

: 생성자 함수와 같이 만들어진다.

이런 구조를 가지는 이유

: 상속을 구현하기 위해서(Inheritance), 코드의 재활용을 높이기 위해서

  • 함수 중복 문제
// 생성자 함수
function Circle(radius) {
    this.radius = radius;
    this.getDiameter = function() {
        return 2 * this.radius;
    }
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

// getDiameter 함수가 계속해서 만들어진다.
// 공통으로 하나 만들어지는 것이 좋다.
console.log(circle1.getDiameter === circle2.getDiameter);

실행 결과 : false

: 함수를 공통으로 사용하기 위해 Circle의 prototype 객체에 함수를 넣어 놓는다. instance의 객체에 함수가 존재하지 않으면 prototype chain을 통해 Circle의 prototype 객체를 찾아가기 때문에 공통으로 함수를 사용할 수 있다.

  • 해결 방법
// 생성자 함수
function Circle(radius) {
    this.radius = radius;

    Circle.prototype.getDiameter = function() {
        return 2 * this.radius;
    }
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.getDiameter === circle2.getDiameter);

실행 결과 : true

: Circle 객체의 prototype property를 찾아가면 prototype 객체를 찾을 수 있다. 이 객체에 함수를 property로 설정한다.

  • 공유 변수
// 생성자 함수
function Circle(radius) {
    this.radius = radius;

    Circle.prototype.getDiameter = function() {
        return 2 * this.radius;
    }

    // 공용변수
    Circle.prototype.name = '홍길동';
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.name, circle2.name);  // 홍길동 홍길동

// circle1에 새롭게 property를 추가하기 때문에 바뀌지 않는다.
//circle1.name = '아이유';
//console.log(circle1.name, circle2.name);  // 아이유 홍길동

Circle.prototype.name = '아이유';  // 이렇게 해야 바뀐다.
console.log(circle1.name, circle2.name); 

circle1.__proto__.name = '김연아';  // 이런 방법으로도 바꿀 수 있다.
console.log(circle1.name, circle2.name);

실행 결과 : 홍길동 홍길동
		   아이유 아이유
           김연아 김연아

: 상위로 prototype chain을 따라 올라가서 공유 변수의 값을 바꿀 수가 없다. 직접 접근해야 바꿀 수 있다.

Overriding / Property shadowing

: 만약 overriding이 발생하면 발생된 overriding에 의해서 숨겨진 prototype 메소드를 property shadowing 되었다고 한다.

function Person(name) {
    this.name = name;
    // instance 메소드
    // this.getName = function() {}
}

// prototype 함수 선언
Person.prototype.sayHello = function() {
    console.log(`안녕하세요. ${this.name}`);
}

// instance를 생성
const me = new Person('홍길동');

// overriding
me.sayHello = function() {
    console.log(`Hello ${this.name}`);
}

me.sayHello();

실행 결과 : Hello 홍길동

prototype 객체 변경

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

Person.prototype = {
    sayHello() {
        console.log('안녕하세요!');
    }
}

const me = new Person('홍길동');

console.log(me.constructor === Object);

실행 결과 : true
function Person(name) {
    this.name = name;
}

Person.prototype = {
    constructor: Person,
    sayHello() {
        console.log('안녕하세요!');
    }
}

const me = new Person('홍길동');

console.log(me.constructor === Person);

실행 결과 : true
function Person(name) {
    this.name = name;
}

const me = new Person('홍길동');

const parent = {
    sayHello() {
        console.log('안녕하세요!');
    }
}

// me의 prototype 객체를 parent로 바꾼다.
Object.setPrototypeOf(me, parent);

console.log(me.__proto__ === Person.prototype);

실행 결과 : false

static property / method

: property chain에 속하지 못하기 때문에 instance로 사용하지 못한다. 함수로 직접 접근해서 사용해야 한다.

function Person(name) {
    this.name = name;
 	// instance 메소드
    this.callme = function() {}
}

// prototype 메소드
Person.prototype.sayHello = function() {
    console.log('안녕안녕!!');
}

// static 메소드
Person.staticMethod = function() {
    console.log('하이하이!');
}

정리

JavaScript engie이 시작 -> built-in 객체 생성 -> 전역 객체 생성(환경에 따라 만들어지는 객체가 다르다.) 브라우저에서는 window 객체가 생성된다. -> 코드를 읽으면서 함수 선언문을 만나면 함수 이름으로 된 property가 window에 생긴다. -> 메모리 어딘가에 있는 함수 객체를 property가 가리키고 있다. -> 모든게 window.으로 시작하기 때문에 생략하고 쓰지 않는 것이다.

변수도 마찬가지이다. const와 var로 변수를 선언하면 window의 property로 들어간다. let은 들어가지 않는다.

기억해야 할 property
prototype : 생성자 함수 객체
constructor : 생성자 함수의 prototype 객체
__proto__ : 인스턴스

0개의 댓글