立即执行函数
Posted: 10.12.2019
背景知识
函数声明 vs. 函数表达式
我们先来看一下函数声明与函数表达式的不同。
function test() {
console.log('函数声明');
}
var test2 = function() {
console.log('函数表达式');
}
匿名函数
那么,接下来的问题是,什么是匿名函数?
function() {
console.log('匿名函数');
}
如果你按照上面那个方式直接声明函数,会报错。为什么?
因为在你声明一个函数后,系统会分配一块内存给这个函数。如果你没有指明函数的名字,那么你怎么获取到这块内存?你根本没有办法获取这块内存,那么就是说会发生内存泄漏。当然,JS 的编译器会阻止这一切发生,因此你在这么做的时候就会报错。
再看上面的函数表达式。这玩意儿先是声明了一个匿名函数,然后赋值给 test2。这样,test2 就指向了这块分配给匿名函数的内存,这样你就有办法到达这块内存,就不会发生内存泄漏。
表达式 vs. 语句
其实就是 expression 和 statement 的区别。
我不太清楚中文的翻译是什么,就自己翻译成了表达式和语句。
- expression:形成了一个值
- statement:干了一件事
这么看可能会有些迷,所以废话不多说,直接看🌰:
// expression
Math.pow(10, 2) + parseInt('123', 10) > 223 ? 'greater' : 'smaller';
// statement
var a = Math.pow(10, 2) + parseInt('123', 10) > 223 ? 'greater' : 'smaller';
console.log(a); // 打印 smaller
看第一行,噼里啪啦地干了一大堆事,最终得到了什么?
最终得到了一个值:一个字符串 'smaller'。
可是这个值用来干嘛了呢?什么都没干。
是的,你只得到了一个值,别的什么都没干。
这就是 expression。每个 expression 都是一个值。
那么要怎么利用这个值呢?这个时候就需要 statement。
statement 干了一件事。在上面的🌰里,statement 就干了赋值这件事。
= 的右边是一个 expression,这个 expression 形成了一个字符串。= 则是把这个字符串赋值给了变量 a。
而且 statement 本身就是一个完整的 statement,无法再与别的 statement 结合在一起。
比如说,你无法这么做:console.log(var a = 123);。
总结
讲到这里,背景知识已经补充得差不多了。
接下来我们就来看一下立即执行函数的定义。
立即执行函数的定义
立即执行函数就是一个立即执行的函数。废话
我们知道,一般的函数被执行,要经历两个步骤:声明函数(statement) -> 调用函数(statement)。
这意味着什么?你在声明函数后,无法在同一时间直接调用,因为声明函数本身就是一个 statement,你无法再同时执行另一个 statement 来调用。
而函数表达式被执行,则是要经历三个步骤:声明匿名函数(expression) -> 赋值变量(statement)--> 变量(expression) -> 调用变量(statement)。
而在函数表达式里,我们调用变量时,变量本身是一个 expression,因此是合法的。
所以你明白立即执行函数的核心是什么了吗?
那就是:将匿名函数转化为函数表达式
立即执行函数的形式
立即执行函数的形式很多,但一般用的话,也就一两种。
在匿名函数前面加!、+、 -甚至是逗号等到都可以起到函数定义后立即执行的效果。
而()、!、+、-、= 等运算符, 都将匿名函数转换成函数表达式。
转化为函数表达式后,这个值就是一个 expression,可以通过()来直接调用
等一下,匿名函数本身不就是 expression 吗?为啥没办法直接调用?
很简单,因为语法不支持,所以我们才将匿名函数转换成函数表达式。
(function(a){
console.log(a);
})(123);
// 最合适的形式,一般用这个
(function(a){
console.log(a);
}(1234));
!function(a){
console.log(a);
}(12345);
+function(a){
console.log(a);
}(123456);
-function(a){
console.log(a);
}(1234567);
/**
* 为什么这个例子是合法的?它难道不是进行了赋值吗?
* 已经执行了一个 statement?为什么还能同时执行另一个?
* 因为 = 这个符号将匿名函数转化成了函数表达式,
* 然后函数表达式被调用,执行,因为没有返回值,所以返回 undefined
* 赋值是最后执行的。这个时候,等号右边不是一个 statement,
* 而是一个 expression(undefined),因此赋值是合法的,编译器不会报错
*/
var fn = function(a){
console.log(a);
}(12345678)
console.log(fn); // 会打印 undefined
// 通过下面的代码,我们可以很轻易地证明上面的猜想
var fn2 = function(a){
console.log(a);
return a;
}(12345678)
console.log(fn2); // 会打印 12345678
为什么要用立即执行函数?
简单来说就五个字:作用域隔离
我们知道 JS 有函数作用域,出函数作用域的时候,本地变量会被销毁。
这个时候,函数作用域外的作用域,是无法获取到函数作用域内的本地变量的。
这就是使用立即执行函数的目的:我们在立即执行函数之外,不想获取立即执行函数之内的变量。
就比如说 jQuery。jQuery 这个库在外面套了一层立即执行函数。这样当你调用 jQuery 的时候,其本地的变量不会对你自己添加的变量形成任何干扰,因为你无法在函数作用域外获取到函数作用域内的变量。
此时若是想访问全局对象,将全局对象以参数形式传进去即可