理解闭包

闭包:能够访问另一个函数作用域的变量的函数;是函数和声明该函数的词法环境的组合
闭包的形成与函数作用域链有关

function outer() {
  var a = 2;
  var inner = function() {
    console.log(a)
  }
  return inner  // inner就是一个闭包,因为它能够访问outer函数的作用域的变量
}

很多人会搞不懂匿名函数与闭包的关系,实际上,闭包是站在作用域的角度上来定义的,因为inner访问到outer作用域的变量,所以inner就是一个闭包函数。
闭包所需要注意的地方:this指向,变量的作用域,会造成内存泄露

思考:为什么闭包函数能够访问其他函数的作用域?

从堆栈的角度看待js函数
基本变量的值一般都是存在栈内存中,对象类型的变量的值存储在堆内存中,栈内存存储对应空间地址。基本类型的数据:Number、Boolean、Undefined、String、Null

var a = 1;
var b = {m: 20}

对应内存存储
理解闭包
当我们执行 b={m: 30} 时,堆内存就用新对象{m:30},栈内存的b指向新的空间地址,而堆内存中原来的{m: 20}就会把程序引擎垃圾回收掉

var a = 3;
function fn() {
  var b = 4;
  function fn1() {
    console.log(b)
  }
  fn1()
}
fn()

栈:一种先进后出的数据结构

  • 在执行fn前,此时我们在全局执行环境,全局作用域里有变量a,对象fn
  • 进入fn,此时栈内存就会push一个fn的执行环境,这个环境里有变量b和对象fn1,在这里可以访问到自身执行环境和全局执行环境
  • 进入fn1,会push一个fn1的执行环境到执行栈,这里没有定义其他变量,但是这里也同样可以访问到fn的执行环境和全局执行环境,因为当程序在访问变量时,会向底层一个个栈查找,如果在全局执行环境里找不到,则会报undefined错误
  • 随着fn1()执行完毕,fn1的执行环境被销毁,接着执行完fn(),fn的执行环境也会被销毁,只剩下全局执行环境。现在没有变量b和fn1函数对象了,只有a和fn

当程序调用某个函数时,做了以下工作:准备执行环境,初始化函数作用域链和arguments参数对象

现在我们会看最初的例子outer与inner

function outer() {
  var a = '1'
  var inner = function() {
    console.log(a)
  }
  return inner;
}
var inner = outer()
inner()

当程序执行完var inner = outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被被inner的函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;


坑点1:应用的变量可能发生变化

function outer() {
  var result = [];
  for(var i = 0; i < 10; i++) {
    result.[i] = function() {
      console.log(i)
    }
  } 
  return result  // 只有打印的结果全部是10
}
改成如下
function outer() {
  var result = []
  for(var i = 0; i < 10; i++) {
    result.[i] = function(num) {
      return function() {
        console.log(num)
      }
    }(i)
  }
}

坑点2:this指向问题

var object = {
  name: 'object',
  getName: function() {
    return function() {
      console.log(this.name)
    }
  }
}
object.getName()() // undefined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows

坑点3:内存泄露问题

function showId() {
  var el = document.getElementById('id')
  el.onclick = function() {
    console.log(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
  }
}
改成下面
fucntion showId() {
  var el = document.getElementById('id')
  var id = el.id
  el.onclick = function() {
    console.log(id)
  }
  el = null // 主动释放
}

用闭包模仿块级作用域

(function() {
  for(var i = 0; i< 10; i++) {
    console.log(i)
  }
})()
console.log(i) // undefined 因为i随着闭包函数的退出,执行环境销毁,变量回收

有意思的示例

function makeAdd(x) {
  return function(y) {
    return x + y
  }
}
var add5 = makeAdd(5)
var add10 = makeAdd(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12

add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10
var Counter = (function() {
    var privateCounter = 0;
    function changeBy(val) {
        privateCounter += val
    }
    return {
        increment: function() {
            changeBy(1)
        },
         decrement: function() {
            changeBy(-1);
        },
        value: function() {
            return privateCounter;
        }
    }
})()
Counter.increment()
Counter.increment()
console.log(Counter.value())

常见闭包形式

function v() {
    setTimeout(function() {
        // 闭包函数
    }, 0)
    document.getElementById('btn').addEventListener('click', function() {
        // 闭包函数
    })
    $.ajax({
        url: '',
        type: 'post',
        success: function() {
            // 闭包函数
        }
    })
    return {
        n: function() {
            // 闭包函数
        }
        n: function() {
            // 闭包函数
        }
    }
}
v()
上一篇:机器A scp 机器B不需要输入密码的详细步骤


下一篇:全局订阅发布模式