JS 替换所有匹配的 id 值



一个网站有多个页面,其中包含从另一个带有ID标签的页面导入的HTML,需要简化。

它目前看起来像这样。

<h2> 
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

有指向所有这些 ID 的锚链接,但这些链接不包括"用户内容-"部分。它们看起来像这样 链接到锚点。它们看起来不像这个 链接到锚点。这些 id 太多,无法手动更改。

如何使用 jQuery 或纯 JS 将所有 id 标签的值从id="user-content-test1更改为仅id="test1?期望的结果应该是:

<h2> 
<a id="test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2> 
<a id="rest4" href="https://www.example.com">
Anything
</a>
</h2>

我已经搜索了堆栈溢出和谷歌,但我只找到如何替换字符串,而不是 ID。我已经尝试了这两个脚本,但没有结果。

<script>
$(document).ready(function(){
let result = 'user-content-'.replaceAll(/+/g, ' ');
});
</script>
<script>
$(document).ready(function(){
var find = 'user-content-';
var re = new RegExp(find, 'g');
str = str.replace(re, '');});
</script>

您可以非常快速地使用 jQuery 匹配<h2>标签中的所有<a>标签,然后替换它们的所有 ID。您的问题可以简化为更小的步骤:

  • 找到匹配所需元素的方法
  • 找到获取这些元素的 ID 属性的方法
  • 操作该 ID 属性中的字符串

前两个可以用jQuery或Pure JS完成,第三个可以通过简单的字符串操作来完成,就像String.slice()一样,因为"user-content-"无论如何都是固定长度的。

使用选择器$("h2 a[id^=user-content]")-> 这意味着选择以用户内容开头的所有 ID

$(document).ready(function() {
$("h2 a[id^=user-content]").each((i, e) => {
let id = $(e).attr("id");
$(e).attr("id", id.replace("user-content-", ""));
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

由于问题是您在文档中的其他地方有链接到这些特定元素的锚点,因此我能想到三种方法来解决问题:

  1. 更改这些元素的id,使其与链接到它们的<a>元素的片段标识符匹配(如您的问题明确提出的那样),或
  2. 更改链接到它们的元素的href;这意味着针对这些元素的任何样式都不需要调整其选择器。

在这些潜在的解决方案中,都有一个相对简单的JavaScript和jQuery解决方案。

所有方法都将使用以下 HTML 和 CSS:

:root {
--color: #000f;
--backgroundColor: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: sans-serif;
line-height: 1.5;
}
nav ul {
display: flex;
justify-content: space-around;
list-style-type: none;
}
nav a:is(:link, :visited) {
color: var(--color);
background-color: var(--backgroundColor);
}
nav a:is(:hover, :active, :focus) {
color: var(--backgroundColor);
background-color: var(--color);
}
h2 {
margin-block: 3em;
}
h2 a:is(:link, :visited) {
background: linear-gradient(90deg, lime, #ffff);
display: block;
color: var(--color);
text-decoration: none;
}
h2 a:is(:hover, :active, :focus) {
text-decoration: underline;
text-decoration-thickness: 3px;
}
h2 a:target {
background: linear-gradient(90deg, #f90, #ffff);
}
h2 a::after {
content: ' (#' attr(id) ').';
}
<nav id="toc">
<ul>
<li><a href="#test1">Link to "test1"</a></li>
<li><a href="#best2">Link to "best2"</a></li>
<li><a href="#nest3">Link to "nest3"</a></li>
<li><a href="#rest4">Link to "rest4"</a></li>
</ul>
</nav>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

因此,要直接回答您的问题:如何更改user-content-*元素的id以删除user-content-部分:

首先,使用纯JavaScript:

// utility function written by: Michał Perłakowski
// (https://stackoverflow.com/users/3853934/)
// taken from:
// https://stackoverflow.com/a/39977764/82548
// used because the behaviour of Object.assign() doesn't
// work well in merging objects with unspecifed values/keys:
const assign = (target, ...sources) =>
Object.assign(target, ...sources.map(x =>
Object.entries(x)
.filter(([key, value]) => value !== undefined)
.reduce((obj, [key, value]) => (obj[key] = value, obj), {})
));
// defining a named function, which accepts an Object of user-
// defined options in order to change the default behaviour of
// the function; if no Object is passed in the function
// sets the opts variable to an empty Object:
function removeTextFromAttribute(opts = {}) {
// these are the default settings for the function:
let defaults = {
// String, accepts a CSS selector to select the
// relevant elements:
elements: 'a',
// String, accepts the attribute-name from which
// you wish to remove characters:
attribute: 'id',
// String, the string you wish to remove from
// those attributes:
remove: 'user-content-',
// Boolean, does the attribute start with the String
// you wish to remove:
startsWith: false,
// Boolean, does the attribute end with the String
// you wish to remove:
endsWith: false,
},
// here we use the utility function (cited above) in
// order to compensate for the behaviour of the
// native Object.assign() functionality (see the
// question related to the linked answer). It
// seems that objects later in the argument-list of
// the function overwrite declared keys of previous
// Objects, and we want the user to overwrite the
// defaults; so remember to put the user-defined
// opts Object last in order that it overwrites
// the defaults:
settings = assign({}, defaults, opts);
// using Object destructuring assignment to assign the
// resulting options to named variables (mainly to avoid
// having to type settings.elements, settings.attribute...):
const {
elements,
attribute,
remove,
startsWith,
endsWith
} = settings;
// declaring a variable, without initialising it:
let selector;
// if the user sets both the startsWith and EndsWith value
// to true, then the attribute-value to be changed may either
// be equal to the provided String or it may both begin and
// start with the provided string; to simplify selection in
// these cases we set the selector variable to '*=' which
// selects an element with the attribute, and an attribute-
// value appears within the String at least once. Otherwise
// if the startsWith and endsWith are both false this implies
// that the remove String appears within the attribute-value
// somewhere, so we also use the '*=' selector:
if ((startsWith && endsWith) || (!startsWith && !endsWith)) {
selector = '*=';
// if startsWith is true and endsWith is not, we use the
// attribute-value-starts with selector:
} else if (startsWith && !endsWith) {
selector = '^=';
// finally if startsWith is falsey, and endsWith is true:
// we use the attribute-value-ends-with selector:
} else if (!startsWith && endsWith) {
selector = '$=';
}
// here we use document.querySelectorAll to select all
// elements that match the created-selector, which takes
// the form of: 'Element[attribute<selector>"StringToRemove"]',
// for example: 'a[id^="user-content-"]'
const haystack = document.querySelectorAll(
// we use a template literal string to interpolate the
// various variables into the resulting String, this is
// in order to avoid having to concatenate Strings and
// variables:
`${elements}[${attribute}${selector}"${remove}"]`
);
// we use NodeList.prototype.forEach() to iterate over the 
// list of Nodes returned:
haystack.forEach(
// we use an Arrow function, since we have no need to use
// 'this'; 'el' is a reference to the current Node of the
// NodeList over which we're iterating:
(el) => {
// we use Element.getAttribute to retrieve the attribute
// to be modified:
let attr = el.getAttribute(attribute);
-
});
}
// calling the named function, in this case with all the default
// settings and passing no arguments as the defaults are set to
// your specified functionality:
removeTextFromAttribute();
:root {
--color: #000f;
--backgroundColor: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: sans-serif;
line-height: 1.5;
}
nav ul {
display: flex;
justify-content: space-around;
list-style-type: none;
}
nav a:is(:link, :visited) {
color: var(--color);
background-color: var(--backgroundColor);
}
nav a:is(:hover, :active, :focus) {
color: var(--backgroundColor);
background-color: var(--color);
}
h2 {
margin-block: 3em;
}
h2 a:is(:link, :visited) {
background: linear-gradient(90deg, lime, #ffff);
display: block;
color: var(--color);
text-decoration: none;
}
h2 a:is(:hover, :active, :focus) {
text-decoration: underline;
text-decoration-thickness: 3px;
}
h2 a:target {
background: linear-gradient(90deg, #f90, #ffff);
}
h2 a::after {
content: ' (#' attr(id) ').';
}
<nav id="toc">
<ul>
<li><a href="#test1">Link to "test1"</a></li>
<li><a href="#best2">Link to "best2"</a></li>
<li><a href="#nest3">Link to "nest3"</a></li>
<li><a href="#rest4">Link to "rest4"</a></li>
</ul>
</nav>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

使用 jQuery:

// here we're creating a jQuery plugin using the IIFE - Immediately-
// Invoked Function Expression - approach:
(function($) {
// defining the name of the plugin, and passing in the opts argument:
$.fn.removeTextFromAttribute = function(opts) {
// defining the defaults (as above), although we're not
// defining the element-types upon which we wish to work
// as jQuery passes that collection to the plugin:
let defaults = {
attribute: 'id',
remove: 'user-content-',
startsWith: false,
endsWith: false,
},
// a far easier means of combining the user-defined
// properties with the default properties (bear in
// mind this is simple because the library has taken
// care of it elsewhere, so it's more concise for
// yourself but obviously including the whole library
// has its own cost/benefit analysis to be undertaken):
settings = $.extend(defaults, opts);
// again using destructuring assignment:
const {
attribute,
remove,
startsWith,
endsWith
} = settings;
// declaring, but not initialising, the 'selector' variable:
let selector;
if ((startsWith && endsWith) || (!startsWith && !endsWith)) {
selector = '*=';
} else if (startsWith && !endsWith) {
selector = '^=';
} else if (!startsWith && endsWith) {
selector = '$=';
}
// using a template String to create the attribute-selector:
selectorString = `[${attribute}${selector}"${remove}"]`
// here we return the results of these chained method calls,
// first we use the filter() method:
return this.filter(function(index, elem) {
// we use a native JavaScript Element.matches method
// to establish whether the current Node of the jQuery
// collection matches the selector string we've
// created above; if yes (Boolean true) that node is
// retained in the collection, if not (Boolean false)
// the current Node is discarded from the collection:
return this.matches(selectorString);

// next we use the attr() method as a setter, in order
// to modify the named attribute, and use the 
// anonymous callback function to modify each attribute
// in turn:
}).attr(attribute, function(i, attr) {
// this is exactly the same as the above - Plain
// JavaScript - version:
if (attr === remove) {
this.removeAttribute(attribute);
} else if (attr.includes(remove)) {
this.setAttribute(
attribute,
attr.replace(new RegExp(remove, 'g'), ''));
}
});
}
// passing jQuery into the function:
}(jQuery));
// note that here we selected all <a> elements, this forms
// the collection passed to the jQuery plugin and is why
// we didn't need to specify the attribute-type within the
// plugin:
$('a').removeTextFromAttribute();
:root {
--color: #000f;
--backgroundColor: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: sans-serif;
line-height: 1.5;
}
nav ul {
display: flex;
justify-content: space-around;
list-style-type: none;
}
nav a:is(:link, :visited) {
color: var(--color);
background-color: var(--backgroundColor);
}
nav a:is(:hover, :active, :focus) {
color: var(--backgroundColor);
background-color: var(--color);
}
h2 {
margin-block: 3em;
}
h2 a:is(:link, :visited) {
background: linear-gradient(90deg, lime, #ffff);
display: block;
color: var(--color);
text-decoration: none;
}
h2 a:is(:hover, :active, :focus) {
text-decoration: underline;
text-decoration-thickness: 3px;
}
h2 a:target {
background: linear-gradient(90deg, #f90, #ffff);
}
h2 a::after {
content: ' (#' attr(id) ').';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav id="toc">
<ul>
<li><a href="#test1">Link to "test1"</a></li>
<li><a href="#best2">Link to "best2"</a></li>
<li><a href="#nest3">Link to "nest3"</a></li>
<li><a href="#rest4">Link to "rest4"</a></li>
</ul>
</nav>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

第二种方法 — 更新链接到这些<a>元素的<a>元素的href属性与上面使用的方法非常相似,但显然我们正在插入/更新属性值而不是从中删除。因此,这将采用一个新函数,如下所示:

首先,使用纯JavaScript:

// utility function written by: Michał Perłakowski
// (https://stackoverflow.com/users/3853934/)
// taken from:
// https://stackoverflow.com/a/39977764/82548
// used because the behaviour of Object.assign() doesn't
// work well in merging objects with unspecifed values/keys:
const assign = (target, ...sources) =>
Object.assign(target, ...sources.map(x =>
Object.entries(x)
.filter(([key, value]) => value !== undefined)
.reduce((obj, [key, value]) => (obj[key] = value, obj), {})
));
// new named function, set up in the same way as above,
// albeit with new arguments:
function insertTextIntoAttribute(opts = {}) {
let defaults = {
attribute: 'id',
elements: 'a',
// Boolean, do you wish to insert the new String
// at the end of the current value?
endWith: false,
// String, the string you wish to insert:
insert: 'user-content-',
// Boolean, do you wish to insert the new String
// at the start of the current value?
startWith: true,
},
settings = assign({}, defaults, opts);
const {
elements,
attribute,
insert,
startWith,
endWith
} = settings,
// using a template literal to create a simple selector
// to find the elements that match your requirements:
selectorString = `${elements}`;
// using document.querySelectorAll() to retrieve a 
// NodeList of elements that match the selector
// passed to the function:
const haystack = document.querySelectorAll(
selectorString
);
// NodeList.prototype.forEach() to iterate over the
// returned NodeList:
haystack.forEach(
(el) => {
// we retrieve the current attribute-value of the
// relevant element:
let currentValue = el.getAttribute(attribute),
// because a hash requires some special consideration
// (the '#' character has to be at the beginning) we
// initialise this variable to false:
isHash = false;
// we use Element.matches to see if the current element
// of the NodeList is an <a> element (we could have instead
// used el.tagName === 'A') but Element.matches is
// more concise, easier to read and doesn't require a
// comparison), it is we then check if the current attribute-
// value matches the <a> element's hash:
if (el.matches('a') && currentValue === el.hash) {
// if it does we then update the isHash variable to true:
isHash = true;
}
// here we use Element.setAttribute() to update the named
// attribute (first argument) to a new value:
el.setAttribute(attribute,
// this is perhaps a little confusing to read, as we're
// taking advantage of Template strings' ability to
// interpolate a variable into the String, and we're
// using conditional operators to do so. In order:
// 1. ${isHash ? '#' : ''}
// we test isHash; if true/truthy
// the expression returns the '#' character, if false/falsey
// the expression returns the empty String ''.
// 2. ${startWith ? insert : ''}
// we test startWith; if true/truthy the expression returns
// the 'insert' variable's value, otherwise if startWith is
// false/falsey the expression returns the empty-string.
// 3. ${isHash ? currentValue.replace('#','')
// here we again test the isHash variable, and if true/truthy
// the expression returns the result of calling
// String.prototype.replace() on the current attribute-value
// of the element; if isHash is false/falsey then it simply
// returns the current attribute-value.
// 4. ${endWith ? insert : ''}
// this is exactly the same as the earlier assessment for
// the startWith variable, if true/truthy we return the
// content of the insert variable, otherwise if false/falsey
// we return an empty String:
`${isHash ? '#' : ''}${startWith ? insert : ''}${isHash ? currentValue.replace('#','') : currentValue}${endWith ? insert : ''}`
);
});
}
// here we call the function, specifying our
// options:
insertTextIntoAttribute({
// we wish to modify the 'href' attribute:
attribute: 'href',
// and we're selecing the <a> elements inside of <li> elements
// inside of the <nav> element:
elements: 'nav li a',
});
:root {
--color: #000f;
--backgroundColor: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: sans-serif;
line-height: 1.5;
}
nav ul {
display: flex;
justify-content: space-around;
list-style-type: none;
}
nav a:is(:link, :visited) {
color: var(--color);
background-color: var(--backgroundColor);
}
nav a:is(:hover, :active, :focus) {
color: var(--backgroundColor);
background-color: var(--color);
}
h2 {
margin-block: 3em;
}
h2 a:is(:link, :visited) {
background: linear-gradient(90deg, lime, #ffff);
display: block;
color: var(--color);
text-decoration: none;
}
h2 a:is(:hover, :active, :focus) {
text-decoration: underline;
text-decoration-thickness: 3px;
}
h2 a:target {
background: linear-gradient(90deg, #f90, #ffff);
}
h2 a::after {
content: ' (#' attr(id) ').';
}
<nav id="toc">
<ul>
<li><a href="#test1">Link to "test1"</a></li>
<li><a href="#best2">Link to "best2"</a></li>
<li><a href="#nest3">Link to "nest3"</a></li>
<li><a href="#rest4">Link to "rest4"</a></li>
</ul>
</nav>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

使用 jQuery:

// here we're creating a jQuery plugin using the IIFE - Immediately-
// Invoked Function Expression - approach:
(function($) {
// defining the name of the plugin, and passing in the opts argument:
$.fn.insertTextToAttribute = function(opts) {
// defining the defaults (as above), although again we're not
// defining the element-types upon which we wish to work
// as jQuery passes that collection to the plugin:
let defaults = {
attribute: 'id',
endWith: false,
insert: 'user-content-',
startWith: false,
},
settings = $.extend(defaults, opts);
// again using destructuring assignment:
const {
attribute,
insert,
startWith,
endWith
} = settings;
// we're not filtering the collection here, since jQuery has
// already taken care of finding the relevant <a> elements and
// we don't need to select according to current attribute-values
// since we're modifying them we may not know what they currently
// are:
return this.attr(attribute, function(i, attr) {
// setting isHash to false:
let isHash = false;
// as above, we're using this - as jQuery makes that available
// within its methods - and again using Element.matches, along
// with checking that the current attribute-value - passed to
// function automatically from jQuery - is equal to the current
// hash of the element:
if (this.matches('a') && attr === this.hash) {
// if both those checks return true, we update the value to
// true
isHash = true;
}

// note that we could have written the above in the following way:
// let isHash = this.matches('a') && attr === this.hash;
// but using an if feels more readable (despite my subsequent
// code)

// this is almost the same as the above approach, using conditional
// operators within template strings to interpolate various variables
// into the string. The difference is that the 'currentValue'
// variable was changed to the 'attr' variable-name (for no particular
// reason):
return `${isHash ? '#' : ''}${startWith ? insert : ''}${isHash ? attr.replace('#','') : attr}${endWith ? insert : ''}`
});
}
// passing jQuery into the function:
}(jQuery));
// selecting all <a> elements on the page that are within <li> elements
// and wrapped within a <nav> element:
$('nav li a').insertTextToAttribute({
attribute: 'href',
insert: 'user-content-',
startWith: true
});
:root {
--color: #000f;
--backgroundColor: #fff;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: sans-serif;
line-height: 1.5;
}
nav ul {
display: flex;
justify-content: space-around;
list-style-type: none;
}
nav a:is(:link, :visited) {
color: var(--color);
background-color: var(--backgroundColor);
}
nav a:is(:hover, :active, :focus) {
color: var(--backgroundColor);
background-color: var(--color);
}
h2 {
margin-block: 3em;
}
h2 a:is(:link, :visited) {
background: linear-gradient(90deg, lime, #ffff);
display: block;
color: var(--color);
text-decoration: none;
}
h2 a:is(:hover, :active, :focus) {
text-decoration: underline;
text-decoration-thickness: 3px;
}
h2 a:target {
background: linear-gradient(90deg, #f90, #ffff);
}
h2 a::after {
content: ' (#' attr(id) ').';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav id="toc">
<ul>
<li><a href="#test1">Link to "test1"</a></li>
<li><a href="#best2">Link to "best2"</a></li>
<li><a href="#nest3">Link to "nest3"</a></li>
<li><a href="#rest4">Link to "rest4"</a></li>
</ul>
</nav>
<h2>
<a id="user-content-test1" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-best2" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-nest3" href="https://www.example.com">
Anything
</a>
</h2>
<h2>
<a id="user-content-rest4" href="https://www.example.com">
Anything
</a>
</h2>

由于 Stack Overflow 答案的字符限制,我无法包含拦截浏览器<a>元素点击事件的方法,尽管我想它在其他地方已经写过了。希望你至少能够从答案中学到一些有用的东西。

最新更新