整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

javascript中的闭包

javascript中的闭包

s中闭包closure,是指函数变量可以保存在函数作用域内,因此看起来是函数将变量“包裹”了起来,根据定义,包含变量的函数就是闭包。

最初的定义

闭包(closure),是指函数变量可以保存在函数作用域内,因此看起来是函数将变量“包裹”了起来。

//根据定义,包含变量的函数就是闭包

function foo() {

var a=0;

}

cosole.log(a)

// Uncaught ReferenceError: a is not defined



《JavaScript高级程序设计》对闭包定义

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


//根据《JavaScript高级程序设计》,访问上层函数的作用域的内层函数就是闭包

function foo() {

var a=2;

function bar() {

console.log(a);

}

bar();

}

foo();



《JavaScript权威指南》对闭包定义

函数对象可以通过作用域链相互关联起来,函数体内部变量可以保存在函数作用域内,这就是闭包。



var global ="global scope"; //全局变量

function checkscope() {

var scope="local scope"; //局部变量

function f() {

return scope; //在作用域中返回这个值

};

return f();

}

checkscope(); // 返回 "local scope"



严格来说,闭包需要满足三个条件:

【1】访问所在作用域;

【2】函数嵌套;

【3】在所在作用域外被调用
有些人觉得只满足条件1就可以,所以IIFE是闭包;有些人觉得满足条件1和2才可以,所以被嵌套的函数才是闭包;有些人觉得3个条件都满足才可以,所以在作用域以外的地方被调用的函数才是闭包

为什么我们需要闭包,js闭包有什么用处

首先来看一个例子,我们来实现一个计数器。

var counter=0;

function add() {

return counter +=1;

}

add();

add();

add();// 计数器现在为 3

现在我们已经达到了目的,可是问题来了,代码中的任何一个函数都可以随意改变counter的值,所以这个计数器并不完美。那我们把counter放在add函数里面不就好了么?

function add() {

var counter=0;

return counter +=1;

}

add();

add();

add();// 本意是想输出 3, 但输出的都是 1

所以这样做的话,每次调用add函数,counter的值都要被初始化为0,还是达不到我们的目的。

如何使用闭包

所以这时候我们就要用闭包去解决这个问题了,先看代码。


var add=(function () {

var counter=0;

return function () {return counter +=1;}

})();

add();

add();

add();// 计数器为 3

这时候我们完美实现了计数器。这段非常精简,可以拆分成如下等价代码。

function outerFunction () {

var counter=0;

function innerFunction (){

return counter +=1;

}

return innerFunction;

}

var add=outerFunction();

add();

add();

add();// 计数器为 3

这时候的add就形成了一个闭包。一个闭包由两部分组成,函数和创建该函数的环境。环境是由环境中的局部变量组成的。对于闭包add来说,它由函数innerFunction和变量counter组成,所以这时候add是可以访问变量counter的。

使用闭包应注意的问题

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

function f2(){

var n=22;

var nAdd=function(){n++};

return function(){

return {

n:n,

nAdd:nAdd

}

}

}

//result2就是创建了一个匿名函数

var result2=f2();

//调用函数

console.log(result2());

result2().nAdd();

console.log(result2());

//解除对匿名函数的引用,以便释放内存

result2=null;

么是闭包?相信很多人听了有点懵,然后去百度查了一下官方的定义“闭包就是能够读取其他函数内部变量的函数”发现感觉自己更懵了。关于闭包如果只看那官方的定义的话确实不是让人很容易理解。

要想理解什么是闭包,首先得弄清楚什么是作用域,作用域可以理解为一个变量可以使用的范围。

在 JavaScript 中有两种作用域类型:

  • 局部作用域

在 JavaScript 函数中声明的变量会成为函数的局部变量。局部变量的作用域是局部的所以只能在函数内部访问它们。

  • 全局作用域

函数外声明的变量属于全局变量。全局变量的作用域是全局的所以网页的任意部分和函数都能够访问它。

有了作用域做铺垫,再来学习闭包就容易多了。

function f1(){
    var n=10;//局部变量n
    					//在f1函数内部声明的f2函数
    function f2(){
      alert(n);
    }
    return f2;//将f2函数作为f1函数的返回值
  }
  var fobj=f1();//f1调用完后的返回值是一个f2函数,此时fobj就是f2函数
  fobj(); // 输出10,调用f2函数

上述代码就是一个简单的闭包函数,闭包的用处有两个,一个是可以读取函数内部的变量,另一个就是在内存中让这些变量的值始终保持。

  function f1(){
    var n=5;//局部变量n
    add=function(){  //fdd前面没有使用var关键字,因此add是一个全局变量的匿名函数
      						n+=1
    					}
   					 //在f1函数内部声明的f2函数
    function f2(){
      alert(n);
    }
    return f2;
  }
  var fadd=f1();
  result(); // 输出5
  add();
  fadd(); // 输出6

从上面这段代码中可以看出,fadd就是闭包f2函数。它一共运行了两次,第一次的值是5,第二次的值是6。这证明函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。因为f1是f2的父函数,而f2相当于赋给了f1一个全局变量,所以f2一直在内存中。而f2的存在又依赖于f1,因此f1也一直在内存中,不会在调用结束后回收。

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

、词法定义域 Lexical

Closure闭包是编程语言Lexical Scoping的专有属性,区别于dynamic scoping。即函数执行调用的是其在定义过程中的”变量定义域“,而非其在调用时候的变量定义域。

Javascript的函数的初始状态不仅包括函数本体而且包括函数定义过程所在的定义域。

Like most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the scope in which the function definition appears. This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.

看下面的例子:

function makeCounter () {
    let counter=0;
    return function() {return counter++;};
}
let counter=makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());

#+RESULTS:
: 0
: 1
: 2

对这个嵌套函数而言,最有意思的一点是:当外部函数被调用返回后(这里是makeCounter()), 再也没有任何手段能够触及到 counter 这个变量。只有内嵌函数拥有专属权限抵达该变量。


二、Closure的标准定义

开发者通常应该都知道“闭包”这个通用的编程术语。

闭包 是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 "new Function" 语法 中讲到)。

也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。

在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节。