SPACE RUMI

Hi, I am rumi. Let's Splattack!

[STUDY] 스터디/Deep Dive JS

js 프로퍼티 어트리뷰트 / js 프로토타입

백루미 2022. 11. 28. 10:44
반응형

Property Attribute

JS 내부적으로 실제로 동작하지만, 개발자가 접근할수 있도록 오픈된 프로퍼티는 아니다.
다만, 간접 접근 수단을 제공한다.

JS엔진은 프로퍼티 생성시 프로퍼티의 상태를 나타내는 어트리뷰트를 기본값으로 자동 정의한다.
모든 객체는 프로토타입 내부 슬롯 [[prototype]]을 가진다. 
이는 __proto__를 통해 접근이 가능하다. 

프로퍼티는 데이터 프로퍼티 접근자 프로퍼티가 있다.

const test = {}

test.__proto__

 

프로토타입 (Prototype)

프로토타입은 어떤 객체의 부모객체의 역할을 하는 객체다.
프로토타입은 자식객체에게 자신의 프로퍼티와 메서드를 상속한다.
하위객체는 부모객체의 프로퍼티나 메서드를 자유롭게 사용할수있다.
프로토타입은 생성자 함수가 생성되는 시점에 같이 생성된다.
프로토타입과 생성자 함수는 단독으로 존재할수없고, 언제나 쌍으로 존재한다.

 

Object.getOwnPropertyDescriptor()

프로퍼티 어트리뷰트를 간접적으로 확인할 수 있다.
descriptor는 기술자.. 뭔가를 기술하는 주체 라는 뜻을 가진다. (description이 설명)
프로퍼티 정보를 설명해주는~ 뭐 그런 메서드라고 이해하면 될듯하다.

const test = {
    one: 'test string',
    name: 'rumi',
}

Object.getOwnPropertyDescriptor(test, 'one') // (참조, key)
Object.getOwnPropertyDescriptors(test) // 모든 프로퍼티의 디스크립터 객체정보를 줌

 

접근자 함수 getter/setter

const test = {
	// 데이터 프로퍼티
    name:'rumi', 
    color:'red',

	// 접근자 프로퍼티
    get testName(){
        return this.name
    },
    set testName(name){
        this.name = name
    }
}

test.name // 'rumi'
test.testName // 'rumi'

test.testName = '100rumi'
test.name // '100rumi'

데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
접근자 프로퍼티는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.

 

Object.defineProperty()

프로퍼티를 추가하면서, 어트리뷰트를 새로 정의하거나 기존의 프로퍼티를 재정의 할 수 있다.
한번에 하나의 프로퍼티만 정의할수있다. Object.defineProperties 메서드를 사용하면 한번에 정의할수있다.

 

const user = {}

Object.defineProperty(user, 'name',{
    value: 'rumi',
    writable: true,
    enumerable: false,
    configurable: true,
})

Object.keys(user) // [] enumerable이 false이기때문에 열거불가능함. 빈배열로 나온다.

* Enum 은 Enumeration(열거, 목록, 일람표)의 약자다. enumerable은 열거 가능한 속성인지를 나타낸다.
이 값이 false라면 for...in 문이나 Object.keys등으로 열거할 수 없다.

* configure은 구성하다 라는 의미이고, configurable이 false라면 프로퍼티를 재정의 할 수 없다.(삭제, 수정 X)

 

객체 확장 금지 Object.preventExtensions

프로퍼티 추가가 금지된다.
확장 가능한 객체인지 확인하는 메서드는 Object.isExtensible 이다.

 

객체 밀봉 Object.seal

프로퍼티 추가, 삭제, 어트리뷰트 재정의가 금지된다. (읽기와 쓰기만 가능)
밀봉 여부 메서드는 Object.isSealed 이다.

 

객체 동결 Object.freeze

프로퍼티 추가, 삭제, 값 쓰기, 어트리뷰트 재정의가 금지된다. (읽기만 가능)
동결 객체 여부 메서드는 Object.isFrozen 이다.

 

객체 확장 금지, 객체 밀봉, 객체 동결은 1depth만 금지한다.
따라서 완전금지를 하려면 모든 depth까지 재귀적으로 돌아야한다.

function deepFreeze(target){
	if(target && typeof target === 'object' && !Object.isFrozen(target)){
            Object.freeze(target)
            Object.keys(target).forEach(key => deepFreeze(target[key]))
	}
    return target;
}

const user = {
    name: 'rumi',
    books: {
        dev : 'Modern JS Deep Dive',
        literature : 'Damian'
    }
}

deepFreeze(user); 

Object.isFrozen(user) // true
Object.isFrozen(user.books) // true

 

일급객체의 조건

1. 무명의 리터럴로 생성할 수 있다. (런타임에 생성이 가능하다.)
2. 변수나 자료구조에 저장할 수 있다.
3. 함수의 매개변수로 전달할 수 있다.
4. 함수의 반환값으로 사용할 수 있다.

함수는 일급객체다. 함수를 객체와 동일하게 사용할수있다는 의미다.

 

arguments객체

유사 배열 객체이면서 이터러블하다.
배열은 아니므로 배열메서드를 사용할경우 에러가 발생한다.

 

length 프로퍼티

함수객체의 length 프로퍼티는, 함수를 정의할때 선언한 매개변수의 개수이다.

function test(x,y,z){
	console.log(test.length);
}

test() // 3

 

name 프로퍼티

익명함수는 식별자를 이름으로 갖는다.
기명함수는 함수명을 이름으로 갖는다.
*(ES5에서는 ES6와 달리 익명함수의 name은 빈 문자열이다)

const testFunc = () => {
	console.log(testFunc.name)
}

testFunc() // testFunc


const testing = function testFunc2(){
	console.log(testFunc2.name) 
}

testing() // testFunc2

 

prototype 프로퍼티

생성자 함수로 호출할수있는 함수객체, constructor만이 소유하는 프로퍼티다.
** 화살표 함수는 non-constructor이므로 prototype 프로퍼티를 소유하지 않으며, 프로토타입도 생성하지않는다.

(function*(){}).hasOwnProperty('prototype'); // true 

// hasOwnProperty는 인수로 전달받은 프로퍼티 키가 객체 고유의 키인 경우에만 true,
// 상속받은 프로토타입의 프로퍼티인경우 false

생성자 함수에 의해 생성된 인스턴스내에 동작하는 메서드가 있으면, 비슷한 구조의 인스턴스를 여러개 생성할 때 동일한 메서드를 중복소유한다. 이는 메모리 성능상 좋지않은데, JS 프로토타입을 기반으로 상속을 구현하여 중복을 제거할 수 있다.

function Circle(radius){
	this.radius = radius;
}

Circle.prototype.getArea = function(){
	return Math.PI * this.radius ** 2;
}

const circle1 = new Circle(1);
const circle2 = new Circle(4);

console.loc(circle1.getArea === circle2.getArea) // true
console.log(Circle) // ƒ Circle(radius){this.radius = radius;}

Circle 생성자가 만든 인스턴스는, Circle.prototype의 모든 프로퍼티와 메서드를 상속받기때문에.
Circle상에서는 안보이지만 사용할수있는 메서드다. 동일한 메서드는 상속을 통해 공유하면 중복을 제거할수있다. (코드의 재사용)

모든 객체는 하나의 프로토타입을 가지며, 생성자 함수와 연결되어있다.
프로토타입 내부슬롯에 직접 접근할수는없으나, __proto__ 접근자 프로퍼티를 통해 간접적으로 접근할 수 있다.
Object.prototype의, __proto__ 접근자 프로퍼티는 상속을 통해 사용한다.

 

prototype 체인

객체의 프로퍼티(+ 메서드)에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없으면 내부 슬롯의 참조를 따라 부모 프로토타입의 프로퍼티를 순차적으로 검색한다. 자바스크립트는 prototype 체인을 통해 상속을 구현한다.
프로토타입 체인의 최상위는 언제나 Object.prototype 이다.

 

왜 접근자 프로퍼티로 간접적 접근을 할까?

상호 참조에 의해 프로토타입 체인이 생성되는것을 방지한다.
프로토타입 체인은 단방향으로 구현되어야한다. (프로퍼티 검색 방향이 한쪽방향으로만 이동해야 한다)

하지만 코드에서 __proto__접근자를 사용하는것은 권장하지 않는다.
모든 객체가 사용할수있는것은 아니기 때문이다.

 

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

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

	User.prototype.greeting = function(){
		console.log(`hello ${this.name}`)
	};
    return User;
}());

const me = new User('rumi'); // 생성자로 함수 만듬

// 인스턴스 메서드로 오버라이딩. 이 시점에서 프로토타입 메서드는 섀도잉처리된다.
me.greeting = function(){
    console.log(`instance method call, ${this.name}`);
}

me.greeting(); // 인스턴스 메서드가 호출됨. instance method call, rumi 출력
delete me.greeting // 인스턴스 메서드 제거

me.greeting() // 인스턴스 메서드는 제거되고 프로토타입 메서드의, Hello rumi 출력

delete me.greeting  // 프로토타입 메서드는 삭제되지 않음.

하위 객체에서는 프로토타입 프로퍼티를 set할수없다. get만 된다.
프로토타입 프로퍼티를 제거하려면 직접 해당 프로토타입에 접근해야한다.

 

delete User.prototype.greeting;

me.greeting() // TypeError me.greeting is not a function

 

instanceof 연산자

우항의 프로토타입이, 좌항의 프로토타입 체인상에 존재하는지 확인한다.

// Object instanceof 생성자함수

function User(name){
	this.name = name
}

const me = new User('rumi');

// User.prototype이 me 객체의 프로토타입 체인상에 존재하므로 true
console.log(me instanceof User); // true

 

in 연산자와 Reflect.has 메서드

프로퍼티가 존재하는지 확인한다.

const user = {
    name:'rumi',
    book: 'the sun'
}

console.log('name' in user) // true
console.log('age' in user) // false
console.log(Reflect.has(user, 'name')); //true

 

for ... in

const user = {
	name:'rumi',
    book:'The sun'
}

for(const key in user){ // 객체돌릴때 굿
	console.log(`${key}: ${user[key]}`)
}

key가 Symbol 인 프로퍼티는 열거하지 않음.
객체의 프로토타입 체인상에 존재하는 모든 프로토타입의 프로퍼티 중, 프로퍼티 어트리뷰트 [[Enumerable]]이 true인 값만 열거한다.

 

반응형