JavaScript应用程序错误:在另一个内部调用功能,将“最大调用堆栈大小超过超过”错误
我有一个 Bootstrap 4 卡片列表,显示在 12 列表格上。
我制作了一个
按年份过滤
和一个
搜索框
。如下面的示例所示,搜索表达式与每张卡片的标题匹配,所选年份与
data-year
属性的值匹配。
class CardsFilteringComponent {
constructor() {
this.filterableCardsList = document.querySelector('.filterableItems');
this.items = this.filterableCardsList.querySelectorAll('.card-container');
this.searchBox = this.filterableCardsList.querySelector('#searchBoxInput');
this.searchBtn = this.filterableCardsList.querySelector('#searchBoxSubmitBtn');
this.selectBox = this.filterableCardsList.querySelector('#selectYear');
this.isHiddenClass = 'd-none';
}
handleSearchButtonClick(target) {
// call the filter function
//this.handleSelectChange(target);
const expression = this.searchBox.value.toLowerCase();
[...this.items].forEach(item => {
const cardTitle = item.querySelector('.title').textContent;
const showItem = cardTitle.toLowerCase().indexOf(expression) > -1;
const method = showItem ? 'remove' : 'add';
item.classList[method](this.isHiddenClass);
});
}
handleSelectChange(target) {
// call the search function
//this.handleSearchButtonClick(target);
const {value } = target;
[...this.items].forEach(item => {
const year = item.dataset.year;
const showItem = year === value || value === 'ALL';
const method = showItem ? 'remove' : 'add';
item.classList[method](this.isHiddenClass);
});
}
init() {
this.searchBtn.addEventListener('click', () => {
this.handleSearchButtonClick();
});
this.searchBox.addEventListener('keyup', e => {
if (e.keyCode === 13) {
this.handleSearchButtonClick();
}
});
this.selectBox.addEventListener('change', e => {
this.handleSelectChange(e.target);
});
}
}
const cardsFilteringComponent = new CardsFilteringComponent();
cardsFilteringComponent.init();
.cards-grid>[class*='col-'] {
display: flex;
flex-direction: column;
margin-bottom: 25px;
}
.cards-grid .card {
flex-grow: 1;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 2px;
transition: all 0.3s ease-in-out;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
}
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container-fluid">
<div class="filterableItems my-4">
<div class="input-group mb-2">
<input type="text" class="form-control" placeholder="Search" id="searchBoxInput">
<div class="input-group-append">
<button class="btn btn-secondary" id="searchBoxSubmitBtn" type="button">
<i class="fa fa-search"></i>
</button>
</div>
</div>
<div class="form-group mb-2">
<select id="selectYear" class="form-control">
<option value="ALL" selected>All</option>
<option value="2019">2019</option>
<option value="2020">2020</option>
<option value="2021">2021</option>
</select>
</div>
<div class="row cards-grid">
<div class="card-container col-xs-12 col-md-4" data-year="2019">
<div class="card">
<div class="cardImage">
<img src="https://picsum.photos/1200/800/?gravity=north" alt="Lorem ipsum dolor" class="img-fluid">
</div>
<div class="cardContent p-3">
<h5 class="title">Lorem ipsum dolor</h5>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat voluptates modi hic molestias quam doloremque. Accusantium odio blanditiis, amet placeat distinctio, quam magni, nobis perferendis error dicta perspiciatis quod delectus.</p>
</div>
<div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2019</div>
</div>
</div>
<div class="card-container col-xs-12 col-md-4" data-year="2020">
<div class="card">
<div class="cardImage">
<img src="https://picsum.photos/1200/800/?gravity=south" alt="In, quod adipisci" class="img-fluid">
</div>
<div class="cardContent p-3">
<h5 class="title">In, quod adipisci</h5>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quia eaque qui ipsa quod facere autem voluptatem.</p>
</div>
<div class="mt-auto p-3 text-right text-muted small">Published on January 3<sup>rd</sup> 2020</div>
</div>
</div>
<div class="card-container col-xs-12 col-md-4" data-year="2021">
<div class="card">
<div class="cardImage">
<img src="https://picsum.photos/1200/800/?gravity=west" alt="Pariatur, sit, dolor" class="img-fluid">
</div>
<div class="cardContent p-3">
<h5 class="title">Pariatur, sit, dolor</h5>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum eaque repellat cumque pariatur dolores enim quibusdam nemo distinctio, dolore incidunt cupiditate ea excepturi est architecto amet tempore voluptatibus alias doloremque.</p>
</div>
<div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2021</div>
</div>
</div>
</div>
</div>
</div>
问题
除了让这两个过滤器独立工作之外,我还需要让它们协同工作:如果我先进行搜索然后进行过滤,则只有搜索结果应按年份进行过滤。
为此,我
在另一个方法内部调用每个方法
,如注释掉的行
//this.handleSearchButtonClick(target)
和
//this.handleSelectChange(target)
所示。
这会导致错误:
Uncaught RangeError: Maximum call stack size exceeded
请参阅 这个 小提琴。
我正在寻找一个优雅的、非黑客的解决方案。
问题:
- 我的错误在哪里?
- 最佳的非黑客修复是什么?
您的错误在于,您正在调用一个函数,而该函数又调用另一个函数,而该函数又调用原始函数,这种情况一直持续到出现
超出最大调用堆栈大小
错误为止。
您可以做的是:保存两个过滤器的值并同时应用它们。
handleSearchButtonClick(target) {
const expression = this.searchBox.value.toLowerCase();
this.filters.search = expression; // save the expression
this.applyFilter();
}
handleSelectChange(target) {
const {value} = target;
this.filters.year = value; // save the year
this.applyFilter();
}
applyFilter() {
// For each item
// Filter by title first
const cardTitle = ...;
const showItemForSearch = cardTitle.toLowerCase().indexOf(this.filters.search) > -1;
// Filter by year next
const year = ...;
const showItemForYear = year === this.filters.year || this.filters.year === 'ALL';
// make it visible if both condition is met
const showItem = showItemForSearch && showItemForYear;
...
});
}
这是一个更新的小提琴: https://jsfiddle.net/9ra4y0cz/1/
class CardsFilteringComponent {
constructor() {
this.isHiddenClass = 'd-none';
this.filterableCardsList = document.querySelector('.filterableItems');
this.items = this.filterableCardsList.querySelectorAll('.card-container');
// saving the initial or default values of the filters
this.filters = {
search: '',
year: 'ALL'
};
// saving each item into an array
// makes it easier to manipulate the state of each item i.e. hidden or visible
this.state = [];
this.items.forEach(item => {
this.state.push({
node: item,
hidden: false // initially saving all items as visible, will modify this later
});
});
this.searchBox = this.filterableCardsList.querySelector('#searchBoxInput');
this.searchBtn = this.filterableCardsList.querySelector('#searchBoxSubmitBtn');
this.selectBox = this.filterableCardsList.querySelector('#selectYear');
this.render(); // initial rendering, all items are visible
}
render() {
// reading value from the state array and applying classname according to .hidden property
this.state.forEach(item => {
const method = item.hidden ? 'add' : 'remove';
item.node.classList[method](this.isHiddenClass);
});
}
applyFilter() {
this.state.forEach(item => {
// Filter by title first
const cardTitle = item.node.querySelector('.title').textContent;
const showItemForSearch = cardTitle.toLowerCase().indexOf(this.filters.search) > -1;
// Filter by year next
const year = item.node.dataset.year;
const showItemForYear = year === this.filters.year || this.filters.year === 'ALL';
// make it visible if both condition is met
// .hidden is the opposite of that
item.hidden = !(showItemForSearch && showItemForYear);
});
this.render(); // re-render since the state array may have changed
}
handleSearchButtonClick(target) {
const expression = this.searchBox.value.toLowerCase();
this.filters.search = expression; // save the expression
this.applyFilter();
}
handleSelectChange(target) {
const { value } = target;
this.filters.year = value; // save the year
this.applyFilter();
}
init() {
this.searchBtn.addEventListener('click', () => {
this.handleSearchButtonClick();
});
this.searchBox.addEventListener('keyup', e => {
if (e.keyCode === 13) {
this.handleSearchButtonClick();
}
});
this.selectBox.addEventListener('change', e => {
this.handleSelectChange(e.target);
});
}
}
const cardsFilteringComponent = new CardsFilteringComponent();
cardsFilteringComponent.init();
你只是有一个递归不定式循环
function a(){ b();}
function b(){ a();}
a(); // recurisive infinite loop
你可以通过添加布尔条件来防止这种情况
function a(guard){ if(!guard) b(true);}
function b(guard){ if(!guard) a(true);}
a();
最大调用堆栈?你必须有无限循环或类似的东西 - 在你的代码中它非常简单:你使用函数,该函数使用调用它的相同函数😛
在你的情况下 - handleSelectChange 触发 handleSearchButtonClick(在此函数主体的第二行),handleSearchButtonClick 做什么?boom!在其主体的第二行:handleSelectChange
所以你做了类似 A 触发 B,B 触发 A,A 触发 B 等等的事情 - 永动机😉
所以也许是这样的(我没有测试过!只是伪代码)?
handleSearchButtonClick(target, fromOtherHandler = false) {
if (!fromOtherHandler) this.handleSelectChange(target, true);
/*rest of the function body*/
}
handleSelectChange(target, fromOtherHandler = false) {
if (!fromOtherHandler) this.handleSearchButtonClick(target, true);
/*rest of the function body*/
}
你还必须在 init 中更改
this.searchBtn.addEventListener('click', (e) => {
this.handleSearchButtonClick(e.target);
});
所以它只会从事件(所以一次)而不是从函数本身(多次)工作
当然是在理论上 ;)