如何优化像"this.parentNode.parentNode.parentNode..."这样的代码?

20
var topClick = function() {
  child = this.parentNode.parentNode.parentNode.parentNode;
  child.parentNode.removeChild(child);
  var theFirstChild = document.querySelector(".m-ctt .slt");
  data[child.getAttribute("data-id")].rank = 5;
  insertBlogs();
};

正如您所看到的,我的代码中有这样一部分:

As you see, there is a part of my code like this:

this.parentNode.parentNode.parentNode.parentNode;  

有没有其他方法来优化代码(不使用jQuery)?


1
你尝试过使用循环吗? - Bergi
2
@Bergi - 使用循环效率会降低。 OP 知道他们想要上树的距离。该对象似乎是“少打字”,而不是“快速搜索与规则匹配的元素”。 - Quentin
2
@kpsingh - 这个搜索方向是错误的。问题是要向上搜索树,而不是向下搜索。 - Quentin
1
在支持的浏览器中,有一个本地的 .closest() 方法(纯 JavaScript),它的工作方式类似于 jQuery 的 .closest() 方法。 - Josh Crozier
2
你可能还想考虑一下你命名变量的方式。 - Stephan Bijzitter
显示剩余4条评论
4个回答

33

您可以使用非递归的辅助函数,例如:

function nthParent(element, n) {
  while(n-- && element)  
    element = element.parentNode;
  return element;
}

30

您可以使用递归辅助函数,例如:

function getNthParent(elem, n) {
    return n === 0 ? elem : getNthParent(elem.parentNode, n - 1);
}

var child = getNthParent(someElement, 4);

1
谢谢!最终我接受了你的方法。但是,当使用一个用函数表达式初始化的变量来定义它时,它会显示“Uncaught ReferenceError: getNthParent未定义”。所以我稍微改了一下:return n === 0 ? elem : arguments.callee(elem.parentNode, n - 1); - yaochiqkl
3
arguments.callee”对我来说有些繁琐。我会使用命名函数表达式代替。例如:var myFn = function getNthParent(elem, n) { ... } - madox2

10

另一种方法


你的目标

根据您在原问题中的评论,您的总体目标是:

有一个博客列表。每个博客都有像“编辑”和“删除”之类的按钮。当我点击这些按钮时,我想找到它的博客元素。

我认为解决您面临的问题的最佳方法(而不是回答您提出的问题-抱歉!)是以不同的方式来解决问题。

从您的评论中,您说您有类似以下内容:

<ul id="blog-list">
  <li class="blog-item">
    <a href="blog1.com">
      Boats galore!
    </a>
    <span class="blog-description">
      This is a blog about some of the best boats from Instagram.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
  <li class="blog-item">
    <a href="blog2.com">
      Goats galore!
    </a>
    <span class="blog-description">
      A blog about the everyday adventures of goats in South Africa.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
  <li class="blog-item">
    <a class="blog-link" href="blog3.com">
      Totes galore!
    </a>
    <span class="blog-description">
      A blog about containers and bags, and the owners who love them.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
</ul>

你的目标是为每个 blog-link 项的 button 元素添加 click 事件处理程序。

因此,让我们将该目标从普通英语翻译成伪代码:

for each `blog-link` `b`
  delete_button.onclick = delete_handler(`b`);
  edit_button.onclick = edit_handler(`b`);

示例脚本

可在此链接查看实际运行效果: http://jsfiddle.net/vbt6bjwy/10/

<script>
  function deleteClickHandler(event, parent) {
    event.stopPropagation();
    parent.remove();
  }

  function editClickHandler(event, parent) {
    event.stopPropagation();
    var description = parent.getElementsByClassName("blog-description")[0];
    description.innerHTML = prompt("Enter a new description:");
  }

  function addClickHandlers() {
    var bloglistElement = document.getElementById("blog-list");
    var blogitems = bloglistElement.getElementsByClassName("blog-item");

    blogitems.forEach(function(blogitem){
      var deleteButtons = blogitem.getElementsByClassName("delete-button");

      deleteButtons.forEach(function(deleteButton){
        deleteButton.onclick = function(event) {
          return deleteClickHandler(event, blogitem);
        }
      });

      var editButtons = blogitem.getElementsByClassName("edit-button");

      editButtons.forEach(function(editButton){
        editButton.onclick = function(event) {
          return editClickHandler(event, blogitem);
        }
      });
    });
  }

  HTMLCollection.prototype.forEach = Array.prototype.forEach;
  addClickHandlers();
</script>

说明

你选择实现的解决方案是有效的,但我想给你提供一个不同的看待同样问题的方法。

在我看来,blog 实体的内部标签不应该对周围 HTML 的结构或属性有所了解,以便使你的 editdelete 按钮能够工作。

你原来的解决方案必须从每个 button 开始向上遍历父元素链,直到找到它所假定的正确父元素,基于一种脆弱的方法,如硬编码在父元素链中向上移动 N 次。如果我们可以使用普通的 JavaScript 元素选择器,那么不管 HTML 结构如何变化,只要类和 ID 保持一致,我们的 JavaScript 就不会出错,这不是更好吗?

这个解决方案迭代了 #blog-list 元素中的每个 blog-item

blogitems.forEach(function(blogitem){ ... });

forEach 循环中,我们获取包含 .delete-button.edit-button 元素的数组。对于每个元素,我们将适当的事件处理程序添加到 onclick 属性中:

deleteButtons.forEach(function(deleteButton){
  deleteButton.onclick = function(event) {
    return deleteClickHandler(event, blogitem);
  }
});

对于数组中的每个deleteButton元素,我们将一个匿名函数分配给事件处理程序onclick。创建这个匿名函数允许我们创建一个闭包。
这意味着每个deleteButton.onclick函数都会单独“记住”它属于哪个blogitem。
查看这个SO问题/答案,了解有关闭包的更多信息:JavaScript闭包如何工作? 我们添加HTMLCollection.prototype.forEach...行,为所有HTMLCollection对象提供forEach功能。像getElementsByClassName这样的函数返回一个HTMLCollection对象。它在大多数情况下像一个数组一样运作,但默认情况下不允许我们使用forEach。关于兼容性的说明:您可以使用标准的for循环,因为forEach在所有浏览器中都不可用(主要是旧版IE浏览器的问题)。我更喜欢使用forEach的习惯用法。
最终结果
最终结果是代码稍微长一些,因为问题的范围比您实际提出的问题要广泛一些。优点是此代码更加灵活,抵抗HTML结构变化的能力更强。

1
我非常感动,你热情地教了我很多。我真的从中学到了很多,知道了如何更好地解决问题,也对闭包有了更深入的理解。我会反复阅读以掌握这种方法。非常感谢! - yaochiqkl
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kyle Pittman

2
var topClick = function(event){
    console.log(event);
    child = this.parentNode.parentNode.parentNode.parentNode;
    child.parentNode.removeChild(child);
    var theFirstChild = document.querySelector(".m-ctt .slt");
    data[child.getAttribute("data-id")].rank = 5;
    insertBlogs();
};

惊喜!我通过console.log打印出事件,并在其中找到了以下数组:

enter image description here

而我找到了我想要的元素:

function(event){
console.log(event.path[4]);
child = event.path[4];
}

它居然能工作!太神奇了!也许事件代理是另一种方式!
感谢你们提供答案!
下次在提问之前我会先仔细思考的!:D


8
这并非标准,可能无法在所有浏览器中得到支持。请参阅https://dev59.com/PV8e5IYBdhLWcg3wAWgL#30021867。 - Radek Pech
3
这假设了问题中未提供的信息。(我知道这是你的问题,但仍然......) - nnnnnn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接