一、立即执行函数的出现
我们都知道,在ES 6出现之前,前端开发人员在JavaScript中只能在函数中才能使用局部变量:
var a = 1 //全局变量
function createB(){
var b = 2 //局部变量
console.log(b)
}
createB() // 2
console.log(a) // 1
console.log(b) // b is not defined
在上面的代码中 createB()
这个函数在声明之后立即调用,在控制台打印出了变量b的值。
在C语言或C++中,我们可以使用 {}
将代码包裹起来实现局部变量,但是JavaScript中有变量提升这个东西,所以在JS中这种方法并不可行。
但是这种方法还是使用到了全局变量,在哪里呢?函数 createB()
,虽然变量B在函数内是一个局部变量,但是函数本身还在全局作用域中。所以我们在调用函数 createB
时还是使用到了全局变量。
怎么办呢,这里我们将函数 createB()
改写成一个匿名函数,并且立即调用它:
function(){
var b = 2
console.log(b)
}.call()
这种方法虽然解决了局部变量的问题,但是在浏览器中执行的时候会报错:
function(){
var b = 2
console.log(b)
}.call() //Uncaught SyntaxError: Unexpected token (
于是广大程序员试出来一些方法使得浏览器不报语法错误:
//第一种,在函数前加取反符号 !
!function(){
var b = 2
console.log(b)
}.call()
//第二种,在函数前加 +/- 号
-function(){
var b = 2
console.log(b)
}.call()
(function(){
var b = 2
console.log(b)
}).call()
//第三种,用()将函数体包裹起来
(function(){
var b = 2
console.log(b)
}).call()
// 还有其他方法不再一一介绍
虽然第一、第二种方法会改变函数的返回值,但是由于我们不在乎函数的返回值,因此并不会有影响。
二、总结立即执行函数
- 我们程序员不想用全局变量
- 我们想用局部变量
- ES 6之前,只有函数有局部变量
- 于是我们声明一个 function xxx(){},然后调用这个函数,xxx.call()
- 但是函数xxx也是全局变量(全局函数),因此我们不能给函数名字
- 就有了匿名函数 function(){}.call()
- 但是这种写法在 Chrome 中会报语法错误
- 程序员试出来一些方法使得浏览器不报错
三、闭包的出现
在上文中我们知道了立即执行函数的出现以及作用就是实现局部变量,那么现在就出现了新的问题,假设我现在有两个 .js
文件,这两个文件想要相互访问要如何实现?
// module1.js
!function(){
var person = {
"name": "wky",
"age": 18
}
window.person = person
}.call()
// module2.js
!function(){
var person = wondow.person
console.log(person)
}.call()
我们将变量person添加到window对象上,这样其他的函数内部也可以访问到变量person,但是为了避免其他函数直接操作变量person,我们修改一下这个函数:
// module1.js
!function(){
var person = {
"name": "wky",
"age": 18
}
window.personGrowUp = function(){
person.age += 1
return person.age
}
}.call()
// module2.js
!function(){
var newAge = window.personGrowUp
console.log(newAge) // 19
}.call()
这里module1.js函数中只暴露了一个函数 personGrowUp()
,在module2.js中只能使用这个使年龄加一的函数,而不能直接使用变量person,这样就起到了一个隐藏变量的功能,在module1.js中就生成了一个闭包,因为函数 personGrowUp()
使用了函数之外的一个变量person。
四、总结闭包的使用
- 立即执行函数使得变量 person 无法被外部访问
- 闭包使得匿名函数可以操作 person
- window.personGrowUp 保存了匿名函数的地址
- 任何地方都可以使用 window.personGrowUp 操作 person ,但是不能直接访问 person
五、改写函数
我们对上面的函数进行一些简单的改写就可以得到一个标准的闭包:
var fn = function(){
var person = {
"name": "wky",
"age": 18
}
return function(){
person.age += 1
return person.age
}
}
var personGrowUp = fn()
personGrowUp()
这里函数fn在声明之后立即调用,这也是一个立即执行函数,函数fn返回一个匿名函数,因此我们可以再次调用 personGrowUp
函数。