최근에 BEF - 177. Implement Object.groupBy() 문제를 풀면서
몰랐던 개념들을 꽤 많이 정리하게 되었다.
특히 아래 두 가지가 핵심이었다:
Object.groupBy()의 개념null-prototype object를 어떻게 만들어야 하는지Object.groupBy()는 배열의 요소들을 특정 기준으로 묶어서
객체 형태로 반환해주는 메서드이다. 이를 몰랐는데 문제를 풀면서 알게 되었다.
const arr = [1, 2, 3, 4, 5];
const result = Object.groupBy(arr, (num) => {
return num % 2 === 0 ? "even" : "odd";
});
console.log(result);
{
odd: [1, 3, 5],
even: [2, 4]
}
즉, callback의 반환값을 key로 해서 그룹핑하는 함수 이다.
문제를 풀다가 이런 조건이 있었다:
"should return a null-prototype object"
처음엔 그냥 const result= {}로 리턴 했는데 이 부분에서 막혔다.
일반 객체는 내부적으로 Object.prototype을 상속받는다.
const obj = {};
console.log(Object.getPrototypeOf(obj));
// Object.prototype
하지만 문제에서는
프로토타입이 아예 없는 객체를 요구했다.
const obj = Object.create(null);
console.log(Object.getPrototypeOf(obj));
// null
이걸 사용하면 prototype이 없는 순수 객체를 만들 수 있다.
일반 객체는 이런 문제가 있다:
const obj = {};
obj["toString"] = "test";
console.log(obj.toString); // 예상과 다를 수 있음
Object.prototype의 기본 메서드들과 충돌 가능
const obj = Object.create(null);
obj["toString"] = "test";
console.log(obj.toString); // "test"
완전히 순수한 key-value 구조
문제에서 요구했던 핵심 조건:
Object.getPrototypeOf(obj)가 null이어야 한다
const obj = Object.create(null);
const obj = {};
Object.setPrototypeOf(obj, null);
가능하지만 성능상 비추천한다고 AI가 그랬다.
(1) 성능 문제가 큼
JS 엔진(V8 등)은 객체를 생성할 때
-> hidden class (shape) 기반으로 최적화한다.
만약 객체의 프로토타입을 바꾸면?
-> 엔진이 기존 최적화를 다 깨버림 (deopt)
즉,속도 느려짐,최적화 포기
-> 내부적으로 비용 큼
그래서 JS 엔진 개발자들도 공식적으로 비추천 한다고 한다.
(2) "이미 만들어진 객체"를 바꾸는 방식
const obj = {};
// ...어딘가에서 사용됨
Object.setPrototypeOf(obj, null);
-> 이런 패턴은 사이드 이펙트를 만들기 쉬움
다른 코드에서 예상한 prototype이 사라짐
->디버깅 어려움
const obj = {};
obj.__proto__ = null;
동작은 하지만 사용 권장하지 않는다고 한다.
(1) 레거시(legacy) 기능
proto는 사실 표준 메서드가 아니라
-> 초기 브라우저 구현에서 나온 비공식 기능 이다.
현재는 표준에 "포함되긴 했지만"
👉 명확히 이렇게 정의됨:
"웹 호환성을 위해 유지되는 기능 (deprecated에 가까움)"
(2) 성능 문제 (setPrototypeOf와 동일)
obj.proto = null;
내부적으로는 결국
Object.setPrototypeOf와 같은 동작
→ 동일하게 최적화 깨짐
(3) 코드 가독성과 안정성 문제
obj.proto = null;
이건:
직관적이지 않음, 협업 시 혼란
,lint에서 경고 나는 경우 많음
(4) 보안 이슈와도 연관 있음
proto는 흔히Prototype Pollution 공격에서 사용된다고 한다.
function ObjectGroupBy<T, K extends keyof any>(
items: Array<T>,
callback: (item: T) => K
): Record<K, Array<T>> {
// Your code here
let result = Object.create(null);;
items.map((item)=>{
const key = callback(item);
if(key in result){
result[key] = [...result[key],item]
}else{
result[key] = [item]
}
})
return result
}
포인트는 단 하나:
결과 객체를 {}가 아니라 Object.create(null)로 생성해야 한다는 것
이번 문제에서 얻은 핵심:
Object.groupBy()는 값을 기준으로 그룹핑하는 함수null-prototype object는Object.create(null)로 만든다{}와 겉보기는 같지만 내부 구조는 완전히 다르다Object.getPrototypeOf(obj) === null 조건을 만족해야 할 때 반드시 필요이 문제 하나로
JS 객체의 내부 구조까지 이해하게 된 게 꽤 큰 수확이었다 👍