【问题标题】:What causes the this.myFunction is not a function error in this JavaScript class?是什么导致 this.myFunction is not a function error in this JavaScript 类?
【发布时间】:2021-08-01 14:25:02
【问题描述】:

我正在使用普通(普通)JavaScript 以 HTML 表格的形式显示 JSON。

我将从 JSON 动态创建的行附加到表的主体:

class CountriesList {
  constructor() {
    this.apiURL =
      "https://gist.githubusercontent.com/Goles/3196253/raw/9ca4e7e62ea5ad935bb3580dc0a07d9df033b451/CountryCodes.json";
    this.countries = [];
    this.searchBox = document.querySelector("#searchBox");
    this.stringToMatch = "";
    this.tableRows = "";
  }

  // Render rows
  renderRows = (arr, container) => {
    let el = document.querySelector(container);
    el.innerHTML = "";
    el.innerHTML += arr
      .map(function(item) {
        return `<tr>
              <td>${item.name}</td>
              <td class="text-right">${item.code}</td>
              <td class="text-right">
                <button data-code="${item.code}" class="btn btn-sm btn-danger" onclick="this.deleteRow()">
                  <i class="fa fa-times" aria-hidden="true"></i>
                </button>
              </td>
           </tr>`;
      })
      .join("");
  };

  // Get Items
  getFilteredCountries = async() => {
    const response = await fetch(this.apiURL);
    this.countries = await response.json();
    // If there is a search string, filter results
    this.stringToMatch = this.searchBox.value;
    if (this.stringToMatch.length > 0) {
      this.countries = this.countries.filter((country) => {
        return (
          country.name.toLowerCase().includes(this.stringToMatch.toLowerCase()) ||
          country.code.includes(this.stringToMatch.toUpperCase())
        );
      });
      this.renderRows(this.countries, "#countries_table tbody");
    }
  };

  deleteRow() {
    let deleteBtns = document.querySelectorAll("#countries_table tr button");
    console.log(event.target.dataset.code);
    this.countries = this.countries.filter(() => item.code != event.target.dataset.code);
    this.renderRows(this.countries, "#countries_table tbody");
  };

  hideLoader = async() => {
    let loader = document.querySelector(".loader");
    const action = this.countries.length > 0 ? "add" : "remove";
    loader.classList[action]("d-none");
  };

  init = async() => {
    await this.getFilteredCountries();
    await this.hideLoader();
    this.searchBox.addEventListener("keyup", this.getFilteredCountries);
    this.renderRows(this.countries, "#countries_table tbody");
  };
}

const countriesList = new CountriesList();
countriesList.init();
.loader {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  opacity: .85;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 50px;
  left: 50%;
  margin-left: -50px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
<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.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div class="container-fluid">
  <div class="card bg-light shadow-sm my-2">
    <div class="card-header px-3 d-flex">
      <h6 class="text-dark m-0 align-self-center">Countries of the World</h6>
    </div>
    <div class="card-body bg-white position-relative p-0">
      <div class="search mx-2">
        <input class="my-2 form-control" id="searchBox" type="text" placeholder="Search..." value="">
      </div>
      <table class="table mb-0" id="countries_table">
        <thead>
          <tr>
            <th>Country</th>
            <th class="text-right">Code</th>
            <th class="text-right">Delete</th>
          </tr>
        </thead>
        <tbody>
        </tbody>
      </table>
      <div class="loader"></div>
    </div>
  </div>
</div>

问题

在 renderRows 方法的模板文字中,我添加了一个删除按钮

<button data-code="${item.code}" class="btn btn-sm btn-danger" onclick="this.deleteRow()">
   <i class="fa fa-times" aria-hidden="true"></i>
</button>

当我单击删除按钮时,我在浏览器中收到此错误:

Uncaught TypeError: this.deleteRow is not a function 

我做错了什么?

【问题讨论】:

  • this 在 html 属性中不是对象的实例
  • 您不能在 HTML 中直接引用 this 删除 this 关键字,它应该可以正常工作
  • @AkshayBhat 它不在 HTML 中,而是在脚本中,其中每个方法 id 都以 this 调用。
  • @RazvanZamfir 您在创建 HTML 时传递字符串,而不是函数本身,因此单击后,this 等于 window 而不是 CountryList。
  • @FilipKováč 我删除了this 关键字,但它仍然不起作用。 :(

标签: javascript es6-class


【解决方案1】:

第二次尝试。现在一切都应该正常了。我尝试了一些不同的方法,只是因为我想了解为什么某些解决方案没有按我预期的那样工作。

据我所知,这工作得很好,虽然有时一个国家不会被删除,并且会在控制台中输出 null 而不是国家代码,但检查按钮会显示它的所有内容,然后按钮会随机工作并删除国家/地区...您可能需要调查一下。

希望这会有所帮助。我在新代码所在的位置添加了几个 cmets;随时问我有关更改的任何问题(尽管我可能不知道比您多:p)

class CountriesList {
  constructor() {
    this.apiURL =
      "https://gist.githubusercontent.com/Goles/3196253/raw/9ca4e7e62ea5ad935bb3580dc0a07d9df033b451/CountryCodes.json";
    this.countries = [];
    this.searchBox = document.querySelector("#searchBox");
    this.stringToMatch = "";
    this.tableRows = "";
  }

  // A reference to the deleteRow method which we can use inside addEventListener inside renderRows
  boundDeleteRow = (event) => { this.deleteRow(event, this) }

  // Render rows
  renderRows = (arr, container) => {
    // Just added a reference to the CountriesList instance ("that")
    let el = document.querySelector(container),
        that = this;
    el.innerHTML = "";
    
    arr.forEach((item) => {
        let row = `<tr>
              <td>${item.name}</td>
              <td class="text-right">${item.code}</td>
              <td class="text-right">
                <button data-code="${item.code}" class="btn btn-sm btn-danger">
                  <i class="fa fa-times" aria-hidden="true" style="pointer-events:none;"></i>
                </button>
              </td>
           </tr>`;
         el.innerHTML += row;
    });
      
    // add event listener to every row (I used querySelectorAll because sometimes countires show up more than once in your list)
    // added "that" as the second param so I can use it to reference the CountriesList instance
    el.querySelectorAll(`button[data-code]`).forEach((i) => {
      i.addEventListener('click',that.boundDeleteRow);
    },that);
  };

  // Get Items
  getFilteredCountries = async() => {
    const response = await fetch(this.apiURL);
    this.countries = await response.json();
    // If there is a search string, filter results
    this.stringToMatch = this.searchBox.value;
    if (this.stringToMatch.length > 0) {
      this.countries = this.countries.filter((country) => {
        return (
          country.name.toLowerCase().includes(this.stringToMatch.toLowerCase()) ||
          country.code.includes(this.stringToMatch.toUpperCase())
        );
      });
      this.renderRows(this.countries, "#countries_table tbody");
    }
  };

  deleteRow(event, that) {
    let deleteBtns = document.querySelectorAll("#countries_table tr button");
    this.countries = this.countries.filter((item) => item.code != event.target.getAttribute('data-code'));
    this.renderRows(this.countries, "#countries_table tbody");
  };

  hideLoader = async() => {
    let loader = document.querySelector(".loader");
    const action = this.countries.length > 0 ? "add" : "remove";
    loader.classList[action]("d-none");
  };

  init = async() => {
    await this.getFilteredCountries();
    await this.hideLoader();
    this.searchBox.addEventListener("keyup", this.getFilteredCountries);
    this.renderRows(this.countries, "#countries_table tbody");
  };
}

const countriesList = new CountriesList();
countriesList.init();
.loader {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  opacity: .85;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 50px;
  left: 50%;
  margin-left: -50px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
<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.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div class="container-fluid">
  <div class="card bg-light shadow-sm my-2">
    <div class="card-header px-3 d-flex">
      <h6 class="text-dark m-0 align-self-center">Countries of the World</h6>
    </div>
    <div class="card-body bg-white position-relative p-0">
      <div class="search mx-2">
        <input class="my-2 form-control" id="searchBox" type="text" placeholder="Search..." value="">
      </div>
      <table class="table mb-0" id="countries_table">
        <thead>
          <tr>
            <th>Country</th>
            <th class="text-right">Code</th>
            <th class="text-right">Delete</th>
          </tr>
        </thead>
        <tbody>
        </tbody>
      </table>
      <div class="loader"></div>
    </div>
  </div>
</div>

【讨论】:

  • 尝试进行搜索(过滤),然后从搜索结果中删除广告,看看控制台抛出了什么。
  • 现在看起来工作正常。更改样式表对您来说可能更好,但在我看来,问题在于点击事件被用于每个按钮中的十字图标的 标记所吞噬。
  • 如果我过滤,删除,他“unfilter”(删除搜索表达式),被删除的国家又出现了:(
  • 我在这里很困惑。每次调用 getFilteredCountries 时(例如搜索时),您都会从 API 中获取相同的数据,而您的 deleteRow 函数只是从 this.countries 的当前值中删除一个国家/地区,因此当然在您之后又会返回一个已删除的国家/地区搜索/过滤,因为您正在使用 API 中的完整国家/地区列表覆盖 this.countries。您可以将已删除的国家/地区存储为另一个属性并在 getFilteredCountries 中对其进行检查,但这超出了您的问题范围。
【解决方案2】:

核心问题是:在您的情况下,this 关键字引用了 button 元素。一个原始的例子:

&lt;button onclick="console.log(this)"&gt;Click me&lt;/button&gt;

您假设,因为您在类中创建按钮,this 将引用该类。但是,您所做的只是创建一个包含 HTML 的字符串,然后将该字符串设置为 DOM 中某个元素的innerHTML。从本质上讲,您最终得到的结果是我在回答中的 sn-p:只是挂在 DOM 中某处的一个按钮。由 DOM 元素触发的事件将元素本身设置为 this 关键字。

为了实现您真正想要的,您需要在onClick 侦听器中对您的类实例进行某种引用。一种简单的方法是让您的类实例作为变量在全局范围内可访问,然后在该变量上调用该方法。这是一个简化的示例:

    class Renderer {
        render() {
            const target = document.querySelector("#app");
            target.innerHTML = `<button onClick="renderer.doSomething()">hi</button>`
        }
        doSomething() {
            console.log("I am doing something...");
        }
    }
    const renderer = new Renderer();
    renderer.render();
&lt;div id="app"&gt;&lt;/div&gt;

显然,此示例存在缺陷,但它应该向您展示为什么您选择的方法不起作用以及前进的方向是什么。

【讨论】:

    猜你喜欢
    • 2012-06-05
    • 2018-11-13
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多