如何在编辑文本区域时计算指定字符序列的出现次数?
How to count the occurrences of specified character sequences while editing a textarea?
我正在寻找一种无需单击按钮即可自动计算文本区域中指定单词数量的方法,在这种情况下,如果有更改则自动更新它..
到目前为止,这是我的混乱代码:
function findWord1() {
var outputDiv = $('#opening-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){ }\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a>');
}
function findWord2() {
var outputDiv = $('#closing-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord3() {
var outputDiv = $('#opening-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[A>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord4() {
var outputDiv = $('#closing-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;A]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord5() {
var outputDiv = $('#opening-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[D>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord6() {
var outputDiv = $('#closing-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;D]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<button onclick="findWord1();findWord2();findWord3();findWord4();findWord5();findWord6();">Count</button>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td><a id="opening-added-bracket"></a></td>
</tr>
<tr>
<td><A]</td>
<td><a id="closing-added-bracket"></a></td>
</tr>
<tr>
<td>[D></td>
<td><a id="opening-deleted-bracket"></a></td>
</tr>
<tr>
<td><D]</td>
<td><a id="closing-deleted-bracket"></a></td>
</tr>
<tr>
<td>[</td>
<td><a id="opening-bracket"></a></td>
</tr>
<tr>
<td>]</td>
<td><a id="closing-bracket"></a></td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>
此外,如果可能的话,我想保留“计数”按钮,这样我仍然可以手动执行,以防代码的其他部分失败..
如果我的代码看起来很糟糕,我深表歉意,但在此先感谢您的帮助。
使用keyup
就这样
function countWordFunc() {
let myText = document.getElementById("myText").value;
let wordsArr = myText.trim().split(" ")
let countWords = wordsArr.filter(word => word !== "").length
let counter = document.getElementById("counter");
counter.innerHTML = `Total Words: ${countWords}`;
}
<textarea onkeyup="countWordFunc()" id="myText">Hello, World</textarea>
<div id="counter"></div>
创建一个调用所有其他函数的函数。这是简化代码的一步。然后,为文本区域的 input
事件创建一个事件侦听器,并将新函数作为其回调。
这个问题本质上是许多其他涉及事件的问题的重复。请参阅 https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events 了解一些有用的信息,如果您有其他问题,请在此处搜索更多答案。
function findAllWords() {
findWord1();
findWord2();
findWord3();
findWord4();
findWord5();
findWord6();
}
document.getElementById('textarea3').addEventListener('input', findAllWords);
function findWord1() {
var outputDiv = $('#opening-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){ }\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a>');
}
function findWord2() {
var outputDiv = $('#closing-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord3() {
var outputDiv = $('#opening-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[A>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord4() {
var outputDiv = $('#closing-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;A]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord5() {
var outputDiv = $('#opening-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[D>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord6() {
var outputDiv = $('#closing-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;D]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td>
<a id="opening-added-bracket"></a>
</td>
</tr>
<tr>
<td><A]</td>
<td>
<a id="closing-added-bracket"></a>
</td>
</tr>
<tr>
<td>[D></td>
<td>
<a id="opening-deleted-bracket"></a>
</td>
</tr>
<tr>
<td><D]</td>
<td>
<a id="closing-deleted-bracket"></a>
</td>
</tr>
<tr>
<td>[</td>
<td>
<a id="opening-bracket"></a>
</td>
</tr>
<tr>
<td>]</td>
<td>
<a id="closing-bracket"></a>
</td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>
试试这个,将功能合二为一:
let textarea = $('#textarea3');
textarea.on('keyup', _ => counting());
function counting() {
var searchText = $('#textarea3').val();
let words = [];
words['[A>'] = '#opening-added-bracket';
words['<A]'] = '#closing-added-bracket';
words['[D>'] = '#opening-deleted-bracket';
words['<D]'] = '#closing-deleted-bracket';
words['['] = '#opening-bracket';
words[']'] = '#closing-bracket';
for (const word in words) {
var outputDiv = $(words[word]);
outputDiv.empty();
let count = searchText.split(word).length - 1;
searchText = searchText.replaceAll(word,'');
outputDiv.append('<a>' + count + '</a>');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button onclick="counting();">Count</button>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td><a id="opening-added-bracket"></a></td>
</tr>
<tr>
<td><A]</td>
<td><a id="closing-added-bracket"></a></td>
</tr>
<tr>
<td>[D></td>
<td><a id="opening-deleted-bracket"></a></td>
</tr>
<tr>
<td><D]</td>
<td><a id="closing-deleted-bracket"></a></td>
</tr>
<tr>
<td>[</td>
<td><a id="opening-bracket"></a></td>
</tr>
<tr>
<td>]</td>
<td><a id="closing-bracket"></a></td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
<A]
[D>
<D]
</textarea>
OP 的代码存在以下问题...
基于特定 元素的代码 id
s 永远无法转换为足够通用的 re-usable 代码。
计算单个括号是危险的,特别是因为它们是其他要匹配的字符序列(OP 称为单词)的一部分。
因此,人们可能会考虑一种尽可能可配置和通用的方法,poor-mans,component-like 方法类似于下一个提供的示例代码,其中...
一个例如可以通过正则表达式配置要匹配的项目。
还可以配置组件的 UI/UX 行为,如 auto-count。
每个配置都是基于data-*
attributes and its HTMLElement.dataset对应的。
由于上述第 3 个 bullet-point,只要正确提供必要的 data-*
属性,组件对其标记是不可知的。
这种方法也可以用我的评论之一来概括......
"Initialize a component like structure by reading and computing the config settings, and upon the latter register the count handler with the correct events. The count handler itself then is a straightforward regex based pattern matching (which also works correctly unlike all the other solutions) and count rendering task. Element IDs are not needed, markup is freely selectable, configurations are applied via markup and data attributes. One does not need to touch JS code in order to introduce more to be matched items/words."
function displayItemCount(itemRoot, target) {
const elmPattern =
target && itemRoot?.querySelector('[data-reg-pattern]');
const elmCount =
elmPattern && itemRoot?.querySelector('[data-match-count]');
const regXItem =
elmCount && RegExp(elmPattern.dataset.regPattern, 'g');
if (regXItem) {
elmCount.textContent = (target.value.match(regXItem) ?? []).length;
}
}
function displayItemCountsOfBoundComponentData() {
const { rootNode, countsTarget } = this;
rootNode
.querySelectorAll('[data-item-match]')
.forEach(itemRoot =>
displayItemCount(itemRoot, countsTarget)
);
}
function initializeItemCountsComponent(rootNode) {
const { dataset } = rootNode;
let {
countsTargetSelector = '',
triggerCountsSelector = '',
autoCount = false,
} = dataset;
countsTargetSelector = countsTargetSelector.trim();
triggerCountsSelector = triggerCountsSelector.trim();
const countsTarget = (
rootNode.querySelector(countsTargetSelector || null) ||
document.querySelector(countsTargetSelector || null)
);
if (countsTarget) {
let countsTriggers = rootNode
.querySelectorAll(triggerCountsSelector || null);
if (countsTriggers.length === 0) {
countsTriggers = document
.querySelectorAll(triggerCountsSelector || null);
}
const isAutoCount = (
(countsTriggers.length === 0) || (
(autoCount !== false) && (
(autoCount.trim() === '') ||
(autoCount.trim().toLowerCase() === 'true')
)
)
);
const displayItemCounts =
displayItemCountsOfBoundComponentData.bind({
rootNode,
countsTarget,
});
countsTriggers.forEach(elmNode =>
elmNode
.addEventListener('click', displayItemCounts)
);
if (isAutoCount) {
countsTarget
.addEventListener('input', displayItemCounts);
// trigger component's initial item counts.
displayItemCounts();
}
}
}
function main() {
document
.querySelectorAll('[data-item-counts]')
.forEach(initializeItemCountsComponent);
}
main();
body { margin: 0; }
article { position: relative; display: inline-block; margin: 0 20px 0 0; }
[data-item-counts] { float: left; margin: 0 10px 0 0; }
textarea { margin: 0; }
button { position: absolute; left: 0; top: 175px; }
<article>
<table
data-item-counts
data-counts-target-selector="#textarea3"
data-trigger-counts-selector="#count-textarea3-items"
>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr data-item-match>
<td data-reg-pattern="\[A>" title="Opening Added Bracket">[A></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<A]" title="Closing Added Bracket"><A]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<D]" title="Closing Deleted Bracket"><D]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
<td data-match-count>.?.</td>
</tr>
</tbody>
</table>
<textarea
id="textarea3"
cols="16"
rows="12">
[A><A][D><D][[]][]<A][D>
[A><A][D><D][[]][]<A][D>
... explicitly triggered count.</textarea>
<button id="count-textarea3-items">Count Items</button>
</article>
<article>
<table
data-item-counts
data-counts-target-selector="#textarea54"
data-auto-count
>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr data-item-match>
<td data-reg-pattern="\[A>" title="Opening Added Bracket">[A></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<A]" title="Closing Added Bracket"><A]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<D]" title="Closing Deleted Bracket"><D]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
<td data-match-count>.?.</td>
</tr>
</tbody>
</table>
<textarea
id="textarea54"
cols="16"
rows="12"
>[[]][]<A][D>[A><A][D><D]
... auto-count while editing.</textarea>
</article>
我正在寻找一种无需单击按钮即可自动计算文本区域中指定单词数量的方法,在这种情况下,如果有更改则自动更新它..
到目前为止,这是我的混乱代码:
function findWord1() {
var outputDiv = $('#opening-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){ }\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a>');
}
function findWord2() {
var outputDiv = $('#closing-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord3() {
var outputDiv = $('#opening-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[A>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord4() {
var outputDiv = $('#closing-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;A]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord5() {
var outputDiv = $('#opening-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[D>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord6() {
var outputDiv = $('#closing-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;D]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<button onclick="findWord1();findWord2();findWord3();findWord4();findWord5();findWord6();">Count</button>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td><a id="opening-added-bracket"></a></td>
</tr>
<tr>
<td><A]</td>
<td><a id="closing-added-bracket"></a></td>
</tr>
<tr>
<td>[D></td>
<td><a id="opening-deleted-bracket"></a></td>
</tr>
<tr>
<td><D]</td>
<td><a id="closing-deleted-bracket"></a></td>
</tr>
<tr>
<td>[</td>
<td><a id="opening-bracket"></a></td>
</tr>
<tr>
<td>]</td>
<td><a id="closing-bracket"></a></td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>
此外,如果可能的话,我想保留“计数”按钮,这样我仍然可以手动执行,以防代码的其他部分失败..
如果我的代码看起来很糟糕,我深表歉意,但在此先感谢您的帮助。
使用keyup
就这样
function countWordFunc() {
let myText = document.getElementById("myText").value;
let wordsArr = myText.trim().split(" ")
let countWords = wordsArr.filter(word => word !== "").length
let counter = document.getElementById("counter");
counter.innerHTML = `Total Words: ${countWords}`;
}
<textarea onkeyup="countWordFunc()" id="myText">Hello, World</textarea>
<div id="counter"></div>
创建一个调用所有其他函数的函数。这是简化代码的一步。然后,为文本区域的 input
事件创建一个事件侦听器,并将新函数作为其回调。
这个问题本质上是许多其他涉及事件的问题的重复。请参阅 https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events 了解一些有用的信息,如果您有其他问题,请在此处搜索更多答案。
function findAllWords() {
findWord1();
findWord2();
findWord3();
findWord4();
findWord5();
findWord6();
}
document.getElementById('textarea3').addEventListener('input', findAllWords);
function findWord1() {
var outputDiv = $('#opening-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){ }\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a>');
}
function findWord2() {
var outputDiv = $('#closing-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord3() {
var outputDiv = $('#opening-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[A>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord4() {
var outputDiv = $('#closing-added-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;A]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord5() {
var outputDiv = $('#opening-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "[D>";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
function findWord6() {
var outputDiv = $('#closing-deleted-bracket');
var searchText = $('#textarea3').val();
var wordMatch = "lt;D]";
outputDiv.empty();
var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\+*?[^\]$(){}\|])/g, "\"), "ig"));
outputDiv.append('<a>' + (m ? m.length : 0) + '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td>
<a id="opening-added-bracket"></a>
</td>
</tr>
<tr>
<td><A]</td>
<td>
<a id="closing-added-bracket"></a>
</td>
</tr>
<tr>
<td>[D></td>
<td>
<a id="opening-deleted-bracket"></a>
</td>
</tr>
<tr>
<td><D]</td>
<td>
<a id="closing-deleted-bracket"></a>
</td>
</tr>
<tr>
<td>[</td>
<td>
<a id="opening-bracket"></a>
</td>
</tr>
<tr>
<td>]</td>
<td>
<a id="closing-bracket"></a>
</td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>
试试这个,将功能合二为一:
let textarea = $('#textarea3');
textarea.on('keyup', _ => counting());
function counting() {
var searchText = $('#textarea3').val();
let words = [];
words['[A>'] = '#opening-added-bracket';
words['<A]'] = '#closing-added-bracket';
words['[D>'] = '#opening-deleted-bracket';
words['<D]'] = '#closing-deleted-bracket';
words['['] = '#opening-bracket';
words[']'] = '#closing-bracket';
for (const word in words) {
var outputDiv = $(words[word]);
outputDiv.empty();
let count = searchText.split(word).length - 1;
searchText = searchText.replaceAll(word,'');
outputDiv.append('<a>' + count + '</a>');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button onclick="counting();">Count</button>
<table>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>[A></td>
<td><a id="opening-added-bracket"></a></td>
</tr>
<tr>
<td><A]</td>
<td><a id="closing-added-bracket"></a></td>
</tr>
<tr>
<td>[D></td>
<td><a id="opening-deleted-bracket"></a></td>
</tr>
<tr>
<td><D]</td>
<td><a id="closing-deleted-bracket"></a></td>
</tr>
<tr>
<td>[</td>
<td><a id="opening-bracket"></a></td>
</tr>
<tr>
<td>]</td>
<td><a id="closing-bracket"></a></td>
</tr>
</tbody>
</table>
<textarea id="textarea3" rows="5">
[A>
<A]
[D>
<D]
</textarea>
OP 的代码存在以下问题...
基于特定 元素的代码
id
s 永远无法转换为足够通用的 re-usable 代码。计算单个括号是危险的,特别是因为它们是其他要匹配的字符序列(OP 称为单词)的一部分。
因此,人们可能会考虑一种尽可能可配置和通用的方法,poor-mans,component-like 方法类似于下一个提供的示例代码,其中...
一个例如可以通过正则表达式配置要匹配的项目。
还可以配置组件的 UI/UX 行为,如 auto-count。
每个配置都是基于
data-*
attributes and its HTMLElement.dataset对应的。由于上述第 3 个 bullet-point,只要正确提供必要的
data-*
属性,组件对其标记是不可知的。
这种方法也可以用我的评论之一来概括......
"Initialize a component like structure by reading and computing the config settings, and upon the latter register the count handler with the correct events. The count handler itself then is a straightforward regex based pattern matching (which also works correctly unlike all the other solutions) and count rendering task. Element IDs are not needed, markup is freely selectable, configurations are applied via markup and data attributes. One does not need to touch JS code in order to introduce more to be matched items/words."
function displayItemCount(itemRoot, target) {
const elmPattern =
target && itemRoot?.querySelector('[data-reg-pattern]');
const elmCount =
elmPattern && itemRoot?.querySelector('[data-match-count]');
const regXItem =
elmCount && RegExp(elmPattern.dataset.regPattern, 'g');
if (regXItem) {
elmCount.textContent = (target.value.match(regXItem) ?? []).length;
}
}
function displayItemCountsOfBoundComponentData() {
const { rootNode, countsTarget } = this;
rootNode
.querySelectorAll('[data-item-match]')
.forEach(itemRoot =>
displayItemCount(itemRoot, countsTarget)
);
}
function initializeItemCountsComponent(rootNode) {
const { dataset } = rootNode;
let {
countsTargetSelector = '',
triggerCountsSelector = '',
autoCount = false,
} = dataset;
countsTargetSelector = countsTargetSelector.trim();
triggerCountsSelector = triggerCountsSelector.trim();
const countsTarget = (
rootNode.querySelector(countsTargetSelector || null) ||
document.querySelector(countsTargetSelector || null)
);
if (countsTarget) {
let countsTriggers = rootNode
.querySelectorAll(triggerCountsSelector || null);
if (countsTriggers.length === 0) {
countsTriggers = document
.querySelectorAll(triggerCountsSelector || null);
}
const isAutoCount = (
(countsTriggers.length === 0) || (
(autoCount !== false) && (
(autoCount.trim() === '') ||
(autoCount.trim().toLowerCase() === 'true')
)
)
);
const displayItemCounts =
displayItemCountsOfBoundComponentData.bind({
rootNode,
countsTarget,
});
countsTriggers.forEach(elmNode =>
elmNode
.addEventListener('click', displayItemCounts)
);
if (isAutoCount) {
countsTarget
.addEventListener('input', displayItemCounts);
// trigger component's initial item counts.
displayItemCounts();
}
}
}
function main() {
document
.querySelectorAll('[data-item-counts]')
.forEach(initializeItemCountsComponent);
}
main();
body { margin: 0; }
article { position: relative; display: inline-block; margin: 0 20px 0 0; }
[data-item-counts] { float: left; margin: 0 10px 0 0; }
textarea { margin: 0; }
button { position: absolute; left: 0; top: 175px; }
<article>
<table
data-item-counts
data-counts-target-selector="#textarea3"
data-trigger-counts-selector="#count-textarea3-items"
>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr data-item-match>
<td data-reg-pattern="\[A>" title="Opening Added Bracket">[A></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<A]" title="Closing Added Bracket"><A]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<D]" title="Closing Deleted Bracket"><D]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
<td data-match-count>.?.</td>
</tr>
</tbody>
</table>
<textarea
id="textarea3"
cols="16"
rows="12">
[A><A][D><D][[]][]<A][D>
[A><A][D><D][[]][]<A][D>
... explicitly triggered count.</textarea>
<button id="count-textarea3-items">Count Items</button>
</article>
<article>
<table
data-item-counts
data-counts-target-selector="#textarea54"
data-auto-count
>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Count</th>
</tr>
</thead>
<tbody>
<tr data-item-match>
<td data-reg-pattern="\[A>" title="Opening Added Bracket">[A></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<A]" title="Closing Added Bracket"><A]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D></td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="<D]" title="Closing Deleted Bracket"><D]</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
<td data-match-count>.?.</td>
</tr>
<tr data-item-match>
<td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
<td data-match-count>.?.</td>
</tr>
</tbody>
</table>
<textarea
id="textarea54"
cols="16"
rows="12"
>[[]][]<A][D>[A><A][D><D]
... auto-count while editing.</textarea>
</article>