作用域及闭包

执行上下文环境到作用域再到闭包

执行上下文环境


说到作用域,首先理解一下执行上下文环境(在执行代码之前,把要用到的变量先取出来,有的直接赋值,有的用undefined占位)

  • 对于普通变量和函数表达式,声明并默认赋值 undefined

    1
    2
    var a = 1  //var a 提升,默认值为undefined
    var fn = function(){}
  • 对于函数声明,会赋值

    1
    function fn(){}

作用域


什么是作用域:

作用域是你的代码在运行时,各个变量、函数和对象的可访问性。换句话说,作用域决定了你的代码里的变量和其他资源在各个区域中的可见性。

为什么需要作用域?最小访问原则

那么,限制变量的可见性,不允许你代码中所有的东西在任意地方都可用的好处是什么?其中一个优势,是作用域为你的代码提供了一个安全层级。计算机安全中,有个常规的原则是:用户只能访问他们当前需要的东西。

下面来看一下具体的作用域的问题,可以将作用域抽象的理解为一块地盘,当中没有变量的概念,需要通过作用域对应的执行上下文环境来获取变量的值,所以作用域中变量的值是在执行过程中产生和确定的,而作用域是在函数创建时就确定了。还要记住一点:除了全局作用域,只有函数才能创建作用域

作用域.png

  • 第一步,加载程序时就确定了全局上下文环境,每个函数也都会创建自己的作用域,函数的作用域在创建时就已经确定,而不是在调用的时候
  • 第二步,执行到最后一行调用函数fn(10),此时生成fn函数的上下文环境,并将它设置成活跃状态
  • 第三步,执行到调用bar(100),此时生成bar函数的上下文环境,并将它设置成活跃状态
  • 第四步,调用完bar(100),它的上下文环境被销毁,接着调用bar(200),生成它的上下文环境,并设置成活跃状态
  • 第五步,bar(200)调用结束,它的上下文环境被销毁,此时又会回到fn(10)的上下文环境,并将它设置成活跃状态
  • 第六步,执行完fn(10),它的上下文环境销毁,此时又回到全局上下文环境的活跃状态

作用域链:当你需要用到一个变量时,在哪个作用域中取得这个变量,这是一个由内而外的查找过程,一层一层直到全局作用域为止,需要注意的是,要到创建这个函数的作用域中取值而不是调用函数的时候

作用域和执行上下文环境的关系

作用域指的是变量的可见性,上下文指的是在相同作用域中this的值,我们可以使用函数方法改变上下文,在全局作用域中,上下文总是Window对象。一个作用域下可能包含了若干的上下文环境;有可能没有过上下文环境(函数始终没有被调用);有可能有过,但在函数被调用完之后,上下文环境被销毁;有可能存在一个或多个(即闭包)

闭包


闭包的概念与词法作用域紧密相关,当内部函数试着访问外部函数的作用域链(词法作用域之外的变量)时产生闭包,闭包包括了它们自己的作用域链、父级作用域链和全局作用域链

结合上面对作用域的理解,闭包的一个核心内容是:当函数调用完之后,它的执行上下文环境不会接着被销毁

1
2
3
4
5
6
7
8
9
10
11
12
var func = function(){
var a = 'jayce';
var fn1 = function(){
a += ' xi';
console.log(a);
}
return fn1
}

var fn2 = func();
fn2(); // jayce xi
fn2(); // jayce xi xi

当你第一次调用完fn2之后,func中的变量a变成’jayce xi’,而没有被销毁,fn1就形成了一个闭包,变量a的生命也因此得以延续。闭包有三个特点:

  • 它是一个函数,比如上面例子中的fn1
  • 函数中使用了其他作用域中的变量,使得变量不会被销毁,如上面的fn1调用了它上级作用域func中的变量a
  • 闭包存在在定义该变量的作用域中,上面的变量a存在于func函数的局部作用域中,fn1也存在于此

闭包是什么:函数、函数内部可以访问到的变量的总和(也叫环境),就是一个闭包。函数内部返回了一个函数,而函数一个很重要的特点在于可以创建一个独立作用域,当这个要返回的函数体中,需要引用一个变量,这个变量存在于它的上级函数的作用域中,那么这个变量所在的上级函数的上下文环境就不能被销毁,否则就不能获取这个变量了。(访问上级作用域中的变量的权限)

闭包解决了什么:因为闭包可以访问上级作用域,从而打破因作用域引起的函数外部不能访问另一个函数内部变量的束缚,就拿ajax的成功回调来说,这里就是一个闭包的概念,回调函数拥有了访问和操作上级作用域的能力,带来了极大的便利。

闭包的应用场景:ajax请求的成功回调、setTimeout延时回调、一个函数内部返回一个匿名函数、事件上绑定的回调方法,不管是哪种方式,只要是对函数类型的值进行传递,当函数在别处被调用时都是闭包的一个具体表现

作用:

  • 隐藏变量,但通过返回一个函数使得外部可以访问这个变量
  • 封装对象的私有属性和方法
0%