最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - querySelector, get elements whose attribute begins with given string - Stack Overflow

programmeradmin1浏览0评论

I'm looking for a way to find all elements that contain an attribute that begins witha a given string. For example:

document.querySelectorAll('[ng-*]') would return all elements with Angular directives (ng-click, ng-show, ng-hide...).

But the wildcard does not seem to work.

Any ideas? I could implement my own, but it wouldn't be as effective.

I'm looking for a way to find all elements that contain an attribute that begins witha a given string. For example:

document.querySelectorAll('[ng-*]') would return all elements with Angular directives (ng-click, ng-show, ng-hide...).

But the wildcard does not seem to work.

Any ideas? I could implement my own, but it wouldn't be as effective.

Share Improve this question asked Jul 30, 2016 at 10:21 Jo ColinaJo Colina 1,9348 gold badges29 silver badges49 bronze badges 4
  • 1 There is no way to do this. – user663031 Commented Jul 30, 2016 at 10:24
  • See stackoverflow./questions/13222334/… or stackoverflow./questions/27326470/… or stackoverflow./questions/21222375/…. – user663031 Commented Jul 30, 2016 at 10:25
  • @torazaburo: None of those applies as they're all jQuery-specific. – T.J. Crowder Commented Jul 30, 2016 at 10:27
  • stackoverflow./questions/21222375/… seems like a pure dup. – user663031 Commented Jul 30, 2016 at 10:50
Add a ment  | 

2 Answers 2

Reset to default 5

There's no CSS selector that lets you do wildcards on the name of an attribute, just on the value, which obviously isn't want you want.

Unfortunately, you'll have to do the work manually.

If you know the list you'll be processing will be small, you can do it with a naive version query-then-filter; if you think it may be larger, a DOM walker solution (see below) would probably be better.

The naive query-then-filter (ES2015 syntax):

// 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 version:

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 solution (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>


For all of the above, note that String#startsWith may need a shim prior to ES2015.

@T.J.Crowder's answer is right in everything.

But IMK a faster way to implement a custom querySelector method should definitely be done with a TreeWalker.

Here is a simple (probably not perfect) implementation of what you are after, using the ng- filtering proposed by T.J:

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: any upvote on this answer should also be rewarded to T.J's one too.

发布评论

评论列表(0)

  1. 暂无评论