知识点
- 自由变量和作用域
- 闭包
- 相关题目
自由变量
一个变量在当前作用域没有定义,但被使用了。就向上级作用域,一层一层一次寻找,找到为止。
如果到全局作用域都没有找到,则报错 xx is not defined
作用域
要理解闭包,首先要理解javascript
的变量作用域
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增)
在javascript
中,函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
var i = 1;
function f1() {
console.log(i);
}
f1(); //输出:1
function f1() {
var i = 1;
}
console.log(i); //error: i is not defined
那么,外部要如何读取内部的局部变量呢?
我们知道了函数内部可以读取外部变量。根据这个条件,我们变通一下方法:
function f1() {
var i =1;
function f2() {
console.log(i)
}
return f2;
}
var result = f1();
result(); //输出:1
这里的f2
函数,就是闭包。
为了外部能够读取到f1
函数内的局部变量,我们在f1
函数内定义一个f2
函数,f2
函数可以读取外部f1
函数的局部变量,这样只要把f2
函数返回出去到外部。就可以在外部借助f2
函数来读取f1
函数内的局部变量。
闭包就是能够读取其他函数内部变量的函数。
在javascript
中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包的用途
- 可以读取函数内部的变量。
- 让这些变量始终保持在内存中。
<script>
function f1(){
var i=1;
add=function(){i+=1}
function f2(){
console.log(i);
}
return f2;
}
var result=f1();
result(); //输出:1
add();
result(); //输出: 2
</script>
在如上代码中,result实际上就是闭包f2
函数。它运行了两次,第一次是1,第二次是2。这证明了,函数f1
中的局部变量i一直保存在内存中,并没有在f1
调用后被自动清除。
原因:
f1
是f2
的父函数,而f2
被赋给了一个全局变量,这导致f2
始终存在内存中,而f2
的存在依赖于f1
,因此f1
也始终在内存中,不会再调用结束后,被垃圾回收机制回收。
另外,注意:
add=function(){i+=1}
在add前面没有使用var关键字,因此add是一个全局变量。
另外,add的值是一个匿名函数,这个匿名函数本身也是一个闭包,所以add相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
隐藏数据,如做一个简单的cache工具
// 闭包隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
使用闭包注意点:
- 闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题。
解决办法: 在退出函数之前,将不使用的局部变量全部删除。 - 闭包会在父函数外部,改变父函数内部变量的值。所以,一定要小心,不要随便改变父函数内部变量的值。
闭包优点:
- 实现公有变量。(函数累加器)
- 模块化开发,防止污染全局变量。
- 可以实现封装,实现私有化。
作用域应用的特殊情况,有两种表现:
- 函数作为返回值被返回
- 函数作为参数被传递
// 函数作为返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200
fn() // 100
// 函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn) // 100
所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
不是在执行的地方!!!