JS 闭包

Posted 你今天学习了吗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS 闭包相关的知识,希望对你有一定的参考价值。

1.先来回顾一下作用域链和执行环境吧,

当某个函数被调用,会创建一个执行环境和相应的作用域链。如:

function compare (value1, value2) {

  if (value1 < value2) {

    return -1;

  } else if (value1 > value2) {

    return1;

  } else {

    return 0;

  }

}

var result = compare(5, 10);

以上代码先定义了compare()函数,然后又在全局作用域中调用了它,当调用compare()时,会创建一个包含arguments,value1,value2的活动对象。全局执行环境的变量对象(包含result,compare)在compare()执行环境的作用域链中则位于第二位,如图:

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部变量的变量对象,则只在函数的执行过程存在。

在创建compare()函数时,会先创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[ [Scope] ] 属性中。

当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[ [Scope] ] 属性中的对象构建起执行环境的作用域链。

此后,又会有一个活动对象(在此作为变量对象使用)被创建并推入执行环境作用域链的前端。

对于上述例子,compare()的执行环境而言,其作用域链包含了两个变量对象:本地活动对象和全局变量对象,显然,作用域链本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

一般来讲,当函数执行完毕时,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),闭包中有所不同

2.闭包

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

创建闭包的常见方式就是在一个函数内部创建另一个函数。

举例:

function createComparisonFunction (propertyName) {

  return function (object1, object2) {

    var value1 = object1[propertyName];

    var value2 = object2[propertyName];

    if (value1 < value2) {

      return -1;

    } else if (value1 > value2) {

      return 1;

    } else {

      return 0;

    }

  }

}

var compare = createComparisonFunction(\'name\');

var result = compare({name: \'alice\'}, {name: \'bob\'});

下列代码执行时:

匿名函数返回到compare中时,其作用域链初始化为包含createComparisonFunction函数的活动对象和全局变量对象。这样,匿名函数就可以访问createComparisonFunction()中定义的所有变量

更为重要的是,createComparisonFunction()函数执行完毕后,其执行环境的作用域链被销毁,但是其活动对象不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象

直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁

例如:

var compareNames = createComparisonFunction(\'names\');    // 创建函数

var result = compareNames( {name: \'alice\' }, { name: \'bob\'} );  // 调用函数

compareNames = null;   // 解除对匿名函数的引用(以便释放内存)

由于闭包会携带包含它的函数的作用域,会占用更多的内存,过度使用闭包会导致内存占用过多,慎用闭包

IE 会造成内存泄漏,这是IE 的一个bug

(1)闭包与变量

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

function createFunctions () {

  var result = new Array();

  for (var i=0; i<10; i++) {

    result[i] = function() {

      return i;

    }

  }

  return result;

}

这个函数会返回一个函数数组,表面上,每个函数都会返回其索引值,但实际上,都返回10

因为每个函数的作用域中都保存着createFunctions()函数的活动对象,所以他们引用的都是同一个变量i

当createFunctions()函数返回后,i为10,此时每个函数都保存着变量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;

}

定义一个匿名函数,并立即执行该匿名函数的结果赋给数组。

 

(2)关于this对象

我们知道,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 呢,不是说闭包可以访问外部函数的变量吗

前面提到过,每个函数在被调用时都会自动取得两个特殊变量,arguments和this

内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能访问直接访问外部函数中的这两个变量

我们可以把外部作用域的this保存在一个闭包能够访问的变量里。

var name = \'The Window\';

var object = {

  name: \'My Object\';

  getNameFunc: function () {

    var that  =  this;   // 保存当前上下文this

    return function () {

      return that.name;  

    };

  }

};

alert(object.getNameFunc()()); // \'My Object\'

在几种情况下,this的值可能会特殊改变,

var name = \'The Window\';

var object = {

  name: \'My Object\',

  getName: function () {

    return this.name;

  }

}

object.getName();   // \'My Object\'

(object.getName) (); // \'MyObject\'

(object.getName = object.getName) (); //  \'My Window\'   相当于赋值,把函数拿到全局中调用

 

(3)内存泄漏

IE的内存泄漏,我们知道IE的垃圾回收机制在IE8之前是引用计数,如下例子

function assignHandler () {

  var element = document.getElementById(\'someElement\');

  element.onclick = function () {

    alert(element.id)

  };

}

由于上述匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会无法减少对element的引用数,只要匿名函数存在,element的引用

数至少为1,因此它占用的内存永远不会回收,所以存在了内存泄漏

改写代码:

function assignHandler() {

  var element = document.getElementById(\'someElement\');

  var id = element.id;

  element.onclick = function () {

    alert(id);

  }

  element = null;

}

通过把element.id的副本保存在变量中,消除了循环引用,但是记住,闭包会引用包含函数的整个活动对象,而其中包含着element。

即使闭包不使用element,包含函数的活动对象中也仍然会保存着一个引用。因此,有必要把element设置为null,解除对dom对象引用

以上是关于JS 闭包的主要内容,如果未能解决你的问题,请参考以下文章

js 闭包 具体作用?

JS闭包的概念

js闭包函数

JS高级---闭包

js闭包

js闭包