getter, setter 프로퍼티
# 왜 포스팅하니?
getter setter 프로퍼티를 접하고 신기해서 써봄.. 이 문법에대해서 사실 몰랐다.
근데 생각보다 꽤나 유용할것 같아서 포스팅하려고 함.
# 내가 문제로 삼았던것.
const obj = {
dog1: "무찌",
dog2: "도리",
teamName: obj.dog1 + obj.dog2+'듀오',
};
위 코드는 에러가 뜬다
요렇게 참조에러가 뜬다. teamName프로퍼티가 dog1과 dog2를 참조하는건데 teamName프로퍼티가 자기자신 obj를 참조하는 시점이 실행컨텍스트의 코드평가할때 참조하려고해서 참조에러가 뜨는거다. 호이스팅이 발생하는거지.
그래서 이걸 억지로 만들어보려면..?
- 즉시실행함수
const obj = {
dog1: "무찌",
dog2: "도리",
};
(function () {
obj.teamName = obj.dog1 + obj.dog2 + "듀오";
})();
console.log(obj.teamName); // 무찌도리듀오 출력
즉시실행함수로 프로퍼티를 추가해주었다.
다만 이건 dog1과 dog2가 바뀔때 즉시실행함수가 또 호출되지않아서 초기설정만 가능하다.
뭐 굳이 즉시실행함수까지도 필요없고 걍 obj.teamName = obj.dog1+obj.dog2+'듀오'로 해줘도 되긴함..
const obj = {
dog1: "무찌",
dog2: "도리",
teamName() {
return this.dog1 + this.dog2 + "듀오";
},
};
console.log(obj.teamName());
뭐 이런식으로 메서드호출방식으로 해도 된다.
이건 dog1과, dog2가 바뀌더라도 teamName()을 호출할때마다 동적으로 리턴값이 바뀐다.
다만 호출을 해야하기때문에 프로퍼티라기보단 메서드로 보는게 맞다.
const obj = {
dog1: "무찌",
dog2: "도리",
teamName: (function () {
return this.dog1 + this.dog2 + "듀오";
})(),
};
console.log(obj.teamName);
teamName에 즉시실행함수로 값을 리턴하는 방식은 역시나 obj를 실행컨텍스트 평가단계에서 프로퍼티 teamName을 바인딩하는과정에서 이 obj를 참조해야하기때문에 호이스팅에러가 뜨게 된다.
# 위 방식으로 억지로 접근자프로퍼티같은 프로퍼티 만들기?
위에서 봤을때 메서드로 하는방법만이 유일하다..
그렇다면 이 방식으로 억지로 접근자프로퍼티같은 메서드?를 만들어보자.
const obj = {
dog1: "무찌",
dog2: "도리",
teamName(how, value) {
if (how == "get") {
return this.dog1 + this.dog2 + "듀오";
} else if (how == "set") {
this.dog1 = value[0] + value[1];
this.dog2 = value[2] + value[3];
} else {
return;
}
},
};
console.log(obj.teamName("get")); // 무찌도리듀오
obj.teamName("set", "바보똥개듀오"); // 프로퍼티 바꿈
console.log(obj.teamName("get")); // 바보똥개듀오
console.log(obj.dog1); // 바보
console.log(obj.dog2); // 똥개
뭐이런식으로 단순하게 만들어보았다.
호출을 해야하고.. set을 인수로 넣어 또 따로 호출해줘야하고.. 짜증나게 생겼다.
때문에 이건 그냥 접근자 프로퍼티로 만드는게 속편하다.
const obj = {
dog1: "무찌",
dog2: "도리",
get teamName() {
return this.dog1 + this.dog2 + "듀오";
},
set teamName(newTeamName) {
this.dog1 = newTeamName[0] + newTeamName[1];
this.dog2 = newTeamName[2] + newTeamName[3];
},
};
console.log(obj.teamName); // 무찌도리듀오
obj.teamName = "바보똥개듀오"; // 이렇게하면 obj의 dog1과 dog2가 전부 새로 세팅된다.
console.log(obj.dog1); // 바보
console.log(obj.dog2); // 똥개
console.log(obj.teamName); // 바보똥개듀오
내가 중요하게 생각하는 점은 접근자프로퍼티는 현재 자신이 속한 객체를 참조할수 있다는것이다.
하지만 이 접근자 프로퍼티는 정확히 프로퍼티가 아니라 메서드로 이루어져있다고 봐야할것이다.
teamName이라는 프로퍼티를 참조할때는 get teamName메서드가 호출되고, 할당할떄는 set teamName이라는 메서드가 호출된다고 보면 된다.
이걸로 프로젝트에서 다크모드를 구현할때 쓰면될듯..?
아래 대충 만들어서 써놨음.
# 검사해주는 접근자 프로퍼티 만들기
위에까지는 한 프로퍼티가 동일객체 다른 프로퍼티를 참조할때를 가정한것이다.
그러나 한 프로퍼티만을 참조, 할당할때 함수가 동작하게 하려면 예시가 있다.
모던 js 에서 본 내용이다.
https://ko.javascript.info/property-accessors
프로퍼티 getter와 setter
ko.javascript.info
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // 너무 짧은 이름을 할당하려 함
요 코드인데 오직 user이란 객체안에서는 name이라는 프로퍼티밖에없다.
다만 이렇게 한다면 name프로퍼티를 할당할때 setter메서드가 호출되어서 길이를 조절해준다.
대신 _name프로퍼티는 접근자프로퍼티 메서드내에서만 쓰는게 적절하단다.
이런 코딩방식은 약간 패턴으로 생각해도 되는듯..?
# 다크모드 예시코드
let mode = "dark";
const obj = {
get backgroundColor() {
if (mode == "dark") {
return "black";
} else {
return "white";
}
},
set backgroundColor(newColor) {
if (newColor == "dark") {
mode = "dark";
} else {
mode = "light";
}
},
};
console.log(obj.backgroundColor); // black 출력
obj.backgroundColor = "white";
console.log(obj.backgroundColor); // white 출력
console.log(mode); // light 출력
대충 다크모드 만들어봤다.
이건 get한정해서 삼항연산자로 쓸 수도 있다.
let mode = "dark";
const obj = {
backgroundColor: mode == "dark" ? "black" : "white",
};
console.log(obj.backgroundColor); // black
let mode = "light";
const obj = {
backgroundColor: mode == "dark" ? "black" : "white",
};
console.log(obj.backgroundColor); // white
다만 삼항연산자는 프로퍼티를 참조할때 작동하는것이 아니라, 처음 프로퍼티들이 할당될때 한번만 동작한다.
따라서 아래와같은 오류?아닌 병신같은 결과가 나올 수 있다.
let mode = "dark";
const obj = {
backgroundColor: mode == "dark" ? "black" : "white",
};
console.log(obj.backgroundColor); // black
mode = "light";
console.log(obj.backgroundColor);//black출력. mode는 바뀌었지만 삼항연산자 코드는 동작하지 않음.
이렇게 삼항연산자는 처음 obj.backgroundColor가 할당될때 한번만 작동한다.
이걸 어떻게 해결하냐면,
let mode = "dark";
const obj = {
backgroundColor: mode == "dark" ? "black" : "white",
};
console.log(obj.backgroundColor); // black
const changeLight = () => {
if (mode == "dark") {
mode = "light";
obj.backgroundColor = "white";
} else {
mode = "dark";
obj.backgroundColor = "black";
}
};
changeLight();
console.log(mode); //light
console.log(obj.backgroundColor); // white
changeLight();
console.log(mode); //dark
console.log(obj.backgroundColor); // black
이렇게 changeLight라는 함수를 만들어서 다크모드 바꾸는 버튼의 클릭이벤트로 넣었다.
뭐 이런식으로도 되긴 된다만 접근자프로퍼티가 더 좋겠지?
# 클래스에서 접근자 프로퍼티
const User = class {
constructor(name) {
this.name = name;
this.level = "normal";
}
get 존칭() {
return this.name + "님" + `(${this.level})`;
}
set 존칭(name) {
this.name = name.slice(0, -1);
}
};
유저 클래스를 만들었따.
그리고 접근자 프로퍼티로 존칭이라는 프로퍼티를 만들었다.
const user1 = new User("무찌");
console.log(user1.존칭); // 무찌님(normal)
console.log(user1); //User { name: '무찌', level: 'normal' }
user1.존칭 = "도리님";
console.log(user1.존칭); // 도리님(normal)
console.log(user1); //User { name: '도리', level: 'normal' }
요클래스로 user1을 만들었고 잘뜬다.
그리고 user1의 존칭을 '도리님'으로 바꾸니 이 클래스의 모든 프로퍼티가 잘 바뀌었다.
근데 user1을 봤는데 '존칭'으로 지정한 접근자프로퍼티가 없다. 이건 프로토타입 메서드로 정해줬기 때문이다.
여기서 Vip라는 확장클래스를 만들어봤다.
class Vip extends User {
constructor(para) {
super(para);
this.level = "vip";
}
}
const user2 = new Vip("도리");
console.log(user2); //Vip { name: '도리', level: 'vip' }
user2.존칭 = "똥개님";
console.log(user2); //Vip { name: '똥개', level: 'vip' }
console.log(user2.존칭); //똥개님(vip)
위와같이 User의 프로토타입메서드인 '존칭'접근자 프로퍼티가 Vip클래스로 생성된 user2에서도 잘 동작한다.
console.log(User.prototype); //여기엔 존칭이라는 접근자 프로퍼티로 존재
console.log(Vip.prototype); // 여기는 존칭이 있긴한데 접근자 프로퍼티가 아님
console.log(Object.getOwnPropertyDescriptor(User.prototype, "존칭")); // 접근자프로퍼티 잘뜸
console.log(Object.getOwnPropertyDescriptor(Vip.prototype, "존칭")); // undefined뜸
하지만 이 존칭 접근자 프로퍼티는 User.prototype 것이지 Vip.prototype꺼는 아니다.
그럼 오버라이딩을 테스트하기위해 새로운 클래스 Banned를 만들어봤다.
class Banned extends User {
constructor(para) {
super(para);
this.level = "강퇴당한고객";
}
get 존칭() {
return this.name + "새끼" + `(${this.level})`;
}
set 존칭(name) {
this.name = name.slice(0, -2);
}
}
const user3 = new Banned("무찌");
console.log(user3); //Banned { name: '무찌', level: '강퇴당한고객' }
// User의 존칭 접근자 프로퍼티를 덮어쓴걸 알 수 있다.
console.log(user3.존칭); // 무찌새끼(강퇴당한고객)
console.log(Object.getOwnPropertyDescriptor(User.prototype, "존칭")); // 접근자프로퍼티 잘뜸
console.log(Object.getOwnPropertyDescriptor(Banned.prototype, "존칭")); // 덮어쓴 접근자프로퍼티 잘뜸
결과적으로 프로토타입 메서드로 접근자 프로퍼티를 만들었지만 하위클래스에서도 다 잘 쓰고 있다.
동적으로 접근자 프로퍼티를 클래스마다 변경 및 부여하려면 오버라이딩을 하면 된다.
그럼 직접적으로 접근자 프로퍼티를 클래스에 넣고 싶다면?
const User = class {
constructor(name) {
this.name = name;
this.level = "normal";
Object.defineProperty(this, "존칭", {
get() {
return this.name + "님" + `(${this.level})`;
},
set(name) {
this.name = name.slice(0, -1);
},
});
}
};
const user1 = new User("무찌");
console.log(user1); //User { name: '무찌', level: 'normal' } 여기엔 존칭 프로퍼티가 안뜸
console.log(user1.존칭); // 무찌님(normal) 하지만 동작됨
user1.존칭 = "도리님";
console.log(user1); //User { name: '도리', level: 'normal' } set함수가 잘 동작함
console.log(Object.getOwnPropertyDescriptors(user1)); // 여기에선 존칭 프로퍼티가 잘 뜸
console.log(User.prototype); // 여기엔 '존칭'프로퍼티가 없다. 즉 프로토타입 메서드가 아니라, constuctor에 직접적으로 들어가는 프로퍼티라는 뜻.
요렇게 defineProperty메서드로 직접적으로 프로퍼티를 지정해주면된다.
그러면 User에서 만든 인스턴스에서 직접적으로 쓸 수 있는 접근자프로퍼티가 된다.
User의 하위 프로퍼티에선 super로 넘겨주지않는한 이 접근자프로퍼티를 사용할 수 없다.
++ 왜 user1을 참조해서 콘솔로 찍을때 접근자프로퍼티가 안떴을까?
const User = class {
constructor(name) {
this.name = name;
this.level = "normal";
Object.defineProperty(this, "존칭", {
get() {
return this.name + "님" + `(${this.level})`;
},
set(name) {
this.name = name.slice(0, -1);
},
enumerable: true,
configurable: false,
});
}
};
const user1 = new User("무찌");
console.log(user1); //User { name: '무찌', level: 'normal', '존칭': [Getter/Setter] }
//잘뜬다
enumerable을 true로 해주면 잘뜬다. ㅅㄱ