本文是我在学习闭包时,查阅相关资料,根据自己的理解得出的结论,如有不对,恳请指正。
定义
「函数」与「函数内部能访问到的变量」的总和,就是一个闭包。
或许可以再根据闭包的形成条件简化为:
「子函数」与「子函数能够访问到的父函数的变量」的总和就是闭包。
也有人说闭包是函数,是 「能够访问父函数的变量的」 子函数,我认为不对不错,但不严谨。
闭包是 「子函数」 + 「变量」。
作用
- 实现私有成员(变量或方法)
- 在内存中维持变量。普通函数执行后销毁内部作用域
- 避免全局污染
闭包形成条件
闭包形成条件:函数嵌套函数,且子函数引用父函数的变量。
闭包经典使用场景
1. return 子函数
let n = "global n"
function father() {
let n = "father n"
function son() {
console.log(n)
debugger
}
return son
}
let foo = father()
foo() // father n
foo 变量存放指向父函数返回值(即子函数)的地址,再调用该地址上的函数。
2. 在父函数中调用子函数(可改写为立即执行函数)
let n = "global n"
function father(){
let n = "father n"
function son(){
console.log(n)
debugger
}
son()
}
father() // father n
内部函数 son 与 外部函数 father 的变量 n 形成了闭包。
可改写为立即执行函数:
let n = "global n";
(function father(){
let n = "father n"
function son(){
console.log(n)
}
son()
})() // father n
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
提一嘴 立即执行函数:
IIFE(Immediately Invoked Function Expression)
定义:声明一个匿名函数,并立即调用这个函数。
作用:创建一个独立作用域,使得外部访问不了内部变量,同时避免全局污染。
ES5 语法:
( function(){} )()
( function(){}() )
ES6 语法:
( ()=>{} )()
( ()=>{}() ) // 语法错误
3. 函数作为参数
let n = 'global n'
function father() {
let n = 'father n'
function son() {
console.log(n)
}
return son
}
function foo(func) {
let n = 'foo n'
func()
}
foo(father()) // father n
4. 循环赋值
for(var i = 0; i < 6; i++){
(function(j){
setTimeout(function(){
console.log(j)
}, 0)
})(i)
} // 6 6 6 6 6 6
5. 使用回调函数就是在使用闭包
function father() {
let n = "father n"
setTimeout(() => {
console.log(n)
debugger
}, 0)
}
father() // father n
father 函数包裹 setTimeout 函数,setTimeout 函数引用了其父函数的变量 n,形成了闭包。
6. 包裹多个子函数
function father() {
let n = "father n"
function son1() {
console.log(n) // son1 引用父函数的变量 n
}
function son2() {
// console.log(n) // son2 不引用父函数的变量 n
debugger
}
son2() // 不调用 son1
}
father()
因为闭包作用域是内部所有子函数共享的,只要有一个子函数使用到了父函数中的变量即可形成闭包。
闭包与类
闭包与类是对同一事物的不同表达。
function person(name) {
this.name = name
this.age = "22"
function sayHi(){
console.log(`Hello, I'm ${name}`)
debugger
return [name, age]
}
return sayHi
}
let p1 = person("Tom")
console.log(p1())
// Hello, I'm Tom
// ["Tom","22"]
// 使用 ES6 class 语法
class Person {
constructor(name) {
this.name = name
// this.sayHi = this.sayHi.bind(this);
}
age = "22"
sayHi() {
console.log(`Hello, I'm ${this.name}`)
}
}
let p1 = new Person("Tom")
p1.sayHi()
console.log(p1)
// Hello, I'm Tom
// {"age":"22","name":"Tom"}
内存泄露
闭包不会造成内存泄漏,老浏览器(主要是 IE6)由于垃圾回收的 bug,容易出现内存泄漏。
滥用闭包才会造成内存泄漏,因为闭包包含其父函数的作用域,比普通函数占有更多的内存空间。