# 프로퍼티가 뭘까? 다시 생각해보자.
const obj = {
key: "value",
};
뭐 이런식으로 생긴게 객체라고 생각을 한다..
저 코드만 봐서 보이는게 obj라는 식별자(객체참조)랑.. key, value라는 키,값이 쌍으로 보인다.
저 코드에선 객체, 키, 값은 명시적으로 보이지만 프로퍼티?라는건 사실 크게 안보인다.
그래서 프로퍼티라는 용어를 난 코딩할때 잘 안썼었음..
정확히는 저 키와 값 한쌍을 프로퍼티 하나라고 부른단다.
메서드는 뭐 다른 개념이고..
암튼 그룹개념으로 나눠보면..
뭐 이렇게 생각을 해보장
# 내부슬롯 내부 메서드
근데 저 위에 그림은 뭔가 좀 음.. 조금 안맞는 느낌이 있어서 그림을 그려보겠다.
일단 이 이유는 아래코드를 쳐보자
console.log(Object.getOwnPropertyDescriptor(obj, "key")); // 뭐시기나옴
저 코드는 프로퍼티의 어트리뷰트(속성)에 접근해서 객체로 가공해서 가져와주는거다.
뭐 사진처럼 이런식으로 나온다. 저 메서드는 프로퍼티 어트리뷰트에 접근하는 메서드인데 출력은 이렇게 된다.
그래서 그림을 다시 그려보면,
뭐 이렇게 생겼을것같다..
객체에서 키를 가지고 참조를 하면, 그 키에 연결된 프로퍼티에 접근해서 값(value)을 참조해준다
암튼 저 이상한 영어들은 뭔가 싶은데.. 이 프로퍼티의 어트리뷰트라고 한다.
모던 js에선 이걸 플래그 설명자?라고 하더라.
https://ko.javascript.info/property-descriptors
또 요상한 대괄호로 표기하는건 ECMA에서 내부슬롯을 대괄호 두개로 감쌌는데,
저것들은 내부슬롯이라고 보면되고 왠만해선 접근하지 못한다.
그래서 저 Object.getProperty~~메서드로 간접적으로 가져온거다. 이 메서드가 이 내부슬롯(프로퍼티 어트리뷰트)에 있는걸 끄집어내서 보여주는거다. 우리가 이런 명시적으로 쓰라고 만들어놓은 메서드말고는 내부슬롯에 접근, 재할당을 할수가 없다.
https://262.ecma-international.org/12.0/#sec-object-internal-methods-and-internal-slots
ECMA에서 설명한건데.. 뭐 이렇단다.
# 쟤들을 바꿔볼까?
const obj = {
key: "value",
};
console.log(Object.getOwnPropertyDescriptor(obj, "key")); // enumerable이 true임
Object.defineProperty(obj, "key", {
enumerable: false,
});
console.log(Object.getOwnPropertyDescriptor(obj, "key")); // enumerable이 false임
이런식으로 바꿔봤더니 됨.
defineProperty메서드로 저 프로퍼티어트리뷰트를 수정할 수 있다.
어? 내부슬롯에는 접근 못한다며? 라고 하지만 요거는 defineProperty메서드로 바꿀 수 있다.
다른 내부슬롯은 왠만해서는 안된다~
뭐 암튼 추가적으로 적어보자면..
Object.defineProperty(obj, "key", {
value: "바보",
}); // obj.key = '바보' 로 재할당한거랑 똑같음
요렇게 값을 바꿀 수 있다.
Object.defineProperty(obj, "key", {
enumerable: "앙",
});// 저 '앙'을 불린값으로 변환해서 갖다넣음
value는 어떤값이든 들어가지만, 다른 프로퍼티어트리뷰트에는 불린값만 들어가야한다.
'앙'이 스트링으로 true로 변환된거고 null을 넣으면 불린으로 변환되서 false가 들어간다. 암묵적타입변환..
뭐 이건 쓰잘데기없는거긴하지만 걍써봄.
# 프로퍼티 어트리뷰트(플래그) 뜯어보기
일단 객체를 좀 바꿔서 쓸게.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
console.log(Object.getOwnPropertyDescriptors(myHouse)); //얜 프로퍼티어트리뷰트 다 가져와보여줌
무찌도리가 재등장하였다..
저기 descriptor에 복수형으로 s붙이면 저렇게 다 가져와준다.
암튼 보자면
- value
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
console.log(Object.getOwnPropertyDescriptors(myHouse));
console.log(myHouse.dog1); // 무찌가 튀어나옴
Object.defineProperty(myHouse, "dog1", {
value: "엉덩",
});
console.log(Object.getOwnPropertyDescriptors(myHouse));
console.log(myHouse.dog1); //엉덩이로 바뀜
dog1에 있던 무찌가 엉덩이로 바뀌었다.
저 [[value]]에있는 값을 가져와주는게 객체에서 키값 참조한거다.
뭐 이건 단순해서 안씀.
- writable (쓸 수 있는)
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
writable: false,
});
myHouse.dog1 = "엉덩";
console.log(myHouse.dog1); // 무찌, dog1이 엉덩이로 바뀌지 않았다..
얘는 재할당을 못하게되서 저 dog1은 읽기전용 프로퍼티가 되는거란다.
delete myHouse.dog1;
console.log(myHouse); // 무찌가 삭제되었다. { dog2: '도리' }
근데 얘는 delete연산자 같은걸로 프로퍼티 삭제는 된다.
- enumerable (열거할 수 있는)
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
for (key in myHouse) {
console.log(myHouse[key]);
} //무찌 도리 순서대로 나옴
이걸 enumerable을 false로 해보면..
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
enumerable: false,
});
for (key in myHouse) {
console.log(myHouse[key]);
} //도리만 나옴..
이렇게 반복문같은걸로 dog1이 열거할 수 없게 되버린다. Object.keys로도 안된다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
console.log(Object.keys(myHouse)); //dog1, dog2둘 다 나옴
Object.defineProperty(myHouse, "dog1", {
enumerable: false,
});
console.log(Object.keys(myHouse));// dog2만 나옴
- configurable (변경가능한)
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
configurable: false,
});
delete myHouse.dog1;
console.log(myHouse.dog1); // 무찌, dog1이 사라지지 않았다..
delete로 myHouse의 dog1을 없애지 못한다..
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
configurable: false,
});
Object.defineProperty(myHouse, "dog1", {
configurable: true,
}); // 타입 에러뜸!!
또 configurable이 false면, 이후로 어트리뷰트들을 다시 재지정못하게 한다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
configurable: false,
});
myHouse.dog1 = "엉덩";
console.log(myHouse.dog1); //엉덩이 출력
Object.defineProperty(myHouse, "dog1", {
writable: false,
}); // 에러안뜸
다만 예외적으로 configurable이 false고, writable이 true인 경우면 dog1 값을 바꾸는건 가능,
프로퍼티어트리뷰트 writable을 false로 바꾸는것도 된다.
모던 js에서는 애초에 configurable이 프로퍼티 삭제나 프로퍼티어트리뷰트를 변경 못하게 하기위한거라 그렇단다.
뭐 하여간 복잡한데 이런걸로 객체의 프로퍼티마다 커스텀을 할 수 있다 ㅎㅎ
참고로 저렇게 프로퍼티어트리뷰트 재정의는 에러띄워주는데 프로퍼티 재할당을 할때는 에러안띄우고 무시한다..
use strict모드에서는 에러띄워주니까 왠만해서 이거쓰려면 use strict를 쓰는게 좋을듯..?
# 프로퍼티 어트리뷰트의 기본값
일반적으로 객체를 만들고 프로퍼티를 만들거나 추가할때 이 프로퍼티 어트리뷰트들은 true로 자동지정이 된다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
console.log(Object.getOwnPropertyDescriptors(myHouse));
때문에 우리가 객체를 다시 지지고 볶고 할 수 있는거다.
다만 defineProperty메서드로 프로퍼티를 새로만들때는 기본값이 달라진다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "host", {
value: "부엉이",
});
console.log(Object.getOwnPropertyDescriptor(myHouse, "host"));
새로운 프로퍼티를 defineProperty메서드로 만들었다. 키는 host, 값은 부엉이(me)로 만들었다.
요렇게 명시적으로 value를 정해준거 말고는 프로퍼티 어트리뷰트 모두 false로 뜬다.
참고로 value까지 비워두면 value에 undefined로 넣어줌..
+ 다만 이미 만들어진 프로퍼티 어트리뷰트를 하나만 설정하면..
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "dog1", {
configurable: false,
});
console.log(Object.getOwnPropertyDescriptor(myHouse, "dog1"));
//configurable만 바뀜, { value: '무찌', writable: true, enumerable: true, configurable: false }
# nodejs에서는 저게 좀 이상함..
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "host", {
value: "부엉이",
});
console.log(Object.getOwnPropertyDescriptor(myHouse, "host")); //아래 사진마냥 잘뜸
console.log(myHouse); // 아래 사진마냥 host가 없는 객체 참조해줌 ㅡㅡ
이렇게 getOwnPropery~로 가져온건 분명 host라는 프로퍼티가 있지만,
객체를 참조해보면 host프로퍼티가 없다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "host", {
value: "부엉이",
enumerable: true,
});
console.log(myHouse); // 잘가져와줌
여기서 host의 enumerable을 true로 하면 가져와준다.,
그래서 객체참조자체가 반복문마냥 하나씩 다 드가서 가져오는가 싶었는데 브라우저환경에서는 다 정상작동한다..
이유는 모르겠따.. MDN에서도 다 nodejs호환된다든데..
이런경우 치명적일 수 있을듯..
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
Object.defineProperty(myHouse, "host", {
value: "부엉이",
});
console.log(JSON.stringify(myHouse)); //host가 없음 ㅡㅡ
만약 nodejs로 백단돌릴거고 이걸 서버로 보내주려고할때, 보통은 JSON으로 보내는데,
JSON으로 바꿔도 여기엔 host가 없다 ㅡㅡ
저 왠만해선 enumerable을 true로 두고 써야겠다.
암튼 이렇게 생략하면 기본값은,
요렇단다. 이 기본값은 객체만들때 정한 프로퍼티의 기본값이 아니라, 만든객체에 새로운 프로퍼티를 Object.defineProperty메서드로 새로 만들었을때 생략하면 정해지는 기본값이다.
get,set은? 접근자프로퍼티 쓸때 설명할거임.
# 데이터프로퍼티
const myHouse = {
dog1: "무찌",
dog2: "도리",
};
console.log(Object.getOwnPropertyDescriptors(myHouse)); //얘네 다 데이터프로퍼티임
우리가 아까만들었던 객체에서 모든게 다 데이터 프로퍼티다.
데이터프로퍼티는 지금까지 설명했던 프로퍼티 어트리뷰트 value, writable, enumerable, configurable을 가지고 있다.
# 값이 없는 프로퍼티가 있다? -> 접근자 프로퍼티
일단 getOwnPropertyDescriptor로 어떤 객체의 프로퍼티의 프로퍼티어트리뷰트를 가져와 보겠다
위에 데이터프로퍼티에 있었던 value, writable이 없다?!
이게 뭘까?
그럼 전체 코드를 가져와보면..
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
get과 set이 붙은 메서드같은걸? 보면 teamName이라는 메서드같이 써져있다.
이게 접근자 프로퍼티다.
이 접근자 프로퍼티는 함수다. 직접적인 값이 없는 프로퍼티다. 이건 메서드가 아니니깐 조심해!
- get함수 (getter함수)
얘는 이 teamName프로퍼티키로 읽으려할때 return한 값을 참조하게 한다.
get함수는 즉 값을 참조하려고 할때 작동한다.
- set함수 (setter함수)
얘는 이 teamName프로퍼티를 수정하려고 할때(재할당같은거) 이때 작동하는 코드이다.
set함수는 꼭 파라미터를 넣어줘야하는데 이 파라미터는 이 teamName에 재할당하려는 표현식을 의미한다.
이걸 함수내에서 파라미터로 가져와서 요러쿵저러쿵 할 수 있는거다.
set함수는 재할당, 즉 값을 변경하려고 할때 작동한다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
console.log(myHouse.teamName); // get함수에서 리턴한 무찌도리듀오를 출력해줌
myHouse.teamName = "빵꾸똥꾸듀오"; // 이 코드에선 set함수가 호출되서 다른프로퍼티들도 바꿔줌
console.log(myHouse); //{ dog1: '빵꾸', dog2: '똥꾸', teamName: [Getter/Setter] }
요러케 get, set함수가 작동을 한다
이 함수들 내에서 커스텀을 할 수 있는게..
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
if (!this.dog1 || !this.dog2) {
throw Error; //요런식으로 dog1이나 dog2프로퍼티가 없으면 에러를 던져줄 수 있음.
}
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
if (name.length < 4) {
throw Error; //요런식으로 새로지정하려는 teamName길이가 짧으면 에러를 던져줄 수 있음.
}
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
요렇게 조건을 붙여서 에러를 띄워준다거나 뭐 이것저것 코딩해서 집어 넣을 수 있다.
이러케 객체의 프로퍼티를 커스텀할 수 있다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
if (!this.dog1 || !this.dog2) {
throw Error;
}
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
if (name.length < 4) {
throw Error;
}
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
console.log(myHouse); //{ dog1: '무찌', dog2: '도리', teamName: [Getter/Setter] }
console.log(JSON.stringify(myHouse)); //{"dog1":"무찌","dog2":"도리","teamName":"무찌도리듀오"}
//제대로 JSON형식으로 만들어줌.
JSON으로 바꿀때 teamName프로퍼티를 어케해주나 했는데 그냥 콘솔로 찍을때처럼 말고 함수호출되서 리턴한 결과값을 값으로 잘 가져와 준다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
if (!this.dog1 || !this.dog2) {
throw Error;
}
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
if (name.length < 4) {
throw Error;
}
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
const afterJson = JSON.parse(JSON.stringify(myHouse));
console.log(afterJson); //{ dog1: '무찌', dog2: '도리', teamName: '무찌도리듀오' }
// get set 함수로 정의했던 접근자 프로퍼티 teamName이 데이터프로퍼티로 바뀌어버림 ㅇㅇ
하지만 JSON자료를 다시 풀면 접근자프로퍼티가 아닌 데이터프로퍼티가 되어버린다.
뭐 당연한게 JSON은 메서드나 함수같은거 못보내니까..
# 객체 설정 방지 메서드
요런 메서드가 있단다. 이 메서드를 어떤 객체에 쓰면 저렇게 설정이 된단다.
# 스트릭모드
위 설정방지 메서드를 썼다면 strict모드를 해야 에러가 잘뜬다.
# 접근자 프로퍼티의 writable을 false로 한다면?
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
Object.defineProperty(myHouse, "teamName", {
// configurable: false,
writable: false,
});
console.log(myHouse); // teamName이 undefined로 됨.
console.log(myHouse.teamName); // undefined
writable을 false로 바꾸니 아예 이 접근자 프로퍼티가 사라져버림..
애초에 writable은 데이터프로퍼티에만 있는거니까.. 자동적으로 바뀌고 저 defindProperty가 새로 프로퍼티를 재할당하는방향으로 가는듯.
# 접근자 프로퍼티의 configurable을 false로 한다면?
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
set teamName(name) {
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
Object.defineProperty(myHouse, "teamName", {
configurable: false,
});
console.log(Object.getOwnPropertyDescriptor(myHouse, "teamName")); //configurable만 false로 바뀜
myHouse.teamName = "바보멍청듀오";
console.log(myHouse); //{ dog1: '바보', dog2: '멍청', teamName: [Getter/Setter] }
console.log(myHouse.teamName); //바보멍청듀오
configurable을 false로 바꿨다.
위에서 configurable이 false고 writable이 true면 재할당은 가능하다고 했는데, 애초에 접근자 프로퍼티에는 writable은 없다.
접근자 함수는 쓰기기능(재할당)을 애초에 set함수로 포함하고 있다.
말하고자 하는건, 접근자 프로퍼티의 configurable을 false로 바꿨다 치더라도 값을 쓸때(재할당할때) set함수는 정상작동한다는거다. 바꿀수 없는건 저 teamName이라는 접근자 프로퍼티의 get, set함수를 defineProperty메서드로 바꿀수 없는거다.
const myHouse = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
// set teamName(name) {
// this.dog1 = name[0] + name[1];
// this.dog2 = name[2] + name[3];
// },
};
myHouse.teamName = "바보멍청듀오";
console.log(myHouse); //{ dog1: '무찌', dog2: '도리', teamName: [Getter] }
console.log(myHouse.teamName); //무찌도리듀오, set함수가 없어서 아예 안바뀜
set함수를 주석처리해서 없앴다.
그이후 접근자프로퍼티인 teamName에 새로운 값을 재할당했지만, set함수가 dog1과 dog2를 안바꿨고 값을 참조할때 쓰이는 get함수에서 바뀌지않은 dog1, dog2를 참조했기때문에 무찌도리듀오는 바뀌지 않았다.
+ set함수만 있다면?
const myHouse = {
dog1: "무찌",
dog2: "도리",
// get teamName() {
// return this.dog1 + this.dog2 + "듀오";
// },
set teamName(name) {
this.dog1 = name[0] + name[1];
this.dog2 = name[2] + name[3];
},
};
myHouse.teamName = "바보멍청듀오";
console.log(myHouse); //{ dog1: '바보', dog2: '멍청', teamName: [Setter] }
console.log(myHouse.teamName); //undefined, get함수가 없으니 teamName을 참조못함 ㅇㅇ
반대로 set함수만 있다면 재할당시 바보멍청듀오가 set함수의 인수로 들어가서 dog1과 dog2는 바뀐다.
하지만 teamName프로퍼티를 참조하려고하면 get함수가 없어서 udefined가 뜬다.
'javaScript > jsDeepDive' 카테고리의 다른 글
메서드가 뭐야? (0) | 2023.06.22 |
---|---|
생성자 함수, prototype, __proto__, constructor 관계 구조. (0) | 2023.06.16 |
즉시실행함수 (0) | 2023.06.01 |
전역변수사용을 억제해야하는 이유? (0) | 2023.05.31 |
스코프에 관해서 (0) | 2023.05.26 |