51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

聊聊Web前端开发中关于JavaScript closest()的一些应用

500.jpg

聊聊Web前端开发中关于JavaScript里closest()的一些基础知识。您是否曾经遇到过在JavaScript中找到DOM节点的父节点的问题,但是不确定是否要遍历多少个层次才能到达它?让我们来看一下这个HTML

<div data-id="123">
  <button>Click me</button>
</div>

那很简单,对吧?假设您要data-id在用户单击按钮后获取的值:

var button = document.querySelector("button");
button.addEventListener("click", (evt) => {
  console.log(evt.target.parentNode.dataset.id);
  // prints "123"
});

在这种情况下,Node.parentNode API就足够了。它的作用是返回给定元素的父节点。在上面的示例中,evt.target单击了按钮;它的父节点是具有data属性的div。

但是,如果HTML结构嵌套得更深呢?根据其内容,它甚至可以是动态的。

<div data-id="123">
  <article>
    <header>
      <h1>Some title</h1>
      <button>Click me</button>
    </header>
     <!-- ... -->
  </article>
</div>

通过添加更多HTML元素,我们的工作变得更加困难。当然,我们可以做类似的事情element.parentNode.parentNode.parentNode.dataset.id,但是来吧......那不是优雅,可重用或可扩展的。

旧方法:使用while-loop

一种解决方案是利用while循环,直到找到父节点为止。

function getParentNode(el, tagName) {
  while (el && el.parentNode) {
    el = el.parentNode;
    
    if (el && el.tagName == tagName.toUpperCase()) {
      return el;
    }
  }
  
  return null;
}

从上面再次使用相同的HTML示例,它看起来像这样:

var button = document.querySelector("button");

console.log(getParentNode(button, 'div').dataset.id);
// prints "123"

该解决方案远非完美。想象一下,如果您要使用ID或类或任何其他类型的选择器,而不是标签名。至少它允许在父级和我们的源之间有可变数量的子节点。

还有jQuery

过去,如果您不想处理上面为每个应用程序编写的上述函数(让我们成为现实,谁想要的?),那么像jQuery这样的库就派上用场了(它仍然可以)。它提供了.closest()一种确切的方法:

$("button").closest("[data-id='123']")

新方法:使用 Element.closest()

即使jQuery仍然是一种有效的方法(嘿,我们当中有些人对此很执着),但是仅将这一种方法添加到项目中就太过头了,特别是如果您可以使用本机JavaScript拥有同样的方法。

这就是起作用的地方Element.closest:

var button = document.querySelector("button");

console.log(button.closest("div"));
// prints the HTMLDivElement

好了!这很容易,而且没有任何库或额外的代码。

Element.closest()允许我们遍历DOM,直到获得与给定选择器匹配的元素。令人敬畏的是,我们可以传递也要给Element.querySelector或的任何选择器Element.querySelectorAll。它可以是ID,类,数据属性,标签等。

element.closest("#my-id"); // yep
element.closest(".some-class"); // yep
element.closest("[data-id]:not(article)") // hell yeah

如果Element.closest根据给定的选择器找到父节点,则它会与返回相同的方式 document.querySelector。否则,如果找不到父项,它将返回null,从而易于使用if条件:

var button = document.querySelector("button");

console.log(button.closest(".i-am-in-the-dom"));
// prints HTMLElement

console.log(button.closest(".i-am-not-here"));
// prints null

if (button.closest(".i-am-in-the-dom")) {
  console.log("Hello there!");
} else {
  console.log(":(");
}

准备好一些真实的例子了吗?我们走吧!

用例1:下拉列表

<!DOCTYPE html>    
<html>    
<head>    
<meta charset="UTF-8">    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<meta charset="utf-8">    
<meta name='viewport' content='width=device-width, initial-scale=1'>    
<title>CodePen - Dropdown Example with `Element.closest`</title>    
<link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" />    
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" />    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<title>CodePen - Dropdown Example with `Element.closest`</title>    
<script>    
if (document.location.search.match(/type=embed/gi)) {    
window.parent.postMessage("resize", "*");    
}    
</script>    
<style>    
html { font-size: 15px; }    
html, body { margin: 0; padding: 0; min-height: 100%; }    
body { height:100%; display: flex; flex-direction: column; }    
.referer-warning {    
background: black;    
box-shadow: 0 2px 5px rgba(0,0,0, 0.5);    
padding: 0.75em;    
color: white;    
text-align: center;    
font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif;    
line-height: 1.2;    
font-size: 1rem;    
position: relative;    
z-index: 2;    
}    
.referer-warning h1 { font-size: 1.2rem; margin: 0; }    
.referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */    
</style>    
</head>    
<body class="">    
<div id="result-iframe-wrap" role="main">    
<iframe id="result" srcdoc="
    
<!DOCTYPE html>    
<html lang=&quot;en&quot; >    
<head>    
 <meta charset=&quot;UTF-8&quot;>    
<link rel=&quot;apple-touch-icon&quot; type=&quot;image/png&quot; href=&quot;https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png&quot; />    
<meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;CodePen&quot;>    
<link rel=&quot;shortcut icon&quot; type=&quot;image/x-icon&quot; href=&quot;https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico&quot; />    
<link rel=&quot;mask-icon&quot; type=&quot;&quot; href=&quot;https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg&quot; color=&quot;#111&quot; />    
 <title>CodePen - Dropdown Example with `Element.closest`</title>    
 <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600&amp;amp;display=swap'>    
<style>    
* {    
 box-sizing: border-box;    
}    
body {    
 font: normal 14px/1.4 &quot;Source Sans Pro&quot;, sans-serif;    
 background-color: #D9E2EC;    
 color: #334E68;    
 margin: 2rem;    
}    
/* Navigation */    
.main-navigation {    
 background-color: white;    
 padding: 1.5rem 2rem;    
 border-radius: .3em;    
 box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);    
 display: flex;    
 align-items: center;    
}    
.main-navigation.is-expanded {    
 border-bottom-left-radius: 0;    
}    
.main-navigation > .entry {    
 font: inherit;    
 color: inherit;    
 text-decoration: unset;    
 border: unset;    
 background-color: unset;    
 padding: unset;    
 font-weight: 600;    
 cursor: pointer;    
 margin-right: 2rem;    
 transition: color .1s linear;    
 position: relative;    
}    
.main-navigation > .entry.is-dropdown {    
 padding-right: 1rem;    
}    
.main-navigation > .entry.is-dropdown::after {    
 content: &quot;&quot;;    
 position: absolute;    
 width: 0;    
 height: 0;    
 border-top: 8px solid #2CB1BC;    
 border-left: 5px solid transparent;    
 border-right: 5px solid transparent;    
 top: 7px;    
 right: 0px;    
}    
.main-navigation > .entry:hover, .main-navigation > .entry:focus {    
 color: #2CB1BC;    
}    
/* Dropdown */    
.menu-dropdown {    
 background-color: white;    
 position: absolute;    
 margin: unset;    
 padding: 1.5rem 2rem;    
 list-style: none;    
 border-radius: 0 0 .3em .3em;    
 box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);    
 display: flex;    
 flex-wrap: wrap;    
 max-width: 650px;    
 border-top: 3px solid #2CB1BC;    
}    
.menu-dropdown.is-hidden {    
 display: none;    
}    
.menu-dropdown > .item {    
 flex: 0 0 50%;    
}    
.menu-dropdown > .item:nth-child(odd) {    
 padding-right: 1rem;    
}    
.menu-dropdown > .item:nth-child(even) {    
 padding-left: 1rem;    
}    
.menu-dropdown > .item:first-child, .menu-dropdown > .item:nth-child(2) {    
 margin-bottom: 2rem;    
}    
.menu-dropdown > .item > .title {    
 margin: unset;    
}    
.menu-dropdown > .item > .text {    
 margin-top: unset;    
 color: #668eb4;    
}    
.menu-dropdown > .item > .link {    
 color: #2CB1BC;    
 font-weight: 600;    
 text-decoration: unset;    
 transition: color .1s linear;    
}    
.menu-dropdown > .item > .link:hover, .menu-dropdown > .item > .link:focus {    
 color: #334E68;    
}    
</style>    
 <script>    
 window.console = window.console || function(t) {};    
</script>    
 <script>    
 if (document.location.search.match(/type=embed/gi)) {    
   window.parent.postMessage(&quot;resize&quot;, &quot;*&quot;);    
 }    
</script>    
</head>    
<body translate=&quot;no&quot; >    
 <nav class=&quot;main-navigation&quot;>    
 <button type=&quot;button&quot; class=&quot;entry is-dropdown&quot; data-dropdown-trigger>Resources</button>    
 <button type=&quot;button&quot; class=&quot;entry is-dropdown&quot; data-dropdown-trigger>Customers</button>    
 <a href=&quot;#&quot; class=&quot;entry&quot;>Contact</a>    
</nav>    
<ul class=&quot;menu-dropdown is-hidden&quot;>    
 <li class=&quot;item&quot;>    
   <h3 class=&quot;title&quot;>Designs</h3>    
   <p class=&quot;text&quot;>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p>    
   <a class=&quot;link&quot; href=&quot;#&quot;>More</a>    
 </li>    
 <li class=&quot;item&quot;>    
   <h3 class=&quot;title&quot;>Tutorials</h3>    
   <p class=&quot;text&quot;>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p>    
   <a class=&quot;link&quot; href=&quot;#&quot;>More</a>    
 </li>    
 <li class=&quot;item&quot;>    
   <h3 class=&quot;title&quot;>Templates</h3>    
   <p class=&quot;text&quot;>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p>    
   <a class=&quot;link&quot; href=&quot;#&quot;>More</a>    
 </li>    
 <li class=&quot;item&quot;>    
   <h3 class=&quot;title&quot;>Mockups</h3>    
   <p class=&quot;text&quot;>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p>    
   <a class=&quot;link&quot; href=&quot;#&quot;>More</a>    
 </li>    
</ul>    
   <script src=&quot;https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js&quot;></script>    
     <script id=&quot;rendered-js&quot; >    
var menu = document.querySelector(&quot;.menu-dropdown&quot;);    
var navigation = document.querySelector(&quot;.main-navigation&quot;);    
function handleClick(evt) {    
 // Only if a click on a dropdown trigger happens, either close or open it.    
 if (evt.target.hasAttribute(&quot;data-dropdown-trigger&quot;)) {    
   if (menu.classList.contains(&quot;is-hidden&quot;)) {    
     menu.classList.remove(&quot;is-hidden&quot;);    
     navigation.classList.add(&quot;is-expanded&quot;);    
   } else {    
     menu.classList.add(&quot;is-hidden&quot;);    
     navigation.classList.remove(&quot;is-expanded&quot;);    
   }    
   return;    
 }    
 // If a click happens somewhere outside the dropdown, close it.    
 if (!evt.target.closest(&quot;.menu-dropdown&quot;)) {    
   menu.classList.add(&quot;is-hidden&quot;);    
   navigation.classList.remove(&quot;is-expanded&quot;);    
 }    
}    
window.addEventListener(&quot;click&quot;, handleClick);    
//# sourceURL=pen.js    
   </script>    
</body>    
</html>    
" sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation  allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true">    
</iframe>    
</div>    
</body>    
</html>

我们的第一个演示是下拉菜单的基本(远非完美)实现,单击一个顶级菜单项后即可打开该下拉菜单。请注意,即使在下拉菜单中的任意位置单击或选择文本,菜单也如何保持打开状态?但是单击外部的某个位置,它将关闭。

该Element.closestAPI是什么检测之外点击。下拉菜单本身是<ul>带有.menu-dropdown类的元素,因此单击菜单外的任何位置都会将其关闭。这是因为for的值evt.target.closest(".menu-dropdown")将是null因为此类没有父节点。

function handleClick(evt) {
  // ...
  
  // if a click happens somewhere outside the dropdown, close it.
  if (!evt.target.closest(".menu-dropdown")) {
    menu.classList.add("is-hidden");
    navigation.classList.remove("is-expanded");
  }}

handleClick回调函数中,条件决定了要做什么:关闭下拉列表。如果单击无序列表中的其他位置,Element.closest将找到并返回它,从而使下拉列表保持打开状态。

用例2:表格

<!DOCTYPE html>    
<html>    
<head>    
<meta charset="UTF-8">    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<meta charset="utf-8">    
<meta name='viewport' content='width=device-width, initial-scale=1'>    
<title>CodePen - Table Example with `Element.closest`</title>    
<link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" />    
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" />    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<title>CodePen - Table Example with `Element.closest`</title>    
<script>    
if (document.location.search.match(/type=embed/gi)) {    
window.parent.postMessage("resize", "*");    
}    
</script>    
<style>    
html { font-size: 15px; }    
html, body { margin: 0; padding: 0; min-height: 100%; }    
body { height:100%; display: flex; flex-direction: column; }    
.referer-warning {    
background: black;    
box-shadow: 0 2px 5px rgba(0,0,0, 0.5);    
padding: 0.75em;    
color: white;    
text-align: center;    
font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif;    
line-height: 1.2;    
font-size: 1rem;    
position: relative;    
z-index: 2;    
}    
.referer-warning h1 { font-size: 1.2rem; margin: 0; }    
.referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */    
</style>    
</head>    
<body class="">    
<div id="result-iframe-wrap" role="main">    
<iframe id="result" srcdoc="
    
<!DOCTYPE html>    
<html lang=&quot;en&quot; >    
<head>    
 <meta charset=&quot;UTF-8&quot;>    
<link rel=&quot;apple-touch-icon&quot; type=&quot;image/png&quot; href=&quot;https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png&quot; />    
<meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;CodePen&quot;>    
<link rel=&quot;shortcut icon&quot; type=&quot;image/x-icon&quot; href=&quot;https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico&quot; />    
<link rel=&quot;mask-icon&quot; type=&quot;&quot; href=&quot;https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg&quot; color=&quot;#111&quot; />    
 <title>CodePen - Table Example with `Element.closest`</title>    
 <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Open+Sans:400,700&amp;amp;display=swap'>    
<style>    
body {    
 margin: unset;    
 font: normal 14px/1.5 &quot;Open Sans&quot;, sans-serif;    
 color: #444;    
 background-color: lightblue;    
 padding: 1rem;    
}    
input[type=&quot;checkbox&quot;] {    
 margin: unset;    
}    
table {    
 max-width: 70vw;    
 margin: auto;    
 border-collapse: collapse;    
 box-shadow: 0 10px 30px rgba(0, 0, 0, .1);    
}    
th {    
 text-align: left;    
 padding: 10px;    
 background-color: rgba(0, 0, 0, .15);    
}    
td {    
 background-color: white;    
 padding: 10px;    
}    
tr:not(:last-of-type) td {    
 border-bottom: 1px solid #ddd;    
}    
</style>    
 <script>    
 window.console = window.console || function(t) {};    
</script>    
 <script>    
 if (document.location.search.match(/type=embed/gi)) {    
   window.parent.postMessage(&quot;resize&quot;, &quot;*&quot;);    
 }    
</script>    
</head>    
<body translate=&quot;no&quot; >    
 <table>    
 <thead>    
   <tr>    
     <th></th>    
     <th>Name</th>    
     <th>Email</th>    
     <th></th>    
   </tr>    
 </thead>    
 <tbody>    
   <tr data-userid=&quot;1&quot;>    
     <td>    
       <input type=&quot;checkbox&quot; data-action=&quot;select&quot;>    
     </td>    
     <td>John Doe</td>    
     <td>john.doe@gmail.com</td>    
     <td>    
       <button type=&quot;button&quot; data-action=&quot;edit&quot;>Edit</button>    
       <button type=&quot;button&quot; data-action=&quot;delete&quot;>Delete</button>    
     </td>    
   </tr>    
   <tr data-userid=&quot;2&quot;>    
     <td>    
       <input type=&quot;checkbox&quot; data-action=&quot;select&quot;>    
     </td>    
     <td>Jane Smith</td>    
     <td>jane@smith.com</td>    
     <td>    
       <button type=&quot;button&quot; data-action=&quot;edit&quot;>Edit</button>    
       <button type=&quot;button&quot; data-action=&quot;delete&quot;>Delete</button>    
     </td>    
   </tr>    
   <tr data-userid=&quot;3&quot;>    
     <td>    
       <input type=&quot;checkbox&quot; data-action=&quot;select&quot;>    
     </td>    
     <td>George Westminster</td>    
     <td>g.westminster@googlemail.com</td>    
     <td>    
       <button type=&quot;button&quot; data-action=&quot;edit&quot;>Edit</button>    
       <button type=&quot;button&quot; data-action=&quot;delete&quot;>Delete</button>    
     </td>    
   </tr>    
   <tr data-userid=&quot;4&quot;>    
     <td>    
       <input type=&quot;checkbox&quot; data-action=&quot;select&quot;>    
     </td>    
     <td>Will Johnson</td>    
     <td>will.johnson@yahoo.com</td>    
     <td>    
       <button type=&quot;button&quot; data-action=&quot;edit&quot;>Edit</button>    
       <button type=&quot;button&quot; data-action=&quot;delete&quot;>Delete</button>    
     </td>    
   </tr>    
 </tbody>    
</table>    
   <script src=&quot;https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js&quot;></script>    
     <script id=&quot;rendered-js&quot; >    
(function () {    
 &quot;use strict&quot;;    
 function getUserId(target) {    
   return target.closest(&quot;[data-userid]&quot;).dataset.userid;    
 }    
 function handleClick(evt) {    
   var { action } = evt.target.dataset;    
   if (action) {    
     let userId = getUserId(evt.target);    
     if (action == &quot;edit&quot;) {    
       alert(`Edit user with ID of ${userId}`);    
     } else if (action == &quot;delete&quot;) {    
       alert(`Delete user with ID of ${userId}`);    
     } else if (action == &quot;select&quot;) {    
       alert(`Selected user with ID of ${userId}`);    
     }    
   }    
 }    
 window.addEventListener(&quot;click&quot;, handleClick);    
})();    
//# sourceURL=pen.js    
   </script>    
</body>    
</html>    
" sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation  allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true">    
</iframe>    
</div>    
</body>    
</html>

第二个示例呈现一个显示用户信息的表,比方说它是仪表板中的组件。每个用户都有一个ID,但是我们没有显示它,而是将其另存为每个<tr>元素的数据属性。

<table>
  <!-- ... -->
  <tr data-userid="1">
    <td>
      <input type="checkbox" data-action="select">
    </td>
    <td>John Doe</td>
    <td>john.doe@gmail.com</td>
    <td>
      <button type="button" data-action="edit">Edit</button>
      <button type="button" data-action="delete">Delete</button>
    </td>
  </tr>
</table>

最后一列包含两个按钮,用于编辑和删除表中的用户。第一个按钮的data-action属性为edit,第二个按钮的属性为delete。当我们单击它们中的任何一个时,我们都想触发一些操作(例如向服务器发送请求),但是为此,需要用户ID。

单击事件侦听器附加到全局窗口对象,因此,每当用户单击页面上的某个位置时,都会调用回调函数handleClick。

function handleClick(evt) {
  var { action } = evt.target.dataset;
  
  if (action) {
    // `action` only exists on buttons and checkboxes in the table.
    let userId = getUserId(evt.target);
    
    if (action == "edit") {
      alert(`Edit user with ID of ${userId}`);
    } else if (action == "delete") {
      alert(`Delete user with ID of ${userId}`);
    } else if (action == "select") {
      alert(`Selected user with ID of ${userId}`);
    }
  }
}

如果单击发生在这些按钮之一以外的其他位置,则不data-action存在属性,因此什么也没有发生。但是,单击任一按钮时,将确定操作(顺便说一句,称为事件委托),并且下一步,将通过调用来检索用户ID getUserId:

function getUserId(target) {
  // `target` is always a button or checkbox.
  return target.closest("[data-userid]").dataset.userid;
}

该函数期望将DOM节点作为唯一参数,并在调用时用于Element.closest查找包含按下按钮的表行。然后data-userid,它返回该值,该值现在可用于将请求发送到服务器。

用例3:React中的表

让我们继续使用表格示例,看看如何在React项目中处理它。这是返回表的组件的代码:

function TableView({ users }) {
  function handleClick(evt) {
    var userId = evt.currentTarget
    .closest("[data-userid]")
    .getAttribute("data-userid");

    // do something with `userId`
  }

  return (
    <table>
      {users.map((user) => (
        <tr key={user.id} data-userid={user.id}>
          <td>{user.name}</td>
          <td>{user.email}</td>
          <td>
            <button onClick={handleClick}>Edit</button>
          </td>
        </tr>
      ))}
    </table>
  );
}

我发现这种用例经常出现-映射一组数据并将其显示在列表或表中,然后允许用户对其进行处理是相当普遍的。许多人使用嵌入式箭头功能,如下所示:

<button onClick={() => handleClick(user.id)}>Edit</button>

虽然这也是解决问题的有效方法,但我更喜欢使用该data-userid技术。内联箭头功能的缺点之一是,每次React重新渲染列表时,它都需要再次创建回调函数,从而在处理大量数据时可能导致性能问题。

在回调函数中,我们只需提取目标(按钮)并获取<tr>包含该data-userid值的父元素来处理事件。

function handleClick(evt) {
  var userId = evt.target
  .closest("[data-userid]")
  .getAttribute("data-userid");

  // do something with `userId`
}

用例4:模态

<!DOCTYPE html>    
<html>    
<head>    
<meta charset="UTF-8">    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<meta charset="utf-8">    
<meta name='viewport' content='width=device-width, initial-scale=1'>    
<title>CodePen - Modal Example with `Element.closest`</title>    
<link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" />    
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" />    
<link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" />    
<meta name="apple-mobile-web-app-title" content="CodePen">    
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" />    
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" />    
<title>CodePen - Modal Example with `Element.closest`</title>    
<script>    
if (document.location.search.match(/type=embed/gi)) {    
window.parent.postMessage("resize", "*");    
}    
</script>    
<style>    
html { font-size: 15px; }    
html, body { margin: 0; padding: 0; min-height: 100%; }    
body { height:100%; display: flex; flex-direction: column; }    
.referer-warning {    
background: black;    
box-shadow: 0 2px 5px rgba(0,0,0, 0.5);    
padding: 0.75em;    
color: white;    
text-align: center;    
font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif;    
line-height: 1.2;    
font-size: 1rem;    
position: relative;    
z-index: 2;    
}    
.referer-warning h1 { font-size: 1.2rem; margin: 0; }    
.referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */    
</style>    
</head>    
<body class="">    

<div id="result-iframe-wrap" role="main">    
<iframe id="result" srcdoc="
    
<!DOCTYPE html>    
<html lang=&quot;en&quot; >    
<head>    
 <meta charset=&quot;UTF-8&quot;>    
<link rel=&quot;apple-touch-icon&quot; type=&quot;image/png&quot; href=&quot;https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png&quot; />    
<meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;CodePen&quot;>    
<link rel=&quot;shortcut icon&quot; type=&quot;image/x-icon&quot; href=&quot;https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico&quot; />    
<link rel=&quot;mask-icon&quot; type=&quot;&quot; href=&quot;https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg&quot; color=&quot;#111&quot; />    
 <title>CodePen - Modal Example with `Element.closest`</title>    
 <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Gelasio:400,700&amp;amp;display=swap'>    
<style>    
body {    
 margin: unset;    
 font: normal 16px/1.5 Gelasio, serif;    
 text-align: center;    
}    
.open-modal {    
 font-family: inherit;    
 font-size: 23px;    
 background-color: tomato;    
 border: none;    
 font-weight: bold;    
 padding: .5rem 4rem;    
 border-radius: .2rem;    
 margin-top: 3rem;    
 cursor: pointer;    
}    
.open-modal:hover {    
 background-color: black;    
 color: white;    
}    
.modal-outer {    
 position: fixed;    
 top: 0;    
 left: 0;    
 width: 100%;    
 height: 100%;    
 background-color: rgba(200, 200, 200, .4);    
 display: flex;    
 align-items: center;    
 justify-content: center;    
}    
.modal-outer.is-hidden {    
 display: none;    
}    
.modal-inner {    
 background-color: white;    
 position: relative;    
 box-shadow: 0 10px 20px -10px rgba(0, 0, 0, .1);    
 width: 100%;    
 max-width: 70vw;    
 max-height: 50vh;    
 border-radius: .5rem;    
 padding: 2rem;    
 box-sizing: border-box;    
 text-align: center;    
}    
.modal-close {    
 position: absolute;    
 top: 0;    
 right: 0;    
 font-size: 2rem;    
 background-color: transparent;    
 border: none;    
 margin: unset;    
 width: 50px;    
 height: 50px;    
 cursor: pointer;    
}    
.modal-close:hover,    
.modal-close:focus {    
 color: tomato;    
}    
</style>    
 <script>    
 window.console = window.console || function(t) {};    
</script>    
 <script>    
 if (document.location.search.match(/type=embed/gi)) {    
   window.parent.postMessage(&quot;resize&quot;, &quot;*&quot;);    
 }    
</script>    
</head>    
<body translate=&quot;no&quot; >    
 <button type=&quot;button&quot; class=&quot;open-modal&quot;>Open modal</button>    
<div class=&quot;modal-outer is-hidden&quot;>    
 <div class=&quot;modal-inner&quot;>    
   <button type=&quot;button&quot; class=&quot;modal-close&quot;>&amp;times;</button>    
   <h1>Modal content</h1>    
   <p>You can click anywhere outside the modal to close it.<br>Alternatively, there's the close button in the upper right corner.</p>    
 </div>    
</div>    
   <script src=&quot;https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js&quot;></script>    
     <script id=&quot;rendered-js&quot; >    
(function () {    
 &quot;use strict&quot;;    
 var modal = document.querySelector(&quot;.modal-outer&quot;);    
 var open = document.querySelector(&quot;.open-modal&quot;);    
 var close = modal.querySelector(&quot;.modal-close&quot;);    
 function handleModalOpen() {    
   modal.classList.remove(&quot;is-hidden&quot;);    
 }    
 function handleModalClose() {    
   modal.classList.add(&quot;is-hidden&quot;);    
 }    
 function handleModalClick(evt) {    
   if (!evt.target.closest(&quot;.modal-inner&quot;)) {    
     handleModalClose();    
   }    
 }    
 function handleKeyDown(evt) {    
   if (evt.key == &quot;Escape&quot; &amp;&amp; !modal.classList.contains(&quot;is-hidden&quot;)) {    
     handleModalClose();    
   }    
 }    
 open.addEventListener(&quot;click&quot;, handleModalOpen);    
 close.addEventListener(&quot;click&quot;, handleModalClose);    
 modal.addEventListener(&quot;click&quot;, handleModalClick);    
 window.addEventListener(&quot;keydown&quot;, handleKeyDown);    
})();    
//# sourceURL=pen.js    
   </script>    
</body>    
</html>    
" sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation  allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true">    
</iframe>    
</div>    
</body>    
</html>

最后一个示例是另一个我可以肯定的组件:模态。模态的实现通常具有挑战性,因为它们需要提供许多功能,同时要易于访问和(理想情况下)美观。

我们要重点关注如何关闭模式。在此示例中,可以通过按下Esc键盘,单击模态中的按钮或单击模态之外的任何位置来实现。

在我们的JavaScript中,我们想听模式中某处的点击:

var modal = document.querySelector(".modal-outer");
modal.addEventListener("click", handleModalClick);

默认情况下,通过.is-hidden实用程序类隐藏模式。只有当用户单击红色的大按钮时,模态才会通过删除此类而打开。一旦打开了模态,单击它的任何地方(关闭按钮除外)都不会无意间将其关闭。事件侦听器回调函数负责:

function handleModalClick(evt) {
  // `evt.target` is the DOM node the user clicked on.
  if (!evt.target.closest(".modal-inner")) {
    handleModalClose();
  }
}

evt.target是被单击的DOM节点,在此示例中,是MODE后面的整个背景<div class="modal-outer">。此DOM节点不在内<div class="modal-inner">,因此Element.closest()可以使所有想要的气泡冒泡,而找不到它。条件将对此进行检查并触发handleModalClose功能。

单击节点内的某个位置(例如标题),将成<div class="modal-inner">为父节点。在这种情况下,条件不是真实的,将模式保留为打开状态。

关于浏览器支持...

与任何酷的"新"JavaScript API一样,需要考虑对浏览器的支持。好消息是,Element.closest它并不是一个新事物,并且已经在相当长的一段时间内在所有主要浏览器中得到支持,支持率高达94%。我会说这可以在生产环境中安全使用。

唯一不提供任何支持的浏览器是Internet Explorer(所有版本)。如果您必须支持IE,那么使用jQuery方法可能会更好。

如您所见,有一些非常可靠的用例Element.closest。过去,像jQuery这样使我们相对容易使用的库现在可以与原始JavaScript一起使用。

得益于良好的浏览器支持和易于使用的API,我在许多应用程序中都严重依赖此小方法,并且尚未感到失望。

赞(0)
未经允许不得转载:工具盒子 » 聊聊Web前端开发中关于JavaScript closest()的一些应用