【问题标题】:Writing getElementByName from scratch从头开始编写 getElementByName
【发布时间】:2019-11-21 11:15:41
【问题描述】:

我正在尝试从头开始编写 getElementByClassName,但我不确定何时返回递归。这就是我想出的:

  const getElementsByClassName = (nameOfClass, parent) => {

  const result = []
  for(let el of parent) {
       // console.log(el.children)
     if(el.firstElementChild != null) {
       // do it again on node deeper
        getElementsByClassName(nameOfClass, el.children)
     }

     if(el.className === nameOfClass) {
        result.push(el)
     }
   }
   // when do I want to return result?
   console.log(result)
   return result

};

问题是我每个子节点都有一个数组,而不是同一个数组结果中的所有内容。我该如何解决这个问题?

【问题讨论】:

  • 如果 getElementsByClassName(nameOfClass, el.children) 返回一些东西,它不应该被推送到数组吗?您必须使用.concat 而不是.pushresult = result.concat(...)

标签: javascript recursion


【解决方案1】:

所以你试图通过递归函数遍历 DOM 树!?

当然,每个孩子都有自己的一组孩子。否则就不是树了。

当您想要返回一个包含所有匹配元素的数组时,您必须将结果与递归调用连接起来。

这可行:

const getElementsByClassName = (nameOfClass, parent) => {

  const result = []
  if (parent.className === nameOfClass) {
    result.push(parent);
  }
  for(let el of parent.children) {
    result = result.concat(getElementByClassName(nameOfClass, el));
   }

   return result
};

此实现应仅用于教育目的,因为它具有很大的存储复杂性。

【讨论】:

  • 这里避免使用箭头函数的建议毫无意义。 const fact = (n) => n < 2 ? 1 : n * fact (n - 1) 是一个用箭头实现的递归函数。
  • @ScottSauyet 谢谢,你是对的。 javascript引擎做对了。我担心 lambda 表达式的分析会失败,因为(此时)未知实体“getElementByClassName”,因为赋值是从右到左评估的,因此在赋值之前评估 lambda 表达式。但事实并非如此,为什么我更正了我的答案。
  • 我认为关键是函数的主体在被调用之前不会运行,到那时函数名称已经被放置在范围内。
【解决方案2】:

您可以像这样使用 querySelectorAll:

const getElementsByClassName = (nameOfClass, ancestor) => (
 ancestor.querySelectorAll(`.${nameOfClass}`)
);

或者,如果您肯定想要父母的直系后代。 es6方式:

const getElementsByClassName = (nameOfClass, parent) => (
 [...parent.querySelectorAll(`.${nameOfClass}`)].filter(item => item.parentElement === parent))
);

javascript方式

const getElementsByClassName = (nameOfClass, parent) => (
 Array.prototype.slice.call(parent.querySelectorAll(`.${nameOfClass}`)).filter(item => item.parentElement === parent))
);

很遗憾,我们不能将直接后代与 querySelectorAll 一起使用。 否则这将是完美的:

const getElementsByClassName = (nameOfClass, parent) => (
 parent.querySelectorAll(`> .${nameOfClass}`)
);

【讨论】:

    【解决方案3】:

    应该这样做:

    const getElementsByClassName  = (name, el) => 
      [
        ... (el .classList .contains (name) ? [el] : []),
        ... ([... el .childNodes] .flatMap (c => getElementsByClassName (name, c)))
      ]
    

    从逻辑上讲,这只是说将测试此节点的结果(使用其classList)与其子节点的flatMapped 递归调用相结合。

    活生生的例子:

    const getElementsByClassName  = (name, el) => 
      [
        ... ([...(el .classList || [])] .includes (name) ? [el] : []),
        ... ([... el .childNodes] .flatMap (c => getElementsByClassName (name, c)))
      ]
    
    console .log (
      getElementsByClassName ('foo', document).map(el => el.textContent)
    )
    .foo {background: yellow; font-weight: bold}
    <div class = 'foo'>Foo</div>
    <div class = 'bar'>Bar</div>
    <div class = 'baz'>
      Baz
      <div class = 'baz qux'>
        Qux 
        <div class = 'foo baz qux'>Foo Baz Qux</div>
      </div>
      <div class = 'baz foo'>Baz Foo</div>
    </div>

    这在 DOM 遍历中可能过于简单,因为并非所有子节点都是元素,但这可能只是第二次检查。这应该表明了这个想法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-03
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多