JavaScript 中只能调用一次的函数
Posted
技术标签:
【中文标题】JavaScript 中只能调用一次的函数【英文标题】:Function in JavaScript that can be called only once 【发布时间】:2012-09-24 16:19:44 【问题描述】:我需要创建一个只能执行一次的函数,在第一次之后的每次都不会执行。我从 C++ 和 Java 知道可以完成这项工作的静态变量,但我想知道是否有更优雅的方法来做到这一点?
【问题讨论】:
~8 年后,我向我的装饰器库 (github.com/vlio20/utils-decorators) 创建了一个功能请求,以拥有一个可以做到这一点的装饰器 (github.com/vlio20/utils-decorators/issues/77) 【参考方案1】:如果“不会被执行”的意思是“当被多次调用时什么都不做”,你可以创建一个闭包:
var something = (function()
var executed = false;
return function()
if (!executed)
executed = true;
// do something
;
)();
something(); // "do something" happens
something(); // nothing happens
回复@Vladloffe 的评论(现已删除):使用全局变量,其他代码可以重置“已执行”标志的值(无论您选择什么名称)。有了闭包,其他代码就无法做到这一点,无论是有意还是无意。
正如这里的其他答案所指出的,几个库(例如 Underscore 和 Ramda)有一个小实用函数(通常命名为 once()
[*]),它接受一个函数作为一个参数并返回另一个函数,该函数只调用一次提供的函数,而不管返回的函数被调用多少次。返回的函数还会缓存提供的函数首先返回的值,并在后续调用中返回。
但是,如果您不使用这样的第三方库,但仍需要实用功能(而不是我上面提供的 nonce 解决方案),那么实现起来很容易。我见过最好的版本是this one posted by David Walsh:
function once(fn, context)
var result;
return function()
if (fn)
result = fn.apply(context || this, arguments);
fn = null;
return result;
;
我倾向于将fn = null;
更改为fn = context = null;
。一旦调用了fn
,闭包就没有理由保持对context
的引用。
用法:
function something() /* do something */
var one_something = once(something);
one_something(); // "do something" happens
one_something(); // nothing happens
[*]但请注意,其他库,例如 this Drupal extension to jQuery,可能有一个名为 once()
的函数,它的作用完全不同。
【讨论】:
效果很好,能不能解释一下后面的login,var怎么执行=false;作品 @EgyCode - 这在MDN documentation on closures 中有很好的解释。 对不起,我的意思是逻辑,我从来不理解布尔变量以及在这种情况下它是如何工作的 @EgyCode - 在某些上下文中(例如在if
语句测试表达式中),javascript 需要值 true
或 false
之一,并且程序流根据找到的值做出反应表达式被评估。 ==
之类的条件运算符始终计算为布尔值。变量也可以保存true
或false
。 (有关详细信息,请参阅boolean、truthy 和 falsey 上的文档。)
@Vaccano - 好的。现在我明白你在问什么,我很高兴你找到了解决方案。本地存储解决方案是全局变量的一种形式(尽管乍一看可能不像)。这里的许多其他答案都使用类似的东西。我认为没有理由将全局变量方法与闭包结合起来(正如 Reflective 对您问题的回答所暗示的那样。)【参考方案2】:
将其替换为可重用的 NOOP (无操作) 函数。
// this function does nothing
function noop() ;
function foo()
foo = noop; // swap the functions
// do your thing
function bar()
bar = noop; // swap the functions
// do your thing
【讨论】:
@fableal:这怎么不优雅?同样,它非常干净,需要更少的代码,并且不需要为每个应该禁用的函数添加新变量。 "noop" 就是专门为这种情况而设计的。 @fableal:我刚刚看了哈克拉的回答。因此,每次您需要对新函数执行此操作时,都要创建一个新的闭包和变量?你对“优雅”的定义很有趣。 根据asawyer的回复,你只需要做_.once(foo)或者_.once(bar),函数本身不需要知道只运行一次(没有需要 noop 而不需要 * = noop)。 并不是最好的解决方案。如果您将此函数作为回调传递,它仍然可以被多次调用。例如:setInterval(foo, 1000)
- 这已经不再起作用了。您只是覆盖当前范围内的引用。
可重用 invalidate
函数,可与 setInterval
等一起使用,.: jsbin.com/vicipar/1/edit?js,console【参考方案3】:
一旦被调用就指向一个 empty 函数:
function myFunc()
myFunc = function(); // kill it as soon as it was called
console.log('call once and never again!'); // your stuff here
;
<button onClick=myFunc()>Call myFunc()</button>
或者,像这样:
var myFunc = function func()
if( myFunc.fired ) return;
myFunc.fired = true;
console.log('called once and never again!'); // your stuff here
;
// even if referenced & "renamed"
((refToMyfunc)=>
setInterval(refToMyfunc, 1000);
)(myFunc)
【讨论】:
这个解决方案更符合 Javascript 等高度动态语言的精神。为什么要设置信号量,一旦使用了函数就可以简单地清空它? 非常好的解决方案!该解决方案的性能也优于闭包方法。唯一的小“缺点”是如果名称更改,您需要保持函数名称同步。 这样的问题是,如果在某处有另一个对该函数的引用(例如,它作为参数传递并隐藏在某个地方的另一个变量中 - 如在对setInterval()
的调用中),那么该引用调用时会重复原来的功能。
@TedHopp - here's 对这些情况进行特殊处理
是的,这与此线程上的 Bunyk's answer 完全相同。它也类似于闭包(如my answer),但使用属性而不是闭包变量。这两种情况都与您在此答案中的方法完全不同。【参考方案4】:
UnderscoreJs 有一个功能可以做到这一点,underscorejs.org/#once
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func)
var ran = false, memo;
return function()
if (ran) return memo;
ran = true;
memo = func.apply(this, arguments);
func = null;
return memo;
;
;
【讨论】:
让once
接受参数对我来说似乎很有趣。您可以使用squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));
并获得1
来调用squareo
。我理解对了吗?
@aschmied 你是对的 - 第一次调用的参数集的结果将被记忆并返回给所有其他调用,无论参数如何,因为基础函数永远不会再次调用。在这种情况下,我不建议使用_.once
方法。见jsfiddle.net/631tgc5f/1
@aschmied 或者我想每个参数集单独调用一次。我不认为这真的是为了那种用途。
如果你已经在使用_
,这会很方便;我不建议依赖整个库来编写这么少的代码。【参考方案5】:
说到静态变量,这有点像闭包变体:
var once = function()
if(once.done) return;
console.log('Doing this once!');
once.done = true;
;
once(); once();
如果你愿意,你可以重置一个函数:
once.done = false;
【讨论】:
【参考方案6】:您可以简单地使用“删除自身”功能
function Once()
console.log("run");
Once = undefined;
Once(); // run
Once(); // Uncaught TypeError: undefined is not a function
但是,如果您不想吞下错误,这可能不是最佳答案。
你也可以这样做:
function Once()
console.log("run");
Once = function();
Once(); // run
Once(); // nothing happens
我需要它像智能指针一样工作,如果没有 A 类型的元素可以执行,如果有一个或多个 A 元素,则无法执行函数。
function Conditional()
if (!<no elements from type A>) return;
// do stuff
【讨论】:
我需要它像智能指针一样工作,如果没有 A 类型的元素可以执行,如果有一个或多个 A 元素,则无法执行函数。 @VladIoffe 你问的不是这个。 如果Once
作为回调传递(例如,setInterval(Once, 100)
),这将不起作用。原来的函数会继续被调用。【参考方案7】:
var quit = false;
function something()
if(quit)
return;
quit = true;
... other code....
【讨论】:
【参考方案8】:试试这个
var fun = (function()
var called = false;
return function()
if (!called)
console.log("I called");
called = true;
)()
【讨论】:
【参考方案9】:来自一个名叫 Crockford 的家伙... :)
function once(func)
return function ()
var f = func;
func = null;
return f.apply(
this,
arguments
);
;
【讨论】:
如果您认为TypeError: Cannot read property 'apply' of null
很棒,那就太好了。这是您第二次调用返回的函数时得到的结果。【参考方案10】:
与setInterval
一起使用的可重用invalidate
函数:
var myFunc = function ()
if (invalidate(arguments)) return;
console.log('called once and never again!'); // your stuff here
;
const invalidate = function(a)
var fired = a.callee.fired;
a.callee.fired = true;
return fired;
setInterval(myFunc, 1000);
在 JSBin 上试用:https://jsbin.com/vicipar/edit?js,console
answer from Bunyk的变体
【讨论】:
【参考方案11】:简单的装饰器,在您需要时易于编写
function one(func)
return function ()
func && func.apply(this, arguments);
func = null;
使用:
var initializer= one( _ =>
console.log('initializing')
)
initializer() // 'initializing'
initializer() // nop
initializer() // nop
【讨论】:
【参考方案12】:这是一个示例 JSFiddle - http://jsfiddle.net/6yL6t/
还有代码:
function hashCode(str)
var hash = 0, i, chr, len;
if (str.length == 0) return hash;
for (i = 0, len = str.length; i < len; i++)
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
return hash;
var onceHashes = ;
function once(func)
var unique = hashCode(func.toString().match(/function[^]+\([\s\S]*)\$/)[1]);
if (!onceHashes[unique])
onceHashes[unique] = true;
func();
你可以这样做:
for (var i=0; i<10; i++)
once(function()
alert(i);
);
它只会运行一次:)
【讨论】:
【参考方案13】:初始设置:
var once = function( once_fn )
var ret, is_called;
// return new function which is our control function
// to make sure once_fn is only called once:
return function(arg1, arg2, arg3)
if ( is_called ) return ret;
is_called = true;
// return the result from once_fn and store to so we can return it multiply times:
// you might wanna look at Function.prototype.apply:
ret = once_fn(arg1, arg2, arg3);
return ret;
;
【讨论】:
【参考方案14】:如果您使用 Node.js 或使用 browserify 编写 JavaScript,请考虑 "once" npm module:
var once = require('once')
function load (file, cb)
cb = once(cb)
loader.load('file')
loader.once('load', cb)
loader.once('error', cb)
【讨论】:
【参考方案15】:如果您希望将来能够重用该功能,那么根据上面 ed Hopp 的代码,这可以很好地工作(我意识到最初的问题并没有要求这个额外的功能!):
var something = (function()
var executed = false;
return function(value)
// if an argument is not present then
if(arguments.length == 0)
if (!executed)
executed = true;
//Do stuff here only once unless reset
console.log("Hello World!");
else return;
else
// otherwise allow the function to fire again
executed = value;
return;
)();
something();//Hello World!
something();
something();
console.log("Reset"); //Reset
something(false);
something();//Hello World!
something();
something();
输出如下:
Hello World!
Reset
Hello World!
【讨论】:
【参考方案16】:只开一次灯的简单示例。
function turnOnLightOnce()
let lightOn = false;
return function ()
if (!lightOn)
console.log("Light is not on...Turning it on for first and last time");
lightOn = true;
;
const lightOn = turnOnLightOnce();
lightOn() // Light is not on...Turning it on for first and last time
lightOn()
lightOn()
lightOn()
lightOn()
https://codesandbox.io/s/javascript-forked-ojo0i?file=/index.js
这是由于 JavaScript 中的闭包造成的。
【讨论】:
【参考方案17】:尝试使用下划线“一次”功能:
var initialize = _.once(createApplication);
initialize();
initialize();
// Application is only created once.
http://underscorejs.org/#once
【讨论】:
不,当你开始用参数调用它时它太丑了。【参考方案18】:var init = function()
console.log("logges only once");
init = false;
;
if(init) init();
/* next time executing init() will cause error because now init is
-equal to false, thus typing init will return false; */
【讨论】:
【参考方案19】:if (!window.doesThisOnce)
function myFunction()
// do something
window.doesThisOnce = true;
;
;
【讨论】:
污染全局范围(又名窗口)是一种不好的做法 我同意你的观点,但有人可能会从中有所收获。 这不起作用。首次执行该代码时,将创建该函数。然后当函数被调用时,它会被执行并且全局设置为false,但是下次仍然可以调用该函数。 它在任何地方都没有设置为false。【参考方案20】:如果你使用 Ramda,你可以使用函数"once"。
引用自文档:
一次函数 (a… → b) → (a… → b) 参数 在 v0.1.0 中添加
接受一个函数 fn 并返回一个保护 fn 调用的函数,这样 fn 只能被调用一次,无论返回的函数被调用多少次。计算的第一个值在后续调用中返回。
var addOneOnce = R.once(x => x + 1);
addOneOnce(10); //=> 11
addOneOnce(addOneOnce(50)); //=> 11
【讨论】:
【参考方案21】:尽量简单
function sree()
console.log('hey');
window.sree = _=>;
你可以看到结果
【讨论】:
如果您在模块内,请使用this
而不是window
【参考方案22】:
JQuery 只允许使用one() 方法调用函数一次:
let func = function()
console.log('Calling just once!');
let elem = $('#example');
elem.one('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<p>Function that can be called only once</p>
<button id="example" >JQuery one()</button>
</div>
使用JQuery方法实现on():
let func = function(e)
console.log('Calling just once!');
$(e.target).off(e.type, func)
let elem = $('#example');
elem.on('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<p>Function that can be called only once</p>
<button id="example" >JQuery on()</button>
</div>
使用原生 JS 实现:
let func = function(e)
console.log('Calling just once!');
e.target.removeEventListener(e.type, func);
let elem = document.getElementById('example');
elem.addEventListener('click', func);
<div>
<p>Functions that can be called only once</p>
<button id="example" >ECMAScript addEventListener</button>
</div>
【讨论】:
【参考方案23】:为了好玩而把我的帽子扔在戒指中,增加了记忆的优势
const callOnce = (fn, i=0, memo) => () => i++ ? memo : (memo = fn());
// usage
const myExpensiveFunction = () => return console.log('joe'),5;
const memoed = callOnce(myExpensiveFunction);
memoed(); //logs "joe", returns 5
memoed(); // returns 5
memoed(); // returns 5
...
【讨论】:
【参考方案24】:这个对于防止无限循环很有用(使用jQuery):
<script>
var doIt = true;
if(doIt)
// do stuff
$('body').html(String($('body').html()).replace("var doIt = true;",
"var doIt = false;"));
</script>
如果您担心命名空间污染,请用一个长而随机的字符串替换“doIt”。
【讨论】:
【参考方案25】:有助于防止粘滞执行
var done = false;
function doItOnce(func)
if(!done)
done = true;
func()
setTimeout(function()
done = false;
,1000)
【讨论】:
循环运行了 206 次,我用我的 CPU 速度计算,谢谢 loool以上是关于JavaScript 中只能调用一次的函数的主要内容,如果未能解决你的问题,请参考以下文章