饿了么|广州蓝景技术分享 — 闭包原理以及使用场景

饿了么|广州蓝景技术分享 — 闭包原理以及使用场景

文章图片


前端面试 , 必问闭包
闭包有多重要?如果你是初入前端的朋友 , 我没有办法直观的告诉你闭包在实际开发中的无处不在 , 但是我可以告诉你 , 前端面试 , 必问闭包 。 面试官们常常用对闭包的了解程度来判定面试者的基础水平 , 保守估计 , 10个前端面试者 , 至少5个都死在闭包上 。
讲解闭包时 , 我们先来看个例子
var n = 999;
function f1() {console.log(n);
f1(); // 999

上面代码中 , 函数f1可以读取全局变量n 。 但是下面例子 , 函数外部无法读取函数内部声明的变量 。
function f1() {var n = 999;
console.log(n);
// Uncaught ReferenceError: n is not defined

上面代码中 , 函数f1内部声明的变量n , 函数外是无法读取的.
如果有时需要得到函数内的局部变量 。 正常情况下 , 这是办不到的 , 只有通过变通方法才能实现 。 那就是在函数的内部 , 再定义一个函数 。
function f1() {var n = 999;
function f2() {
 console.log(n); // 999
上面代码中 , 函数f2就在函数f1内部 , 这时f1内部的所有局部变量 , 对f2都是可见的 。 既然f2可以读取f1的局部变量 , 那么只要把f2作为返回值 , 我们不就可以在f1外部读取它的内部变量了吗!
闭包是什么
闭包是一种特殊的对象 。
它由两部分组成 。 执行上下文(代号A) , 以及在该执行上下文中创建的函数(代号B) 。
当B执行时 , 如果访问了A中变量对象中的值 , 那么闭包就会产生 。
在大多数理解中 , 包括许多著名的书籍 , 文章里都以函数B的名字代指这里生成的闭包 。 而在chrome中 , 则以执行上下文A的函数名代指闭包 。
我们可以对上面代码进行如下修改:
function f1(){
   var a = 999;
   function f2(){
       console.log(a);
   
   return f2; // f1返回了f2的引用
var result = f1(); // result就是f2函数了
result();  // 执行result , 全局作用域下没有a的定义 ,
//但是函数闭包 , 能够把定义函数的时候的作用域一起记住 , 输出999

上面的例子 , 首先有执行上下文f1 , 在f1中定义了函数f2 , 而通过对外返回f2的方式让f2得以执行 。 当f2执行时 , 访问了f1内部的变量a 。 因此这个时候闭包产生
闭包就是函数f1 ,



【饿了么|广州蓝景技术分享 — 闭包原理以及使用场景】在上面的图中 , 红色箭头所指的正是闭包 。 其中Call Stack为当前的函数调用栈 , Scope为当前正在被执行的函数的作用域链 , Local为当前的局部变量 。
JavaScript拥有自动的垃圾回收机制 , 关于垃圾回收机制 , 有一个重要的行为 , 那就是 , 当一个值 , 在内存中失去引用时 , 垃圾回收机制会根据特殊的算法找到它 , 并将其回收 , 释放内存 。
而我们知道 , 函数的执行上下文 , 在执行完毕之后 , 生命周期结束 , 那么该函数的执行上下文就会失去引用 。 其占用的内存空间很快就会被垃圾回收器释放 。 可是闭包的存在 , 会阻止这一过程 。
我们再来看个例子:
var fn = null;function foo() {
   var a = 2;
   function innnerFoo() {
       console.log(a);
   
   fn = innnerFoo; // 将 innnerFoo的引用 , 赋值给全局变量中的fn
function bar() {
   fn(); // 此处的保留的innerFoo的引用foo();bar(); // 2