hasOwnProperty를 사용하면 안되는 이유

dante Yoon·2022년 9월 14일
21

js/ts

목록 보기
3/14
post-thumbnail
post-custom-banner

잠깐, 영상으로 보고싶다면?

https://www.youtube.com/watch?v=ZeUdFdTq2gs

프로토타입

안녕하세요, 단테입니다.

자바스크립트에서는 프로토타입이라는 다른 프로그래밍 언어에서는 생소한 개념이 있는데요,

어떤 객체의 속성 값을 참조할 때 객체 자신의 속성 값으로는 존재하지 않아도 프로토타입 체이닝을 통해 속성 값을 참조할 수 있다면, 다음의 코드의 hi.bye에서 function bye에 선언한 this.bye를 참조할 수 있습니다.

function hello() {
  this.hello = "hello";
}

function bye() {
  this.bye = "bye by prototype chaining";
}

hello.prototype = new bye();
const hi = new hello();
console.log(hi.__proto__) // {bye: "bye by prototype chaining"}
console.log(hi.hello) // hello 
console.log(hi.hasOwnProperty("hello")); // true
console.log(hi.bye) // bye by prototype chaining
console.log(hi.hasOwnProperty("bye")); // bye

hasOwnProperty

Object.create(null)

ES5에 추가된 Object.create을 사용하면 특정 객체의 프로토타입을 명시적으로 지정해주며 빈 객체를 만들 수 있습니다.

특정 객체를 console.log로 찍어보았을 때 object literal 형식처럼 찍히면
개발자는 a 변수의 생성 근원지를 추적해보지 않는 이상 Object.create으로 객체가 생성이 되었는지,
{}로 선언이 되었는 알지 못합니다.

// 어디 안드로메다에서 생성된 변수 a
const a = Object.create(null)
// ... 
console.log(a) // {}
a.b = "hello";
console.log(a); // {b: "hello"}

해당 사항이 문제가 되는 이유는 변수 a가 Object.create(null)로 생성이 되었다면 Object.prototype 메소드를 사용할 수 없어 런타임 에러를 발생시킨다는 것입니다.

// 어디 안드로메다에서 생성된 변수 a

const a = Object.create(null)
// ...
console.log(a) // {}
a.b = "hello";
console.log(a); // {b: "hello"}
console.log(a.__proto__) // undefined
console.log(a.hasOwnProperty()) // Error hasOwnPropety is not a function

쉐도잉

객체 속성 선언을 통해 객체 프로토타입 체이닝을 이용하지 못하는 현상을 쉐도잉(shadowing)이라고 합니다.

다음 코드에서는 변수 a에 명시적으로 hasOwnPropety 객체 속성을 지정해주었을 때 일어날 수 있는 쉐도잉 문제점을 지적합니다.

const a = Object.create(null)
console.log(a) // {}
a.b = "hello";
console.log(a); // {b: "hello"}
console.log(a.__proto__) // undefined
a.hasOwnProperty = function() {
  return true;
}
console.log(a.hasOwnProperty("notExist")) // 항상 true

보안 이슈

위의 쉐도잉을 이용해 nodejs 서버에 DOS 공격이 들어올 수 있습니다.

다음과 같은 json 형식을 클라이언트에서 서버로 전달받았을 때 nodejs 서버에서 parse된 객체를 참조한다면, 서버가 런타임 에러를 일으킬 수 있습니다.

{"hasOwnProperty": 1}
const json = {"hasOwnProperty": 1}
const parse = JSON.parse(json);
parse.hasOwnProperty("") // Error hasOwnProperty is not callable.

위와 같은 이유로 hasOwnPropety를 포함하여 Object.prototype 메소드를 사용할 때는 조심해야 합니다.

그럼 어떻게 해야되는데요?

객체 생성의 근원지를 장담할 수 없는 경우에는 다음과 같이 사용하셔야 합니다.

Object.prototype.hasOwnProperty.call(foo, "bar");

call, apply 함수 사용하실 일 많이 없으시죠?
근데 이제는 알고 있어야겠죠?

safe call?

// 어디 안드로메다에서 생성된 변수 a

const a = Object.create(null)
// ...
console.log(a) // {}
a.b = "hello";
console.log(a); // {b: "hello"}
console.log(a.__proto__) // undefined
console.log(a.hasOwnProperty()) // Error hasOwnPropety is not a function

앞서 Object.create(null)로 객체 생성시 hasOwnPropety is not a function 처럼 에러가 날 수 있는 상황을 대비해 런타임 에러를 방지할 수 있는 수단이 있습니다.

optional-call operator

정식 명칙은 Optional chaining with function calls입니다.

obj.val?.prop
obj.val?.[expr]
obj.func?.(args)

비단 함수 호출 뿐만이 아니라 속성 참조 시에도 사용할 수 있는 이 문법은 함수가 존재하지 않는 경우에 런타임 에러를 방지할 수 있게 해줍니다.

console.log(a.hasOwnProperty()) // undefined

근데 이 방법은 지양해야 합니다.
자칫 안전하게 함수를 호출하는 처럼 보이지만 그렇지 않습니다.

obj.func?.(args)는 어떤 의미를 가지고 있나요?
이렇게 해석하지 않으세요?

이 함수가 정의되어 있다면 호출해라

?. operator는 func이 함수임을 보장하지 않습니다.
rmwj obj가 nullish한지만 체크합니다.

?. 옆에 () 처럼 함수 호출식이 적혀있기 때문에 마치
non-nullish하고 callbale 두 가지 조건을 모두 만족할 경우에만 호출해라,
즉 safe call 기능을 하는 것 처럼 오해를 가져다 줄 수 있습니다.

optional chaning function call은 safe call이 아닙니다.
꼭 기억하세요.

이 문제는 you don't know js의 저자 카일 심슨이 지적한 바 있습니다.

ES2022 hasOwn

Object.create(null)에서 파생된 문제를 피할 수 있게하는 Object.hasOwn 메소드가 ES2022에서 소개되었습니다.

하지만 node18버전 이상, 최신 브라우저에서만 사용이 가능하므로 Object.create(null) 문제에 한정되서만 제한된 해결방법을 제시한다고 할 수 있습니다.

const a = Object.create(null)
console.log(a) // {}
a.b = "hello";
console.log(a); // {b: "hello"}
console.log(a.__proto__) // undefined

console.log(Object.hasOwn(a,"b")) // true

ESLint

eslint룰인 no-prototype-builtinsObject.prototype 메소드를 사용하는 것을 금지합니다.

가장 현실적인 방지 방법입니다.
다음처럼 builtin 메소드를 직접적으로 참조해서 사용할 경우 에러를 발생시킵니다.

/*eslint no-prototype-builtins: "error"*/

var hasBarProperty = foo.hasOwnProperty("bar");

var isPrototypeOfBar = foo.isPrototypeOf(bar);

var barIsEnumerable = foo.propertyIsEnumerable("bar");

오늘은 hasOwnProperty를 사용할때 조심해야 하는 이유에 대해 알아보았습니다.
난해할 수 있는 프로토타입 개념을 쉽게 표현하려고 했는데요, 도움이 되셨으면 좋겠습니다.
그럼 모두 안전 코딩 하세요 :)
읽어주셔서 감사합니다.

profile
성장을 향한 작은 몸부림의 흔적들
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 9월 23일

좋은 글 감사합니다!

1개의 답글