理解函数作用域与闭包

前言

但凡读书,或者学一门技术,都要问自己以下几个问题。

  • 它是什么?
  • 它有什么用?/发明它是为了解决什么问题?
  • 它有什么弊端?

我下面就试着从这几个方向来阐述闭包这个概念。

概念

在了解闭包之前,我们需要了解几个概念。本文在这里只做简单介绍,如需要进一步了解,请参考文章末尾的链接。

作用域

变量和函数的可作用范围,分为局部作用域和全局作用域。Javascript不具有块级作用域,而具有函数作用域。

执行环境(execuation context)

变量和函数有权访问的其他数据。

执行环境栈(execuation context stack)

每个函数在执行的时候,会把它的执行环境推入一个栈中,在函数执行完毕后执行环境出栈并被销毁。保存在其中的所有函数和比变量定义随之销毁,控制权返回到之前的执行环境中。全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。

作用域链(scope chain)

作用域链用于保证对执行环境有权访问的变量和函数的有序访问。

什么是闭包?

闭包这个概念,在函数式编程里很常见,简单的说,就是使内部函数可以访问定义在外部函数中的变量。严格一点的定义是

在函数内声明另一个函数,并且返回这个函数。这个返回的函数和它的执行环境整体叫做闭包。

让我们来看一个例子:

function f1(){
  var val = 10;
}
console.log(val);    //Uncaught ReferenceError: val is not defined(…)

由于从函数外部无法访问函数内部的变量,所以报出了错误。那么如何能够访问到局部作用域的变量呢?

function f1(){
  var val = 10;
  function f2(){
    console.log(val);
  }
  return closure;
}

var f2 = f1();
f2();           // 10

在这段代码中,f2 函数和其执行环境构成了一整个闭包。对于常规的 f1() 方法, 在其内部的变量 val 应该在 f1() 方法执行完毕以后就被垃圾回收。但是 f1() 返回了一个新的方法 f2()。由于 f2() 访问了其外部函数的变量 val,val就构成了f2函数的执行环境。val 存在于f2的作用域链中,只要f2()方法没有被销毁,其作用域链中的变量和函数就不会被销毁, val 也就会一直存在。

闭包有什么用?

for循环变量无法保持的问题

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 5);
}

上面这个代码块会打印五个 5 出来,而我们预想的结果是打印 1 2 3 4 5。

之所以会这样,是因为 setTimeout 中的 i 是对外层 i 的引用。当 setTimeout 的代码被解释的时候,运行时只是记录了 i 的引用,而不是值。而当 setTimeout 被触发时,五个 setTimeout 中的 i 同时被取值,由于它们都指向了外层的同一个 i,而那个 i 的值在迭代完成时为 5,所以打印了五次 5

为了得到我们预想的结果,我们可以把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。

for (var i = 0; i < 5; i++) {
  (function (idx) {
    setTimeout(function () {
      console.log(idx);
    }, 5);
  })(i);
}

制造函数构造器

假如我们要实现一系列的函数:add10,add20。我们为此构造了一个名为 adder 的构造器,如下:

var adder = function (x) {
  var base = x;
  return function (n) {
    return n + base;
  };
};

var add10 = adder(10);
console.log(add10(5));

var add20 = adder(20);
console.log(add20(5));

每次调用 adder 时,adder 都会返回一个函数给我们。我们传给 adder 的值,会保存在一个名为 base 的变量中。由于返回的函数在其中引用了 base 的值,于是 base 的引用计数被 +1。当返回函数不被垃圾回收时,则 base 也会一直存在。

闭包有什么弊端?

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

参考:
学习Javascript闭包(Closure)node-lessons/lesson11 at master · alsotang/node-lessons · GitHubJavaScript作用域链
时间: 2024-10-10 08:24:41

理解函数作用域与闭包的相关文章

09 匿名函数、函数作用域、闭包、递归

匿名函数(lambda) 语法规则: lambda 参数:表达式 filter(lambdax: x>10, li) 匿名函数:可以赋给一个变量,重复使用. fun1 = lambda x: x * 10 print(fun1(10)) 打印结果: 100 fun = (lambda x: x * 10)(5) print(fun) 打印结果: 50 函数作用域 01外部不能访问函数内部变量: def fun1(): x = 1 print(x) 02函数内部能够访问函数外部变量: x = 12

关于var函数作用域,闭包与let的区别应用,(前端网备份)

var a = [];for (var i = 0; i < 10; i++) {a[i] = (function(j){return function(){ console.log(j); };})(i);}a[7](); var b = []; for (let i = 0; i < 10; i++) { b[i] = function(){ console.log(i); }; } b[7](); var pubvar = 1; 这两个结果是一样的 关于闭包的varfunction cr

js中理解函数作用域的实例

一.Javascript的变量的scope是根据方法块来划分的(也就是说以function的一对大括号{ }来划分).切记,是function块,而for.while.if块并不是作用域的划分标准,可以看看以下几个例子:           1 <script type ="text/javascript" > 2 function test2(){ 3 alert ("before for scope:"+i); // i未赋值(并不是未声明!使用未声

JavaScript函数,作用域以及闭包

JavaScript函数,作用域以及闭包 1. 函数 (1). 函数定义:函数使用function关键字定义,它可以用在函数定义表达式或者函数声明定义. a. 函数的两种定义方式: * function functionName() {} * var functionName = function(){} b. 两种函数定义不同之处 1). 声明提前问题 函数声明语句   :声明与函数体一起提前 函数定义表达式 :声明提前,但是函数体不会提前 请看下面图示:绿色线上面实在js初始加载的时候,查看

深入理解javascript原型和闭包(12)——简介【作用域】

摘自:http://www.cnblogs.com/wangfupeng1988/p/3991151.html:作者:王福朋: 提到作用域,有一句话大家(有js开发经验者)可能比较熟悉:"javascript没有块级作用域".所谓"块",就是大括号"{}"中间的语句.例如if语句: 再比如for语句: 所以,我们在编写代码的时候,不要在"块"里面声明变量,要在代码的一开始就声明好了.以避免发生歧义.如: 其实,你光知道&quo

理解Javascript_15_作用域分配与变量访问规则,再送个闭包 【转】

在阅读本博文之前,请先阅读<理解Javascript_13_执行模型详解> 在'执行模型详解'中讲到了关于作用域分配的问题,这一篇博文将详细的说明函数对象.作用域链与执行上下文的关系. 作用域分配与变量访问规则 在 ECMAScript 中,函数也是对象.函数对象在变量实例化过程中会根据函数声明来创建,或者是在计算函数表达式或调用 Function 构造函数时创建.(关于'函数对象'请见<理解Javascript_08_函数对象>).每个函数对象都有一个内部的 [[scope]] 

深入理解javascript原型和闭包(18)——补充:上下文环境和作用域的关系

本系列用了大量的篇幅讲解了上下文环境和作用域,有些人反映这两个是一回儿事.本文就用一个小例子来说明一下,作用域和上下文环境绝对不是一回事儿. 再说明之前,咱们先用简单的语言来概括一下这两个的区别. 00 上下文环境: 可以理解为一个看不见摸不着的对象(有若干个属性),虽然看不见摸不着,但确实实实在在存在的,因为所有的变量都在里面存储着,要不然咱们定义的变量在哪里存? 另外,对于函数来说,上下文环境是在调用时创建的,这个很好理解.拿参数做例子,你不调用函数,我哪儿知道你要给我传什么参数? 01 作

函数放到onload里面,在html里面执行函数会报错-----作用域和闭包相关问题

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>修改显示隐藏</title> <meta name="description" content

深入理解javascript原型和闭包(2)——函数和对象的关系 (转载)

深入理解javascript原型和闭包(2)--函数和对象的关系 上文(理解javascript原型和作用域系列(1)--一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; console.log(fn instanceof Object); // true 对!函数是一种对象,但是函数却不像数组一样--你可以说数组是对象的一种,因为数组就像是对象的一个子集一样.但是函数与对象之间,却不仅仅是一种包含和被包含