javaScript基础之闭包的深入理解

这周要在公司小组内分享一下对闭包的理解。之前自己也是似懂非懂,趁着这次机会也翻看了不少的资料与文章。在这里整理并记录了自己的理解。如果有误欢迎指正~~

一、变量的作用域

ECMScript5没有块级作用域。

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

function add(num1, num2) {
  var sum = num1 + num2;
  return sum;
}
var result = add(10, 20); //30
alert(sum); //由于sum是局部变量,外部访问不到。

使用 var 声明的变量会自动被添加到最接近的环境中。

在函数内部,最接近的环境就是函数的局部环境;

如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。 

例:

function add(num1, num2) {
  sum = num1 + num2; //全局变量
  return sum;
}
var result = add(10, 20); //30
alert(sum); //30

总结:就在于函数内部可以直接读取全局变量,在函数外部自然无法读取函数内的局部变量。

例:

var color = "blue";
function getColor(){
  return color;
}
alert(getColor()); //"blue"

对应的作用域链:

ECMScript6新增let、const、在的代码块内有效。拥有块级作用域。这里不做过的介绍(查看详情请点击此处

二、闭包的概念

官方版闭包是指有权访问另一个函数作用域中的变量的函数 ;

简单易懂(阮一峰老师的解释)闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处

于第三位,……直至作为作用域链终点的全局执行环境。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量 。(摘录红宝书)

例:

function compare(value1, value2){
  if (value1 < value2){
    return -1;
  } else if (value1 > value2){
    return 1;
  } else {
    return 0;
  }
}
var result = compare(5, 10);

三、闭包的作用

主要的作用:

1)就是前面提到的可以读取函数内部的变量;

例:

 function f1(){

    var n=999;

    function f2(){
      alert(n); 
    }

    return f2;

  }

  var result=f1();

  result(); // 999

2)就是让这些变量的值始终保持在内存中;

例:

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。然而闭包的情况有点不同。

f1函数内部定义的函数f2会将包含函数f1的活动对象添加到它的作用域链中。f2函数的作用域链被初始化为包含f1
函数的活动对象和全局变量对象 ,在f1函数执行完毕后,活动对象也不会销毁,因为f2的作用域仍然引用f1的活动对象。换句话说就是,f1的作用域链会被销毁,但是活动对象还存在内存中,直到f2函数被销毁。

即:
result = null;

四、注意事项

4.1.闭包与变量
闭包只能取得包含函数中任何变量的最后一个值;

例:
function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(){
    return i;
  };
}
  return result;
}

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数返回 0,位置 的函数返回 1,以此类推。但实际上,每个函数都返回 10

因为每个函数的作用域链中都 保 存 着 createFunctions() 函 数 的 活 动 对 象 , 所 以 它 们 引 用 的 都 是 同 一 个 变 量 每个函数都引用着保存变量 i 的同一个变量对象,所以在每个函数内部 的值都是 10 。

用我的话理解就是,i相对于匿名函数(闭包)来说是全局变量,全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组result的函数内部的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i

导致运行时输出的是最后一轮的i的值,也就是 10。

解决办法:

function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(num){
    return function(){
      return num;
      };
    }(i);
  }
  return result;
}

4.2.关于this对象

每个函数在被调用时都会自动取得两个特殊变量: this arguments 。this引用的是函数执行的环境对象。

  • 在全局函数中, this 等于 window;
  • 当函数被作为某个对象的方法调用时, this 等于那个对象 ;
  • 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window (内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量 );

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); //"The Window"

如果我们想返回‘My Object’可以这样写:

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
      var that = this; //this指向object
    return function(){
      return that .name;
    };
  }
};
alert(object.getNameFunc()()); //"My Object"    

4.3.内存泄露

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包。



 


 

 









 

 

原文地址:https://www.cnblogs.com/yy136/p/11276879.html