| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
- 이벤트 루프
- dom
- 객체
- array
- 이벤트
- slice
- IntersectionObserver
- video
- Push
- object
- animation
- 문자열
- 이벤트 위임
- map
- ios
- json
- 클로저
- input
- event
- 배열
- ES6
- 스크롤
- ajax
- 비동기
- This
- Promise
- 애니메이션
- 모듈
- scroll
- Flex
- Today
- Total
FEDev Story
프로토타입 오염을 막기 위해 hasOwnProperty를 사용해라 본문
프로토타입 오염
function NaiveDict() { }
NaiveDict.prototype.count = function() {
var i = 0;
for (var name in this) { // counts every property
i++;
}
return i;
};
NaiveDict.prototype.toString = function() {
return "[object NaiveDict]";
};
var dict = new NaiveDict();
dict.alice = 34;
dict.bob = 24;
dict.chris = 62;
dict.count(); // 5
Array.prototype.first = function() {
return this[0];
};
Array.prototype.last = function() {
return this[this.length – 1];
};
var dict = new Array();
dict.alice = 34;
dict.bob = 24;
dict.chris = 62;
dict.bob; // 24
var names = [];
for (var name in dict) {
names.push(name);
}
names; // ["alice", "bob", "chris", "first", "last"]
위 코드는 편의를 위해서 Array.prototype에 추가된 메서드로 인해서 객체의 항목을 열거할 때 프로토타입 객체의 프로퍼티가 예상치 않게 나타나게 된다.
하지만 new Array()를 단순히 빈 객체 리터럴로 교체하면 프로토타입 오염에 훨씬 덜 민간해 진다.
var dict = {};
dict.alice = 34;
dict.bob = 24;
dict.chris = 62;
var names = [];
for (var name in dict) {
names.push(name);
}
names; // ["alice", "bob", "chris"]
가벼운 객체를 만들기 위해 객체 리터럴을 사용해야 하며 for...in 반복문을 오염시킬 수 있기 때문에, 절대 Object.prototype에 프로퍼티를 추가하지 말아야 한다.
프로토타입 오염을 막기 위한 null 프로토타입
ES5 이전에는 프로토타입이 비어있는 새로운 객체를 만드는 표준적인 방법이 없다.
function C() C.prototype = null;
하지만 이 생성자로 인스턴스를 만들면 여전히 Object의 인스턴스를 갖게 된다.
var o = new C(); Object.getPrototypeOf(o) === null; // false Object.getPrototypeOf(o) === Object.prototype; // true
하지만 Object.create 함수는 단순히 null 프로토타입 인자를 전달하면, 다음과 같이 진짜로 비어있는 객체를 만들 수 있다.
var x = Object.create(null); Object.getPrototypeOf(x) === null; // true
Object.create를 지원하지 않는 오래된 자바스크립트 환경에서는 객체 리터벌을 사용하여 새로운 객체의 프로토타입 연결을 null로 초기화할 수 있다.
var x = { __proto__: null };
x instanceof Object; // false (비표준)
이 문법은 동등하지만, Object.create를 사용하는 것이 더 신뢰할 만 하다.
프로토타입 오염을 막기 위한 hasOwnProperty
빈 객체 리터럴조차도 Object.prototype의 수많은 프로퍼티들을 상속한다.
var dic = {};
"alice" in dict; //false
"toString" in dict; //true
"valueOf" in dict; //true
hasOwnProperty 메서드를 사용하면 프로토타입 오염을 피할 수 있게 도와준다.
dic.hasOwnProperty("alice"); // false
dic.hasOwnProperty("toString"); // false
dic.hasOwnProperty("valueOf"); // false
dic.hasOwnProperty("alice") ? dict.alice : undefined;
dic.hasOwnProperty(x) ? dic[x] : undefined;
hasOwnProperty는 Object.prototype에서 상속된다. 하지만 dict 객체에 'hasOwnProperty'라는 이름의 항목을 저장한다면, 프로토타입의 메서드에는 더 이상 접근할 수 없다.
가장 안전한 방법은 hasOwnProperty를 dict의 메서드로써 호출하는 대신에 call 메서드를 사용하는 것이다.
var hasOwn = Object.prototype.hasOwnProperty;
혹은
var hasOwn = {}.hasOwnProperty;
hasOwn.call(dict, "alice");
이 방법은 수신자 객체의 hasOwnProperty 메서드가 오버라이딩되었는지와 상관없이 잘 작동한다.
다음과 같이 이 패턴을 Dict 생성자에 추상화시킬 수 있다.
function Dict (elements) {
this.elements = elements || {};
}
Dict.prototype.has = function(key){
//자신이 소유한 프로퍼티만
return {}.hasOwnProperty.call(this.elements, key);
};
Dict.prototype.get = function(key){
// 자신이 소유한 프로퍼티만
return this.has(key) ? this.elements[key] : undefined;
};
Dict.prototype.set = function(key, val){
this.elements[key] = val;
};
Dict.prototype.remove = function(key){
delete this.elements[key];
};
var dict = new Dict({
alice : 34,
bob : 24,
chris : 62
});
dict.has("alice"); //true
dict.get("bob"); //24
dcit.has("valueOf"); // false
몇몇 실행환경에서 특수한 프로퍼티 __proto__는 스스로의 오염 문제를 일으킬 수 있다. 어떤 실행환경에서 __proto__ 프로퍼티는 단순히 Object.prototype에서 상속되므로, 빈 객체는 (다행히도) 진짜로 비어 있다.
var empty = Object.create(null);
"__proto__" in empty;
// false (몇몇 실행환경에서)
var hasOwn = {}.hasOwnProperty;
hasOwn.call(empty, "__proto__");
// false (몇몇 실행환경에서)
다른 실행환경에서는 in 연산자만 true를 보이기도 한다.
var empty = Object.create(null);
"__proto__" in empty; // true (몇몇 실행환경에서)
var hasOwn = {}.hasOwnProperty;
hasOwn.call(empty, "__proto__"); // false (몇몇 실행환경에서)
또 다른 실행환경에서는 __proto__라는 인스턴스 프로퍼티가 있으면 영원히 모든 객체를 오염시키기도 한다.
var empty = Object.create(null);
"__proto__" in empty; // true (몇몇 실행환경에서)
var hasOwn = {}.hasOwnProperty;
hasOwn.call(empty, "__proto__"); // true (몇몇 실행환경에서)
즉 실행환경에 따라 다음 코드는 서로 다른 결과를 보일 수 있다.
var dict = new Dict();
dict.has("__proto__"); // ?
최대의 이식성과 안전을 위해, 모든 Dict 메서드에 "__proto__" 키를 위한 특별한 로직을 추가한다. 더 복잡하지만 안전한 구현을 보장한다.
function Dict (elements) {
this.elements = elements || {};
this.hasSpecialProto = false; // "__proto__"키를 가지는가?
this.specialProto = undefined; // "__proto__" 엘리먼트
}
Dict.prototype.has = function(key){
if (key === "__proto__") {
return this.hasSpecialProto;
}
//자신이 소유한 프로퍼티만
return {}.hasOwnProperty.call(this.elements, key);
};
Dict.prototype.get = function(key){
if (key === "__proto__") {
return specialProto;
}
// 자신이 소유한 프로퍼티만
return this.has(key) ? this.elements[key] : undefined;
};
Dict.prototype.set = function(key, val){
if (key === "__proto__"){
this.hasSpecialProto = true;
this.specialProto = val;
} else {
this.elements[key] = val;
}
};
Dict.prototype.remove = function(key){
if (key === "__proto__"){
this.hasSpecialProto = false;
this.specialProto = undefined;
} else {
delete this.elements[key];
}
};
이 구현은 실행환경에서 __proto__를 어떤 식으로 처리하더라도 동작을 보장한다. __proto__라는 이름을 가지는 프로퍼티를 처리하지 않도록 피하기 때문이다.
var dict = new Dict();
dict.has("__proto__"); // false
'Javascript > ★★★' 카테고리의 다른 글
| 이벤트 위임, 이벤트 버블링, 이벤트 캡처 (0) | 2021.10.01 |
|---|---|
| 간결한 표현식 만들기 (1) | 2016.07.04 |
| 자바스크립트 믹스인 패턴 (0) | 2016.07.03 |
| 자기실행 익명함수를 활용한 모듈패턴 (0) | 2016.04.04 |
| 클래스 기반과 프로토타입 기반의 자바스크립트 객체 만들기 (0) | 2016.03.30 |