立即执行函数
我们一起想一下,如果一个js文件里面声明了一个函数 function a(){},如果这个函数一直未被调用或者被调用过但是其他地方还有他的引用,那么它就一直占在它所属环境的内存里(因为预编译时,被提升了)或者引用它的函数的作用域里(闭包,未被释放),等待着被调用执行或者无法释放,除非整个文件全部执行完毕,否则一直在不销毁。那么有没有一种函数针对初始化功能(只被执行一次,只能执行一次)这样的呢?
有!立即执行函数!!!
定义:
立即执行,执行完后立即被销毁的函数 怎样证明执行完后立即被销毁了呢?
(function abc() { console.log(123) }()) //123
console.log(abc) //error:abc is not defined
abc执行完后,再次访问报错,证明已经被销毁找不到了!
与其他函数的共同点和不同点:
共同:可以有参数,可以有返回值,有执行期上下文,其他函数有的所有它都有。
不同:执行完后,立即被销毁。 有参数的例子
(function abc(a, b, c) { console.log(a + b + c) }(1, 2, 3)) //6
有返回值的例子
var num = (function abc(a, b, c) { return a + b + c }(1, 2, 3))
console.log(num) //6
立即执行函数的写法
(function(){}()) //W3C推荐
(function(){})()
关于函数声明和函数表达式的区别
关于函数声明(function (){})和函数表达式(var a = function(){}),虽然都能定义函数,但是是两个东西。只有表达式才能被执行符号执行,函数声明不能被执行,请看例子1和2。
- 函数声明:
***例子1***
function test() {
console.log(1)
} () //error:Unexpected token ')'
- 函数表达式:
***例子2***
var test = function test() {
console.log(1) //1
}()
被执行符号执行的表达式,它会自动放弃自己的函数名称(请对比看例子2和3)
***例子3***
var test = function () {
console.log(1) //1
}()
被执行符号执行的表达式,它会自动放弃自己的函数名称,也就是说被执行符号执行的表达式基本上就成了立即执行函数!你看(例子4):
***例子4***
var test = function () {
console.log(1) //1
}() //被执行函数执行了,这个函数就被永久放弃了
test() //error:test is not a function
但是有些看似是函数声明,但是还能立即执行,请看例子5
***例子5***
+function test() {
console.log(1)//1
}()
test()//error:test is not a function
function test(){}是函数声明,但是它的前面放了一个(+),这个+号趋向于将后面的东西转换为数字类型,所以test函数在+的作用下转成了一个函数表达式。和+有相同效果的还有(- 和 !)
所以也不要完全被function(){}这样的定义给蒙蔽了哦!
test为什么是undefined或者报错呢?
***例子6***
var test = function () {
console.log(1)
}()
console.log(test) //undefined
test()//error:test is not a function
因为 var test 是变量声明,=function(){}是赋值。当()立即执行符号作用下立即执行后,此时会放弃function(){}这个函数在test里面的引用,回到被声明的状态,所以是undefined,所以用执行符访问会报错。
解释为什么被()包裹起来就属于函数表达式了呢?
(function(){})()是立即执行函数,因为有最后面的()执行符去访问,那么(function(){})只是一个被括号包起来的函数声明(function(){}),怎么就变成类似函数表达式了?能执行了呢?
我们可以从数学角度思考,数学中()的作用。(1+2)X2中,()是函数符号,被()包裹就属于函数表达式。
所以这么看是不是能解释的通了呢!
那么有人要提问了,为什么(function(){}())也是立即执行函数呢?
我们可以先识别最外层的括号,最外层括号当成数学运算的小括号,把里面的function(){}()就变成了函数表达式,然后执行这个匿名函数,也解释的通!
特别的(逗号也是运算符)
function test(a, b, c, d) {
console.log(a + b + c + d)
} (1, 2, 3, 4) //不报错,控制台什么也不打印说明没有执行
这个函数不符合只有表达式才能被执行符号执行,函数声明不能被执行这一条,因为这个函数是函数声明那么应该报错,但是这个函数不报错!但是这个函数也没有执行,那是为什么呢?
因为逗号也是运算符!!!
系统执行的时候会尽量去避免出错,把,当成了运算符,这段代码在系统中会被解析成如下这样:
function test(a, b, c, d) {
console.log(a + b + c + d)
}
(1, 2, 3, 4) //不报错,控制台什么也不打印说明没有执行
常见题
描述:调用一个函数,函数里面有一个数组,给里面添加10个项,每一个项都是一个方法,执行方法,打印数组正确下标。
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i)
}
}
return arr
}
var myArr = test()
myArr[0]()
myArr[1]()
myArr[2]()
myArr[3]()
myArr[4]()
myArr[5]()
myArr[6]()
myArr[7]()
myArr[8]()
myArr[9]()
按照思路编写代码如上,但是打印结果却是10个10,不是我们想要的0,1,2,...9,思考一下为什么?
可能很多人都知道是闭包了,但是为什么会打印10个10呢?
我们先想一下,是不是执行myArr[0]()的时候出错了?这只是一个函数的执行,拿的就是它作用域里面的东西,所以错不在这,应该是它作用域里面就不对了。那么看一下他的作用域是怎么来的?
myArr[0]()的作用域在他定义时就有了,执行时建立了自己的OA。执行时他的OA:{}什么也没有,因为没有形参和变量,也没有函数定义。但别忘了他是站在test这个函数的肩膀上定义的,他继承了test的OA,OA(test):{arr:[],i:?}。那么这个i究竟是几呢?我们一起来看执行过程。
第一次循环 -> OA(test):{arr:[function(){console.log(i)}],i:0}
第二次循环 -> OA(test):{arr:[function(){console.log(i)},unction(){console.log(i)}],i:1}
第三次循环 -> OA(test):{arr:[function(){console.log(i)},unction(){console.log(i)},function(){console.log(i)}],i:2}
...
第十次循环 -> OA(test):{arr:[function(){console.log(i)},unction(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)},function(){console.log(i)}],i:9}
i++
这个时候 OA(test)[i] = 10 停止了for循环
当我们执行myArr[0]()时,要输出i,但是他自己的OA:{},什么也没有,沿着作用域链查找,找到OA(test)里面的i,这个时候已经是10了。所以输出10个10.
- 有疑问之处,下面这步i= 0时 arr[i]=arr[0],那么function函数里面的i应该也是0啊?
arr[i] = function () {
console.log(i)
}
这里面的函数function(){},并没有被调用,只有函数执行时候才真正去看内部,才去找i。
- 有疑问之处,var myArr = test(),
test()被调用了,执行后OA(test),不是被销毁了吗?怎么还能找的到?
每一次循环,都定义了新的函数function(){console.log(i)},function(){console.log(i)}这个函数自己的OA:{},但别忘记了他站在OA(test)的肩膀上,OA(test)被引用在他的作用域链里面。所以一共创建了10个新的函数,都分别拿着OA(test)的引用呢!
解决:
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function () {
console.log(j)
}
}(i))
}
return arr
}
var myArr = test()
myArr[0]()
myArr[1]()
myArr[2]()
myArr[3]()
myArr[4]()
myArr[5]()
myArr[6]()
myArr[7]()
myArr[8]()
myArr[9]()
在for循环里面写一个立即执行函数,这个函数把变量i 作为实参传递进去
第一次循环 -> OA(test):{arr:[function (j) {
arr[j] = function () {
console.log(j)
}
}],i:0}
function(){}的OA:{j:0}[因为j是形参]
第二次循环 -> OA(test):{arr:[function (j) {
arr[j] = function () {
console.log(j)
}
},function (j) {
arr[j] = function () {
console.log(j)
}
}],i:1}
function(){}的OA:{j:1}[因为j是形参]
...
所以当myArr0执行时候取的是j,j在自己身上有,那么就正确得到了 0~9
- 有疑问之处,i是怎样传进去的?
i作为立即执行函数的实参传入
- 有疑问之处,arr[j]在立即执行函数的内部,怎么还能给arr生成10个项?
arr[j]是test的变量(不是立即执行函数的,var arr = []是test函数里面定义的,注意作用域),所以改变的是arr!!