Ryf_Closure

94 阅读4分钟

一、作用域

学习闭包之前必须理解JS当中变量的作用域。

变量的作用域无非就是两种:全局变量局部变量

  • 函数内部可以直接读取全局变量
var n = 999;

function f1(){
  // 函数作用域名
  {
    // 块级作用域
    let a = 1;
  }
  console.log(n); // 999
}

f1();
  • 函数外部自然无法读取函数内部的局部变量
function f2(){
  const n = 'xww';
}

console.log(n); // undefined


二、闭包的形成

提到闭包先来聊聊闭包的作用:闭包可以帮助外部读取函数内部的变量,声明不带 var 全局变量。让我们来看下如何让局部变量可以在全局当中访问:闭包是指函数与其词法环境的组合。当一个内部函数被外部函数返回且引用了外部函数作用域的变量时,闭包就形成了。即使外部函数执行结束,内部函数仍能访问其变量,因为这些变量会被保存在内存中。借助闭包可实现数据私有和状态持久化。


三、如何介绍闭包

函数嵌套函数(作用域链的嵌套),f2f1的里面形成了一个闭包,内部函数是需要返回的,且闭包也叫背包,自由变量不会被销毁。这完全得益于底层的垃圾回收机制,如果不做垃圾回收机制的话就会导致内存的泄漏

// 让局部变量可以在全局访问
function f1(){
  // 局部变量
  var n = 999; // 自由变量
  function f2(){
    console.log(n);
  }
  return f2;
}

通常我们将内层函数叫闭包函数,闭包就是将函数内部和函数外部连接起来的桥梁。


四、闭包的用途

读取函数内部的变量 让这些变量的值始终保持在内存中

function f1(){
  var n = 999;
  nAdd = function(){
    n += 1
  }
  function f2(){
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();  // 全局的外部也可以访问
result(); // 1000

可以看到f1当中的函数nAdd没有用var声明,因此函数nAdd可以被外界访问,只要让内部变量可以跟外界沟通,打通了沟通的桥梁这就形成了闭包。因此在上述函数当中有两个闭包函数!!!(闭包可以帮助外部读取函数内部的变量)


      var name = "The Window";
      // let b = 2;
      var object = {
        name: "My Object",
        getNameFunc: function () {
          return this.name;
        },
      };
      console.log(object.getNameFunc()); // MY Object

getNameFunc当中的this是指对象的方法进行调用的。

      var name = "The Window";
      // let b = 2;
      var object = {
        name: "My Object",
        getNameFunc: function () {
          return function () {
            return this.name;
          };
        },
      };
      console.log(object.getNameFunc()()); // The Window

可以看到第二次的输出结果是The Window,是因为object.getNameFunc() 调用返回了一个匿名函数。紧接着 () 调用了这个匿名函数。在非严格模式下,独立函数调用时 this 会绑定到全局对象,在浏览器环境里全局对象就是 window 。(要使其的返回结果是My Object可以看到下面代码)

      var name = "The Window";
      // let b = 2;
      var object = {
        name: "My Object",
        getNameFunc: function () {
          // return this.name;
          var that = this;
          return function () {
            return that.name;
          };
        },
      };
      console.log(object.getNameFunc()());


五、注意事项

我们可以观察看到多次执行闭包函数然后在垃圾回收机制当中看到引用计数

闭包内存消耗大且可能引发内存泄漏,可在退出函数前销毁不用的局部变量。

此外,闭包能在父函数外部改变其内部变量值,存在不确定性,自由变量的生命周期也有影响。



六、总结

  1. 闭包是什么:闭包是一个函数加上到创建函数的作用域的连接,闭包“关闭”了函数的自由变量。即变量a不会被垃圾回收啊!!!因为并不能确定里边的函数,也就是闭包这函数用不用这个a。
function fun(){
  var a = 10
  return function(){
    console.log(a);
  }
}
fun()()
  1. 闭包可以解决什么问题(闭包的优点) 2.1 内部函数可以访问到外部函数的局部变量
var lis = document.getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
    lis[i].onclick = function(){
        alert(i);
    }
}
// 先执行同步函数,最后再执行异步函数,所以一直打印为4

2.2闭包可以解决的问题 如何解决当点击每一个i出现0123的下标情况

var lis = document.getElementsByTagName('li')
for(var i = 0; i < lis.length; i++){
  (function(i){
    lis[i].onclick = function(){
      alert(i);
    }
  })(i)
}
// 通过闭包的方式
  1. 闭包的缺点

3.1 变量会驻留在内存中,造成内存损耗问题。 解决:把闭包的函数设置为null

var lis = document.getElementsByTagName('li')
for(var i = 0; i < lis.length; i++){
  (function(i){
    lis[i].onclick = function(){
      alert(i);
    }
    lis[i] = null;
  })(i)
}

3.2 内存泄漏[ie] ==> 可说可不说,如果说一定要提到ie