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
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 함수 사용하실 일 많이 없으시죠?
근데 이제는 알고 있어야겠죠?
// 어디 안드로메다에서 생성된 변수 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
의 저자 카일 심슨이 지적한 바 있습니다.
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룰인 no-prototype-builtins
는 Object.prototype
메소드를 사용하는 것을 금지합니다.
가장 현실적인 방지 방법입니다.
다음처럼 builtin 메소드를 직접적으로 참조해서 사용할 경우 에러를 발생시킵니다.
/*eslint no-prototype-builtins: "error"*/
var hasBarProperty = foo.hasOwnProperty("bar");
var isPrototypeOfBar = foo.isPrototypeOf(bar);
var barIsEnumerable = foo.propertyIsEnumerable("bar");
오늘은 hasOwnProperty를 사용할때 조심해야 하는 이유에 대해 알아보았습니다.
난해할 수 있는 프로토타입 개념을 쉽게 표현하려고 했는데요, 도움이 되셨으면 좋겠습니다.
그럼 모두 안전 코딩 하세요 :)
읽어주셔서 감사합니다.
좋은 글 감사합니다!