有没有比使用 IIFE 更好的方法来做到这一点?

Is there a better way to do this than using an IIFE?

我正在尝试为表单创建一个模板,该模板检查 HTML 元素上的 data-regex 自定义参数,然后从中创建一个正则表达式以用于验证输入。这是我目前所拥有的。

var textInputs = document.getElementsByClassName("form__text");
for (var i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    (function() {
      var pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
      textInputs[i]
        .getElementsByTagName("input")[0]
        .addEventListener("input", function(e) {
          checkText(pattern, e);
        });
    })();
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    (function() {
      var patternStr = textInputs[i].getAttribute("data-regex");
      var pattern = patternStr ? new RegExp(patternStr) : null;
      textInputs[i]
        .getElementsByTagName("input")[0]
        .addEventListener("input", function(e) {
          checkText(pattern, e);
        });
    })();  
  }
}

function checkText(pattern, e) {
  if (pattern && e.target.value.search(pattern) == -1) {
    e.target.parentElement.classList.add("form__text--error");
  } else {
    e.target.parentElement.classList.remove("form__text--error");
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>

我必须将两个 addEventListener 块包装在 IIFE 中,因为否则,只要触发回调,模式变量就会被覆盖。我很好奇是否有更清洁的方法来做到这一点。我假设创建正则表达式对象需要一些费用,这就是为什么我试图在回调之外创建它们。提前致谢。

问题是 pattern 将是整个函数的共享变量名。然后当事件侦听器触发时,它将选择 pattern 的最新版本而不是当前版本。这是 a classic problem.

ES6 解决方案

随着 let and const 的引入,您可以在块范围内使用变量,因此解决方案非常简单 - 将任何 var 更改为 letconst

const textInputs = document.getElementsByClassName("form__text");
for (let i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    const pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", function(e) {
        checkText(pattern, e);
      });
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    const patternStr = textInputs[i].getAttribute("data-regex");
    const pattern = patternStr ? new RegExp(patternStr) : null;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", function(e) {
        checkText(pattern, e);
      });
  }
}

function checkText(pattern, e) {
  if (pattern && e.target.value.search(pattern) == -1) {
    e.target.parentElement.classList.add("form__text--error");
  } else {
    e.target.parentElement.classList.remove("form__text--error");
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>

您可以使用 Babel 将您的代码从 ES6+ 转换为 ES5,因此您可以编写上面的代码,但它会自动转换为与新代码工作方式相同的旧代码。

注意: letconst 将被 IE11 接受 然而 ,它们的行为将完全与 var 相同。 IE11 只允许它们使用语法,但作为 var 的别名。它们不会在块范围内。

ES5 解决方案

如果你必须使用 ES5 那么这不是什么大问题。问题是缺少块作用域,只有全局作用域和功能作用域。因此,您需要在功能范围内捕获变量。 IIFE 可以做到这一点,但它看起来有点难看。

咖喱 checkText()

我们可以改为 checkText 一个高阶函数,即 curried - 它不是采用两个参数,而是首先采用一个参数,然后 returns 一个采用第二个参数的函数范围。这看起来像这样:

function checkText(pattern){
  return function(e) {
    if (pattern && e.target.value.search(pattern) == -1) {
      e.target.parentElement.classList.add("form__text--error");
    } else {
      e.target.parentElement.classList.remove("form__text--error");
    }
  }
}

这是一个强大的构造,因为它允许我们在调用 var returnedFunction = checkText(pattern) 时立即捕获变量 - 现在即使变量模式在封闭上下文中发生变化,returnedFunction 仍将保持不变上一个

替换旧调用

作为奖励,这使我们能够消除一些无用的代码。让我们一步步走完整的理解 - 首先这个

.addEventListener("input", function(e) {
  checkText(pattern, e);
});

要变成

.addEventListener("input", function(e) {
  var returnedFunction = checkText(pattern);
  returnedFunction(e);
});

因为我们现在必须将参数传递给两个不同的函数。 此时还是和之前一样的问题。现在这并不能解决任何问题,但我想展示干预步骤。同样的问题 - 当侦听器触发时,then checkText(pattern) 将在此时执行 pattern 已更改。

确保 checkText(pattern) 捕获正确的值

我们需要确保它在 在事件侦听器中初始化 之前触发:

var pattern = patternStr ? new RegExp(patternStr) : null;
var returnedFunction = checkText(pattern);
/* ...code... */
.addEventListener("input", function(e) {
  returnedFunction(e);
});

当我们将它放在与 pattern 变量相同的级别时,即在 .addEventListener 回调之外,它可以在不使用 IIFE 的情况下按预期工作。我们将捕获 current 模式变量,对其进行更改不会影响 returnedFunction.

简化抽象

但是,回调函数现在是这个 function(e) { returnedFunction(e); } - 一个接受参数并调用传递相同参数作为参数的函数的函数。调用 <anonymous function>(e) 与调用 returneFunction(e) 相同。外部包装函数和内部 returneFunction 具有完全相同的签名,并且使用单个参数调用它们的语义几乎相同。因此,包装器现在没用了。我们可以将其移除并执行 lambda 演算称为 Eta 归约以简化抽象,因此整个回调变为

var pattern = patternStr ? new RegExp(patternStr) : null;
var returnedFunction = checkText(pattern);
/* ...code... */
.addEventListener("input", returnedFunction);

现在事件侦听器只是 returnedFunction。当它被触发时,它会传递与之前相同的参数。

删除多余的变量

最后,简化的最后一步是内联 checkText(pattern) 调用。我们在这里真的不需要额外的变量。很容易摆脱它并拥有

.addEventListener("input", checkText(pattern));

完成。这很详细,因为我想展示这个过程。实际上,它只是将 checkText() 函数转换为柯里化变体,然后用它替换回调。希望这些步骤可以将它从一些奇怪的指令转变为理解为什么要这样做。

最后的结果是这样的:

var textInputs = document.getElementsByClassName("form__text");
for (var i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    var pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", checkText(pattern));
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    var patternStr = textInputs[i].getAttribute("data-regex");
    var pattern = patternStr ? new RegExp(patternStr) : null;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", checkText(pattern));
  }
}

function checkText(pattern){
  return function(e) {
    if (pattern && e.target.value.search(pattern) == -1) {
      e.target.parentElement.classList.add("form__text--error");
    } else {
      e.target.parentElement.classList.remove("form__text--error");
    }
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>