querySelector,获取属性以给定字符串开头的元素

querySelector, get elements whose attribute begins with given string

我正在寻找一种方法来查找包含以给定字符串开头的属性的所有元素。例如:

document.querySelectorAll('[ng-*]') 将 return 所有具有 Angular 指令的元素(ng-clickng-showng-hide...)。

但是通配符好像不起作用。

有什么想法吗?我可以自己实现,但效果不佳。

没有 CSS 选择器可以让您在属性的 name 上使用通配符,只能在值上使用通配符,这显然不是您想要的。

很遗憾,您必须手动完成这项工作。

如果您知道要处理的列表会很小,您可以使用简单版本的先查询后过滤来完成;如果您认为它可能更大,DOM 助行器解决方案(见下文)可能会更好。

朴素的先查询后过滤(ES2015 语法):

// Best if you can use *some* selector in the querySelectorAll rather than just *
// to keep the list to a minimum
let elements =
  [...document.querySelectorAll("*")].filter(
    element => [...element.attributes].some(
      attr => attr.nodeName.startsWith("ng-")
    )
  );
console.log(elements);
<div></div>
<div ng-bar></div>
<div></div>
<div ng-foo></div>
<div></div>

ES5 版本:

var elements = Array.prototype.filter.call(
  document.querySelectorAll("*"), // Best if you can use *something* here
                                  // to keep the list to a minimum
  function(element) {
    return Array.prototype.some.call(
      element.attributes,
      function(attr) {
        return attr.nodeName.startsWith("ng-");
      }
    );
  }
);
console.log(elements);
<div></div>
<div ng-bar></div>
<div></div>
<div ng-foo></div>
<div></div>


Walker解决方案(ES2015+):

function domFind(element, predicate, results = []) {
  if (!element.children) {
    throw new Error("Starting node must be an element or document");
  }
  if (predicate(element)) {
    results.push(element);
  }
  if (element.children && element.children.length) {
    [...element.children].forEach(child => {
      domFind(child, predicate, results);
    });
  }
  return results;
}
let elements = domFind(document, element => {
  return element.attributes && [...element.attributes].some(attr => attr.nodeName.startsWith("ng-"));
});
console.log(elements);
<div></div>
<div ng-bar></div>
<div></div>
<div>
  <div>
    <div ng-foo></div>
  </div>
  <div ng-baz></div>
</div>
<div></div>

ES5:

function domFind(element, predicate, results) {
  if (!results) {
    results = [];
  }
  if (!element.children) {
    throw new Error("Starting node must be an element or document");
  }
  if (predicate(element)) {
    results.push(element);
  }
  if (element.children && element.children.length) {
    Array.prototype.forEach.call(element.children, function(child) {
      domFind(child, predicate, results);
    });
  }
  return results;
}
var elements = domFind(document, function(element) {
  return element.attributes && Array.prototype.some.call(element.attributes, function(attr) {
    return attr.nodeName.startsWith("ng-");
  });
});
console.log(elements);
<div></div>
<div ng-bar></div>
<div></div>
<div>
  <div>
    <div ng-foo></div>
  </div>
  <div ng-baz></div>
</div>
<div></div>


对于以上所有内容,请注意 String#startsWith 在 ES2015 之前可能需要一个 shim。

@T.J.Crowder的回答什么都对

但是恕我直言,实现自定义 querySelector 方法的更快方法绝对应该使用 TreeWalker

这是一个简单(可能不完美)的实现,使用 T.J 提出的 ng- 过滤:

var filterAttr = function(element) {
  return Array.prototype.some.call(
    element.attributes,
    function(attr) {
      return attr.nodeName.indexOf("ng-") === 0;
    }
    // webkit defaults to FILTER_REJECT
  ) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; 
};
// IE doesn't like {acceptNode:...} object
filterAttr.acceptNode = filterAttr;

var treeWalker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  filterAttr,
  false
);
var nodeList = [];
while (treeWalker.nextNode()) {
  nodeList.push(treeWalker.currentNode);
}

console.log(nodeList);
<div></div>
<div ng-bar></div>
<div></div>
<div>
  <div>
    <div ng-foo></div>
  </div>
  <div ng-baz></div>
</div>
<div></div>

PS:对此答案的任何赞成票也应奖励给 T.J 的赞成票。