Skip to content
New issue

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

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

Already on GitHub? Sign in 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 等方法,来实现上述方法。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant