일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- dom
- array
- Push
- 모듈
- animation
- video
- 고차함수
- ES6
- 이벤트 루프
- ajax
- 클로저
- 문자열
- event
- 이벤트 위임
- json
- 이벤트
- IntersectionObserver
- ios
- 비동기
- Flex
- This
- async
- input
- scroll
- 배열
- slice
- object
- 스크롤
- 애니메이션
- Promise
- 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 |