转:对JavaScript中闭包的理解

关于 const     let      var 

总结:javascript

建议使用 let  ,而不使用var,若是要声明常量,则用const。php

ES6(ES2015)出现以前,JavaScript中声明变量只有经过 var 关键字,函数声明经过 function 关键字,html

ES6以后,声明的方式有 var 、 let 、 const 、 function 、 class 。前端

const 用来定义常量,使用时必须初始化(即必须赋值),只能在块做用域里访问,并且不能修改。java

let      let是相对var的,优势是let 声明的变量不存在变量提高。nginx

var     存在变量提高,变量提高是指不管 var 出如今一个做用域的哪一个位置,这个声明都属于当前的整个做用域,在其中处处均可以访问到。面试

参考连接: JavaScript中var、let和const的区别   js中 let var const 的差别和使用场景  JS中的块级做用域,var、let、const三者的区别docker

 

关于 局部变量 和 全局变量

js的变量有两种做用域:全局变量和局部变量。浏览器

没有使用 var 声明的变量和在function以外声明的变量都是全局变量,是window属性之一;缓存

使用 var 声明的变量属于所在函数,无论在函数的哪一个位置出现,等价于在函数一开始声明

局部变量比同名全局变量的优先级高,因此局部变量会隐藏同名的全局变量。要想访问被隐藏的全局变量就加上 window. 前缀。

js(ES6以前)没有块做用域,在语句块以后,在函数结束以前,语句块定义的变量都是能够访问的。

好比:for(var idx =0; idx<2;idx++){} alert(idx); 结果显示为2。ES6出现后,有了let ,这里代码若是为for(let idx =0; idx<2;idx++){} alert(idx);  则会运行时报错:idx is not difined;

关于局部变量和全局变量的示例:

<script type="text/javascript">
var a = 1;
function hehe()
{
         window.alert(a);//undifined
         var a = 2; //声明了局部变量
         window.alert(a);//2
}
hehe();
</script>
若是上面这个例子中把 var 换成 let ,则会报错,由于 let 在同一个范围内不能重复定义。 
<script type="text/javascript">
var a = 1;
function hehe()
{
         window.alert(a);//1
         a = 2; // 修改的是全局变量 a
         window.alert(a);//2
}
hehe();
</script>
<script type="text/javascript">
var a = 1;
function hehe()
{
     window.alert(this.a);//1 //访问全局变量  this.a 换成 window.a 也能够  关于 this 的指向,就一句话:谁最终调用函数,this指向谁!!! 参考 this指向
     var a = 2;
     window.alert(a);//2
}
hehe();
</script> 

参考连接:JavaScript局部变量和全局变量的理解

 

关于 js 闭包

JavaScript 变量属于本地或全局做用域。全局变量可以经过闭包实现局部(私有)。

Javascript容许使用内部函数---即函数定义和函数表达式位于另外一个函数的函数体内。

这些内部函数能够访问它们所在的外部函数中声明的全部局部变量、参数和声明的其余内部函数。

当其中一个这样的内部函数在包含它们的外部函数以外被调用时,就会造成闭包。

详见下文。

参考:W3school JS 闭包

原文连接:http://www.javashuo.com/article/p-pjavktlw-z.html

在前端开发中闭包是一个很重要的知识点,是面试中必定会被问到的内容。以前我对闭包的理解主要是"经过闭包能够在函数外部能访问到函数内部的变量",对闭包运用的也不多,甚至本身写过闭包本身都不太清楚,只知道这样写能够解决问题。最近在梳理本身的js知识点,发现本身对js闭包理解的很不透彻,因而想全面的分析一下闭包,特别是闭包的造成缘由和闭包的使用场景。

闭包的定义

闭包是指有权访问另外一个函数做用域中的变量的函数 --《JavaScript高级程序设计》

函数对象能够经过做用域关联起来,函数体内的变量均可以保存在函数做用域内,这在计算机科学文献中称为“闭包”,全部的javascirpt函数都是闭包 --《Javascript权威指南》

看完这些专业的定义是否是感受一头雾水,不要紧,我和同样也没明白这些定义说的是啥,咱接着往下分析。

在认识闭包原理以前咱们先必须对做用域、执行上下文、执行上下文堆栈、变量对象、活动对象、做用域链有着全面的认识

做用域 Scope

做用域是一套规则,用于肯定在何处以及如何查找变量(标识符)
做用域共有两种主要的工做模型:

  • 词法做用域:做用域是在编写代码的时候肯定的
  • 动态做用域:做用域是在代码运行的时候肯定的

咱们知道javascript使用的是词法做用域

执行上下文 Execution Contexts

Javascript中代码的执行上下文分为如下三种:

  • 全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,引擎最早进入的就是这个环境。
  • 函数级别的代码 – 当执行一个函数时,运行函数体中的代码。
  • Eval的代码 – 在Eval函数内运行的代码。

一个执行的上下文能够抽象的理解为一个对象。每个执行的上下文都有一系列的属性(变量对象(variable object),this指针(this value),做用域链(scope chain) )

Execution Contexts = { variable object:变量对象; this value: this指针; scope chain:做用域链; }

执行上下文堆栈 Execution Contexts Stack

活动的执行上下文在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(globalContext),而顶部就是当前(活动的)执行上下文。

<script> function add(num){ var sum = 5; return sum + num; } var sum = add(4); </script>

当add函数被调用时,add函数执行上下文被压入执行上下文堆栈的顶端,此时执行上下文堆栈可表示为:

EC Stack = [
  <add> functionContext globalContext ];

add函数执行完毕后,其执行上下文将会从执行上下文堆栈顶端弹出并被销毁。全局执行上下文只有在浏览器关闭时才会从执行上下文堆栈中销毁

变量对象 Variable Object

若是变量与执行上下文相关,那变量本身应该知道它的数据存储在哪里,而且知道如何访问。这种机制称为变量对象(variable object)。
能够说变量对象是与执行上下文相关的数据做用域(scope of data) 。它是与执行上下文关联的特殊对象,用于存储被定义在执行上下文中的变量(variables)、函数声明(function declarations) 。
当进入全局上下文时,全局上下文的变量对象可表示为:

VO = {  add: <reference to function>,  sum: undefined,  Math: <...>,  String: <...> ...  window: global //引用自身 }

活动对象 Activation Object

当函数被调用者激活时,这个特殊的活动对象(activation object) 就被建立了。它包含普通参数(formal parameters) 与特殊参数(arguments)对象(具备索引属性的参数映射表)。活动对象在函数上下文中做为变量对象使用。
当add函数被调用时,add函数执行上下文被压入执行上下文堆栈的顶端,add函数执行上下文中活动对象可表示为

AO = {
    num: 4,
    sum :5,
    arguments:{0:4}
}

做用域链 Scope Chain

函数上下文的做用域链在函数调用时建立的,包含活动对象AO和这个函数内部的[[scope]]属性。

var x = 10; function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar(); } foo(); 

在这段代码中咱们看到变量"y"在函数"foo"中定义(意味着它在foo上下文的AO中)"z"在函数"bar"中定义,可是变量"x"并未在"bar"上下文中定义,相应地,它也不会添加到"bar"的AO中。乍一看,变量"x"相对于函数"bar"根本就不存在;
函数"bar"如何访问到变量"x"?理论上函数应该能访问一个更高一层上下文的变量对象。实际上它正是这样,这种机制是经过函数内部的[[scope]]属性来实现的。
[[scope]]是全部父级变量对象的层级链,处于当前函数上下文之上,在函数建立时存于其中。

注意: [[scope]]在函数建立时被存储是静态的(不变的),直至函数销毁。即:函数能够永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
在这里咱们逐步分析下
全局上下文的变量对象是:

globalContext.VO === Global = { x: 10 foo: <reference to function> };

在"foo"建立时,"foo"的[[scope]]属性是:

foo.[[Scope]] = [ globalContext.VO ];

在"foo"激活时(进入上下文),"foo"上下文的活动对象是:

fooContext.AO = {
  y: 20, bar: <reference to function> };

"foo"上下文的做用域链为:

fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

内部函数"bar"建立时,其[[scope]]为:

bar.[[Scope]] = [ fooContext.AO, globalContext.VO ];

在"bar"激活时,"bar"上下文的活动对象为:

barContext.AO = {  z: 30 };

"bar"上下文的做用域链为:

bar.Scope= [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

介绍了一大堆内容,是否是有点晕忽忽的?坚持一下,下面是重点

闭包的原理

咱们经过一个闭包的例子来分析一下闭包的造成原理

function add(){ var sum =5; var func = function () { console.log(sum); } return func; } var addFunc = add(); addFunc(); //5

js执行流进入全局执行上下文环境时,全局执行上下文可表示为:

globalContext = {  VO: {  add: <reference to function>,  addFunc: undefined },  this: window, scope chain: window }

当add函数被调用时,add函数执行上下文可表示为:

addContext = {
    AO: { sum: undefined //代码进入执行阶段时此处被赋值为5 func: undefined //代码进入执行阶段时此处被赋值为function (){console.log(sum);} }, this: window, scope chain: addContext.AO + globalContext.VO }

add函数执行完毕后,js执行流回到全局上下文环境中,将add函数的返回值赋值给addFunc。

因为addFunc仍保存着func函数的引用,因此add函数执行上下文从执行上下文堆栈顶端弹出后并未被销毁而是保存在内存中。

当addFunc()执行时,func函数被调用,此时func函数执行上下文可表示为:

funcContext = {
    this: window, scope chain: addContext.AO + globalContext.VO }

当要访问变量sum时,func的活动对象中未能找到,则会沿着做用域链查找,因为js遵循词法做用域,做用域在函数建立阶段就被肯定,在add函数的活动对象中找到sum = 5;

介绍到这里你明白造成闭包的缘由了吗?
Javascript容许使用内部函数---即函数定义和函数表达式位于另外一个函数的函数体内。并且,这些内部函数能够访问它们所在的外部函数中声明的全部局部变量、参数和声明的其余内部函数。当其中一个这样的内部函数在包含它们的外部函数以外被调用时,就会造成闭包。

闭包的用途

闭包能够用在许多地方。它的最大用处有两个,一个是前面提到的能够读取函数内部的变量,另外一个就是让这些变量的值始终保持在内存中。

1. 保护变量的安全实现JS私有属性和私有方法

利用闭包能够读取函数内部的变量,变量在函数外部不能直接读取到,从而达到保护变量安全的做用。由于私有方法在函数内部都能被访问到,从而实现了私有属性和方法的共享。
常见的模块模式就是利用闭包的这种特性创建的

var Counter = (function() { //私有属性 var privateCounter = 0; //私有方法 function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(privateCounter); //privateCounter is not defined console.log(Counter.value()); // 0 Counter.increment(); Counter.increment(); console.log(Counter.value()); // 2 Counter.decrement(); console.log(Counter.value()); // 1

在jQuery框架的私有方法和变量也是这么设计的

var $ = jQuery = function(){ return jQuery.fn.init(); } jQuery.fn = jQuery.prototype = { init:function(){ return this; //this指向jQuery.prototype }, length: 1, size: function(){ return this.length; } } console.log($().size()); // 1

2. 将处理结果缓存

var mult = (function(){ var cache = {}; var calculate = function(){ var a = 1; for(vari=0,l=arguments.length;i<l;i++){ a = a*arguments[i]; } return a; }; return function(){ var args = Array.prototype.join.call(arguments,','); if(args in cache){ return cache[args]; } return cache[args] = calculate.apply(null,arguments); } })();

这样咱们在第二次调用的时候,就会从缓存中读取到该对象。

理解了闭包的原理咱们发现闭包的这些用途都是利用了闭包保存了当前函数的活动对象的特色,这样闭包函数在做用域以外被调用时依然可以访问其建立时的做用域

闭包的缺点

  • 闭包将函数的活动对象维持在内存中,过分使用闭包会致使内存占用过多,因此在使用完后须要将保存在内存中的活动对象解除引用;
  • 闭包只能取得外部函数中任何变量的最后一个值,在使用循环且返回的函数中带有循环变量时会获得错误结果;
  • 当返回的函数为匿名函数时,注意匿名函数中的this指的是window对象。

这里仅仅是我对闭包的一些看法,如有错误的地方,还望你们提出,一块儿交流共同进步!
参考文献

若是您对本文有什么疑问,欢迎提出我的看法,若您以为本文对你有用,不妨帮忙点个赞,或者在评论里给我一句赞美,小小成就都是从此继续为你们编写优质文章的动力, 欢迎您持续关注个人博客:)

做者:Jesse131

出处:http://www.cnblogs.com/jesse131/

关于做者:专一前端开发。若有问题或建议,请多多赐教!

本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文连接。

相关文章
相关标签/搜索