简体   繁体   English

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

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

I am trying to create a template for forms that checks for a data-regex custom param on an HTML element and then creates a regular expression from it to be used to validate the input.我正在尝试为 forms 创建一个模板,该模板检查 HTML 元素上的data-regex自定义参数,然后从中创建一个正则表达式以用于验证输入。 Here is what I have so far.这是我到目前为止所拥有的。

 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>

I had to wrap the two addEventListener blocks inside IIFE's because if not, the pattern variable would be overwritten whenever the callback was triggered.我不得不将两个addEventListener块包装在 IIFE 中,因为如果没有,只要触发回调,模式变量就会被覆盖。 I'm curious if there's a cleaner way to do this.我很好奇是否有更清洁的方法来做到这一点。 I'm assuming that there is some expense to creating the regex objects, which is why I was trying to create them once outside of the callbacks.我假设创建正则表达式对象需要一些费用,这就是为什么我试图在回调之外创建它们。 Thanks in advance.提前致谢。

The problem is that pattern will be a shared variable name for the entire function.问题是pattern将是整个 function 的共享变量名。 Then later when the event listener fires, it will pick up whatever is the last version of pattern rather than the current one.然后稍后当事件侦听器触发时,它将拾取pattern的最新版本而不是当前版本。 It's a classic problem .这是一个经典的问题

ES6 solution ES6 解决方案

With the introduction of let and const you can have variables in block scope, so the solution is dead simple - change any var to either let or const .随着letconst的引入,您可以在块 scope 中拥有变量,因此解决方案非常简单 - 将任何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>

You can use Babel to transpile your code from ES6+ to ES5, so you'd write the above but it will be automatically tranfformed to older code that works the same as the new one.您可以使用 Babel 将代码从 ES6+ 转换为 ES5,因此您可以编写上述代码,但它会自动转换为与新代码相同的旧代码。

NOTE: let and const will be accepted by IE11 however , they will behave exactly the same as var .注意: letconst将被 IE11 接受,但是它们的行为与var完全相同。 IE11 only makes them allowed syntax but as aliases to var . IE11 仅使它们成为允许的语法,但作为var的别名。 They wouldn't be block scoped.它们不会是块范围的。

ES5 solution ES5 解决方案

If you have to use ES5 then it's not that big of a problem.如果你必须使用 ES5,那么这不是什么大问题。 The trouble is lack of block scopes, there are only global and functional scopes.问题是缺少块作用域,只有全局作用域和函数作用域。 So, you'd need to capture the variable inside a functional scope.因此,您需要在功能 scope 中捕获变量。 An IIFE does that but it looks a bit ugly. IIFE 可以做到这一点,但它看起来有点难看。

Curry checkText()咖喱checkText()

We can instead make checkText a higher order function that is curried - instead of taking two parameters, it takes one parameter first, then returns a function that takes a second parameter.我们可以改为使checkText成为一个高阶function ,它是柯里化的 - 它不是采用两个参数,而是首先采用一个参数,然后返回一个采用第二个参数的 function。 This looks like this:这看起来像这样:

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");
    }
  }
}

It is a powerful construct here, since it allows us to immediately capture a variable if we call var returnedFunction = checkText(pattern) - now even if the variable pattern changes in the enclosing context, returnedFunction will still hold the previous one.它在这里是一个强大的构造,因为它允许我们在调用var returnedFunction = checkText(pattern)时立即捕获变量 - 现在即使变量模式在封闭上下文中发生变化, returnedFunction仍将保留前一个。

Replace the old call替换旧调用

As a bonus, this allows us to eliminate some useless code.作为奖励,这使我们能够消除一些无用的代码。 Let's walk step by step for complete understanding - first this让我们一步一步来完全理解 - 首先这个

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

has to turn into必须变成

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

because we have to pass the parameters into two different functions now.因为我们现在必须将参数传递给两个不同的函数。 There is still the same problem as before at this point .此时仍然存在与之前相同的问题 Right now this doesn't solve anything but I want to show the intervening step.现在这并不能解决任何问题,但我想展示干预步骤。 Same issue - when the listener fires, then checkText(pattern) will be executed at which point pattern is already changed.同样的问题 - 当侦听器触发时, checkText checkText(pattern)将在该点pattern已更改时执行。

Make sure checkText(pattern) captures the correct value确保checkText(pattern)捕获正确的值

We need to make sure it fires before being initialised in the event listener:我们需要确保它在事件侦听器中初始化之前触发:

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

When we place it at the same level as the pattern variable, so just outside the .addEventListener callback, it works as intended without using an IIFE.当我们将它放在与pattern变量相同的级别时,就在.addEventListener回调之外,它可以按预期工作而无需使用 IIFE。 We would capture the current pattern variable and changes to it will not affect returnedFunction .我们将捕获当前模式变量,对其进行更改不会影响returnedFunction

Simplifying the abstraction简化抽象

However, the callback function now is this function(e) { returnedFunction(e); }然而,回调 function 现在是 this function(e) { returnedFunction(e); } function(e) { returnedFunction(e); } - a function that takes a parameter and calls a function that passing the same parameter as an argument. - 一个function(e) { returnedFunction(e); }接受一个参数并调用一个 function 传递相同的参数作为参数。 Calling <anonymous function>(e) is the same as calling returneFunction(e) .调用<anonymous function>(e)与调用returneFunction(e)相同 The outer wrapper function and the inner returneFunction have the exact same signature and the semantics of calling them with a single argument are virtually the same.外部包装器 function 和内部returneFunction具有完全相同的签名,并且使用单个参数调用它们的语义几乎相同。 Thus, the wrapper is useless now.因此,包装器现在没用了。 We can remove it and perform what lambda calculus calls Eta reduction to simplify the abstraction, thus the whole callback becomes我们可以删除它并执行 lambda 演算调用 Eta 归约来简化抽象,因此整个回调变成

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

Now the event listener is just returnedFunction .现在事件监听器只是returnedFunction函数。 When it is fired, it will be passed the same argument as before.当它被触发时,它将传递与以前相同的参数。

Remove the extra variable删除额外的变量

Finally, the last step in the simplification is to just inline the checkText(pattern) call.最后,简化的最后一步是内联checkText(pattern)调用。 We don't really need the extra variable here.我们在这里并不需要额外的变量。 It's easy to just get rid of it and have摆脱它并拥有它很容易

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

Done.完毕。 This was detailed because I wanted to show the process.这很详细,因为我想展示这个过程。 In practice, it's just converting the checkText() function to a curried variant and then replacing the callback with it.实际上,它只是将checkText() function 转换为咖喱变体,然后用它替换回调。 Hopefully, the steps turn it from some weird instructions that happen to work into understanding why that's being done.希望这些步骤将它从一些奇怪的指令转变为理解为什么要这样做。

The final result is this:最终结果是这样的:

 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM