Introduction
IIFE stands for Immediately Invoked Function Expression. It is a design pattern that the program will invoke the functions once they are defined. It is also known as the Self-Executing Anonymous Function design pattern, but it is theoretical. There are some differences we should know when working with this type of stuff beforehand, and 2 of them are declarations and expressions. But for this use case, we will see function differences.
Function Expression and Function Declaration
A function expression can be used as an IIFE (Immediately Invoked Function Expression), which runs as soon as it is defined. Function expressions often do not have a name, and they use the name where they are stored to invoke. For example:
--!strict
local foo: () -> () = function()
-- // ...
end
This function could be known as an anonymous function. As opposed to this, the declaration of a function contains a name and body and stills around on our program, and we can use it after it declares itself. For example:
--!strict
local function foo(): nil
-- // ...
end
Note that you cannot use function expressions before they are defined, formally known as hoisting.
--!strict
foo();
function foo(): nil
-- // ...
end
But in an expression:
--!strict
foo() -- // Tried to access a global foo
local foo: () -> () = function()
-- // ...
end
The difference between Function Expressions and Function Declarations
Function expression and function declaration are different. The main difference between a function expression and a function declaration is the function name, which can be omitted in function expressions to create anonymous functions. (Luau interprets all of this data differently. But don’t pay attention to that yet!). Why do we need to know about this type of stuff when discussing IIFE? When we use this design pattern in our program, we use function expressions, not declarations. You might wonder how.
Well, first, we need to use the ()
to delimit the scope of the function, and it lets us run it since it got declared from there. It is like using coroutine.wrap()()
since it returns a function, but this is not returning one. Anyway, let us see an example:
(function() ... end)()
Just like this, we have already created our first function that will be invoked straight away from its creation! But in a more use-case scenario, we could do this:
(function(message) print(message) end)("Hello, world!")
And this should give us the output, Hello, world!
, which is perfect!
Examples
The examples I found below are from Wikipedia. But I rewrote the examples in Luau.
Evaluation Context
A lack of block scope means that variables defined inside (for example) a for loop will have their definition “hoisted” to the top of the enclosing function. Evaluating a function that depends on variables modified by the outer function (including by iteration) can be difficult. We can see this without a loop if we update a value between defining and invoking the function.
--!strict
local v: number, getValue: () -> number;
v = 1;
getValue = function() return v end;
v = 2;
getValue(); -- // 2
While the result may seem obvious when updating v
manually, it can produce unintended results when getValue()
is defined inside a loop.
Here after the function passes v
as an argument and is invoked immediately, preserving the inner function’s execution context.
--!strict
local v: number, getValue: () -> number;
v = 1;
getValue = (function(x)
return function() return x end
end)(v);
v = 2;
getValue(); -- // 1
Establishing Private Variables and Accessors
IIFEs are also useful for establishing private methods for accessible functions while still exposing some properties for later use. The following example comes from Alman’s post on IIFEs.
--!strict
-- // "counter" is a function that returns an object with properties, which in this case are functions.
local counter: any = (function(): any
local i: number = 0;
return {
get = function()
return i;
end,
set = function(val: number)
i = val;
end,
increment = function()
i += 1;
return i;
end
};
end)();
-- // These calls access the function properties returned by "counter".
counter.get(); -- // 0
counter.set(3); -- // 3
counter.increment(); -- // 4
counter.increment(); -- // 5
Now you know this information, and you will keep it forever in your brain. I hope you guys like it! Happy coding, and always practice this type of stuff on your own!
(This post is inspired by @SiriusLatte)