开发者问题收集

JavaScript应用程序错误:在另一个内部调用功能,将“最大调用堆栈大小超过超过”错误

2021-01-08
393

我有一个 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

请参阅 这个 小提琴。

我正在寻找一个优雅的、非黑客的解决方案。

问题:

  1. 我的错误在哪里?
  2. 最佳的非黑客修复是什么?
3个回答

您的错误在于,您正在调用一个函数,而该函数又调用另一个函数,而该函数又调用原始函数,这种情况一直持续到出现 超出最大调用堆栈大小 错误为止。

您可以做的是:保存两个过滤器的值并同时应用它们。

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();
mehamasum
2021-01-10

你只是有一个递归不定式循环

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(); 
Daphoque
2021-01-08

最大调用堆栈?你必须有无限循环或类似的东西 - 在你的代码中它非常简单:你使用函数,该函数使用调用它的相同函数😛

在你的情况下 - 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);
});

所以它只会从事件(所以一次)而不是从函数本身(多次)工作

当然是在理论上 ;)

Kamil Rogala
2021-01-08