Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

学习 DOM API 中 before,after,prepend,append 等操作,并思考生产系统该如何使用? #37

Open
OhCoder opened this issue Mar 6, 2019 · 0 comments

Comments

@OhCoder
Copy link

OhCoder commented Mar 6, 2019

什么是 DOM( Document Object Model ) ?

DOM 是针对有效的 HTML 和具有良好规范的 XML 文本的一种应用程序接口(API),它定义了文本的逻辑结构以及针对文本的一系列访问和操作方式。在 DOM 规范里,“document” 词汇被用在了更广泛的场景里。越来越多的 XML 被存储在不同的系统里,来呈现不同类型的信息,通常来讲,这些信息被看作数据而不是文本。然而,XML 会以文本的方式呈现这些数据,而 DOM 就是用来管理这些数据的。

使用 DOM ,程序员们可以创建文本,操纵文本的结构,对内容和元素进行增删改查。可以对任何找到的某个 HTML 或 XML 文本进行增删改查。

Prepend 方法

定义:

一个或多个节点被插入到当前节点的第一个子节点的前面。每一个节点可以是一个节点对象(Node Object)或字符串(DOMString),如果是字符串,那么就等价于插入一个新的 Text 节点。(原文定义参见

源码分析(源码链接):

void Node::Prepend(const HeapVector<NodeOrString>& nodes,
                   ExceptionState& exception_state) {
  if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state))
    insertBefore(node, firstChild(), exception_state);
}

在做 insertBefore 之前,先做了一个节点转换的调用,也就是 ConvertNodesIntoNode 方法,那 ConvertNodesIntoNode 方法又做了什么呢?

源码分析(源码链接):

// Returns nullptr if an exception was thrown.
static Node* ConvertNodesIntoNode(const HeapVector<NodeOrString>& nodes,
                                  Document& document,
                                  ExceptionState& exception_state) {
  if (nodes.size() == 1)
    return NodeOrStringToNode(nodes[0], document);

  Node* fragment = DocumentFragment::Create(document);
  for (const NodeOrString& node_or_string : nodes) {
    fragment->appendChild(NodeOrStringToNode(node_or_string, document),
                          exception_state);
    if (exception_state.HadException())
      return nullptr;
  }
  return fragment;
}

查看上述代码,我们可以大体知道,这个方法先是对传入的 Nodes 节点做了判断,如果节点只有一个,那么就直接将节点转换成 Node 节点,并将其返回。如果大于 1 个,做一次遍历,将 Nodes 里的每个节点都做一次 NodeOrStringToNode 的转换。这里可以看到,结合官方对 Prepend 方法的定义,以及 NodeOrStringToNode 方法的名字,可以推测,对于新创建的节点,将节点分为了 Node 类型和字符串类型,并最终转换成 Node 节点,并生成一个 fragment

NodeOrStringToNode 方法执行完成后,会调用 insertBefore 方法,将生成的节点进行插入。

Append 方法

定义:

一个多节点被插入到当前节点的最后一个字节点的后面。每一个节点可以是一个节点对象(Node Object)或字符串(DOMString),如果是字符串,那么就等价于插入一个新的 Text 节点。(原文定义参见

源码分析(源码链接):

void Node::Append(const HeapVector<NodeOrString>& nodes,
                  ExceptionState& exception_state) {
  if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state))
    appendChild(node, exception_state);
}

查看源码我们知道,与 Prepend 类似,都是先生成 Node 节点,与 Preppend 不同的是,Append 调用的是 appendChild 方法插入节点。

Before 方法

定义:

仅在当前 ChildNode 父节点的孩子列表之前,插入一个 Node 集合或 DOMString 对象集。其中 DOMString 对象作为 Text 节点插入。(原文定义参见

源码分析(源码链接

void Node::Before(const HeapVector<NodeOrString>& nodes,
                  ExceptionState& exception_state) {
  Node* parent = parentNode();
  if (!parent)
    return;
  Node* viable_previous_sibling = FindViablePreviousSibling(*this, nodes);
  if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state))
    parent->insertBefore(node,
                         viable_previous_sibling
                             ? viable_previous_sibling->nextSibling()
                             : parent->firstChild(),
                         exception_state);
}

通过源码我们知道,先找到前一个兄弟节点,如果将传入的节点成功转换成 Node 节点,并且找到当前节点的前一个兄弟节点,则将传入的节点插入到下一个兄弟节点的前面,否则插入到当前父节点的第一个子节点之前。

After 方法

定义:

仅在当前 ChildNode 父节点的孩子列表之后,插入一个 Node 集合或 DOMString 对象集。其中 DOMString 对象作为 Text 节点插入。(原文定义参见

源码分析(源码链接

void Node::After(const HeapVector<NodeOrString>& nodes,
                 ExceptionState& exception_state) {
  Node* parent = parentNode();
  if (!parent)
    return;
  Node* viable_next_sibling = FindViableNextSibling(*this, nodes);
  if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state))
    parent->insertBefore(node, viable_next_sibling, exception_state);
}

通过源码我们知道,与 Before 方法正好相反,这里是先找到下一个兄弟节点,如果将传入的节点成功转换成 Node 节点,则直接将传入的节点插入到下一个兄弟节点的前面。

注意事项

上述介绍的方法截止到当前日期(2019.03.06)仍然处于试验阶段,所以在生产环境中使用要考虑兼容性问题。

解决兼容性问题,通常有两种方案:

  • 第一种是借助第三方的 polyfill ,例如DOM4
  • 第二种是自己实现 polyfill,实现的大致思路是借助现有兼容性较好的 API,比如 InsertBefore 等方法,来实现上述方法。
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant