며칠 전에 angular 코드를 보다가 design note을 찾으러 위키를 갔는데 우연히 [performance]을 봤다.(design note는 [다른 곳]에서 찾음.)
다른 내용들은 일반적인 내용인데 내가 의아하게 느낀 부분이 자식노드를 탐색할 때 `DOM.childNodes`이 `node.nextSibling`보다 느리다는 얘기다. 그 예로 [jsperf링크]가 있어서 확인해보니 실제로 nextSibling이 빨랐고 몇 개 브라우저에서 좀 더 확인해봤는데 모두 nextSibling이 항상 빨랐다.
"왜 그럴까?" 잠깐 고민해봤는데 딱히 아이디어가 안떠올라 blink의 구현로직을 찾아보기로 했다.
먼저 clideNodes부분을 보니 Container노드(parentNode)일 때 그 안에 childeNode을 찾아 nodelist로 만든 다음 반환하는데 뭔가 특별해보이진 않는다.
PassRefPtrWillBeRawPtr<NodeList> Node::childNodes() { if (isContainerNode()) return ensureRareData().ensureNodeLists().ensureChildNodeList(toContainerNode(*this)); return ensureRareData().ensureNodeLists().ensureEmptyChildNodeList(*this); }
그럼 nextSibling은 뭘까 찾아봤다.
Node* previousSibling() const { return m_previous; } Node* nextSibling() const { return m_next; }
엇. 이거 뭐지 그냥 반환만 하네... 그래서 찾아보니 nextSibling, previousSibling을 호출할 때는 뭔가 찾는게 아니라 아래와 같이 Node가 변경되면 값을 업데이트하고 호출할 때는 위와 같이 그냥 반환한다.
void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild) { Node* prev = nextChild.previousSibling(); nextChild.setPreviousSibling(&newChild); if (prev) { prev->setNextSibling(&newChild); } else { m_firstChild = &newChild; } newChild.setParentOrShadowHostNode(this); newChild.setPreviousSibling(prev); newChild.setNextSibling(&nextChild); } void ContainerNode::appendChildCommon(Node& child) { child.setParentOrShadowHostNode(this); if (m_lastChild) { child.setPreviousSibling(m_lastChild); m_lastChild->setNextSibling(&child); } else { setFirstChild(&child); } setLastChild(&child); }
즉, childNodes을 찾을 때는 하위 노드를 찾아서 nodelist로 만든 다음 반환하기 때문에 비용이 좀 드는 것 같고, nextSibling은 그냥 바로 노드를 반환하기 때문에 비용이 적게 드는 것 같다.
갑자기 비슷한 children이 생각나서 이건 어떻게 구현되어 있을까 하고 찾아봤는데 아래와 같이 구현되어 있다.
PassRefPtrWillBeRawPtr<HTMLCollection> ContainerNode::children()
{
return ensureCachedCollection<HTMLCollection>(NodeChildren);
}
이건 우리가 알고 있는 [live nodelist]인 것 같아 아마도 성능이 좋지 않을까 하고 비교해봤는데 결과는 nextSibling보다는 느리지만, childNodes보다는 빨랐다. [링크]
처음 가지고 있던 의구심을 해결하고 nextSibling, previousSibling, lastChild, firstChild는 이미 계산되어 가지고 있다는 사실을 알게됐다.
ps. github에서 유명한 프로젝트에 childNodes을 사용하는 부분을 찾아서 pr줍기를 시도했지만... 테스트 코드를 제외하곤 거의 없어 아쉬었다. 하지만, 우연히 버그같은 코드를 발견해서 [pr 줍기 성공]. :)