天天看点

作用域作用域链及闭闭包作用域作用域链及闭闭包

作用域作用域链及闭闭包

1作用域作用域链

作用域:我相信很多学习者都学过一些编程语言,作用域应该并不陌生,用我自己理解来说,作用域就是规定变量的使用的一个范围

作用域链:用我自己的理解来说,作用域链是一种层级关系,就是一个大的作用域范围嵌套了几个小的作用域范围。

1.1作用域

在javascript(ES5)中有两种类型的作用域

函数作用域:(函数内部作用域)

  • 在函数内部声明的变量,会成为全局作用域
  • 函数内部声明的变量,函数外部不能声明

全局作用域:(函数外部作用域)

  • 函数之外声明的变量,会成为全局变量。
  • 函数外部声明的变量,在函数内部可以访问。
  • 当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。

以上可以归结为:**函数内部作用域 ** 和 函数外部作用域

在其他语言中也有作用域,其实大部分的规定可以在javascript中使用,但是在javascript也有跟其他语言不同的规定

举个例子:

{
  var a = 10
}
console.log(a); //a
           

在其他语言中,看到

{}

包含着的变量,在其他语言中一般是不可调用的,但是在

javascript

中可以使用

1.2作用域

在了解作用域链之前先了解一下什么是自由变量

var a = 100
function fn() {
    var b = 200
    console.log(a) // 这里的a在这里就是一个自由变量  // 100
    console.log(b)
}
fn()
           

简单理解就是在 要使用的那个作用域中找不到定义的变量

代码中

console.log(a)

这条语句的作用域是在函数

fn

中的 此时的

a

不在此层作用域,所以叫做自由变量。

理解了自由变量之后,让我们来理解一下作用域链

var a = 100
function F1() {
  var b = 200
  function F2() {
    var c = 300
    console.log(a) // 自由变量,顺作用域链向父作用域找 //100
    console.log(b) // 自由变量,顺作用域链向父作用域找 //200
    console.log(c) // 本作用域的变量  //300
  }
  F2()
}
F1()
           

上面的例子中

F2

函数作用域中,只用

c

声明定义了,

a

b

都没有声明定义中,所以为自由变量。

那么自由变量会怎么获取它本身的值呢?

答案是:自由变量会顺着生成 当前函数的作用域 的父作用域查找

作用域作用域链及闭闭包作用域作用域链及闭闭包

这种向着父作用域,查找的链式结构就可以理解成为作用域。简单来说就是,嵌套,儿子找爸爸

2闭包

闭包就是能够读取其他函数内部变量的函数。在javascript中,可以将闭包理解成“函数中的函数“。

理解了作用域和作用域链之后,我们来说说闭包

假设有一种情况

function f1() {
  var n = 999;
}
console.log(n)
           

外面向获取函数中的局部变量,是获取不到的,所以我们必须得打造一个闭包。

function f1() {
  var n = 999;
  function f2(){
     console.log(n)
  }
  return f2
}
var fn = f1()
fn()  //999
           

上面代码中

f2

函数就是一个闭包,能够获取局部变量。

那么闭包有什么作用呢?

  1. 可以读取外层函数内部的变量
  2. 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

第一点在上面已经举例,下面我们来理解第二个作用

例如

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7
           

return

返回的匿名函数是一个闭包

这个时候他会让外部调用的变量,一直储存到它里面闭包环境中。所以每次调用的时候不是

5

,而是依次递增。

3作用域作用域链及闭闭包习题

如果你看了上面的内容还是不太理解的话,没关系,我们做几道题让你加深理解

做题前,回忆一下自由变量的概念和闭包的结构

示例一:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
           

解析:

1.先看真正调用的是哪个函数 这里是

checkscope

调用这个函数返回它的内部函数

f

,这是符合一个闭包结构 (函数内部嵌套一个函数,且返回它)

2.

f

中的变量

scope

是一个自由变量,所以会顺着生成当前函数作用域的父作用域来寻找

3.向上寻找找到

checkscope

函数作用域,正好定义了一个

scope

所以返回值是

local scope

示例二

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope(); 
foo();
           

解析:

1.

foo()

调用,这里的

foo()

相当于

checkscope

中的

f

。还是那句话,看是否是自由变量,然后顺着生成当前函数作用域的父作用域

这里输出的还是

local scope

做了两题是不是加深了概念,请记住,自由变量是顺着生成当前函数作用域的父作用域寻找

示例三:

var value = 1;
 
function foo() {
    console.log(value);
}
 
function bar() {
    var value = 2;
    foo();
}
 
bar();
           

看了这一题你就会理解为什么自由变量是顺着生成当前函数作用域的父作用域寻找了

解析:

1.调用了

bar

bar

函数里又调用了

foo

,再看

foo

可以得知输出

value

2.可能有些同学会觉得选择

2

,不过请记住自由变量是顺着生成当前函数作用域的父作用域寻找

3.生成

foo

函数的作用域是 全局作用域,这个自由变量

value

就是向全局作用域寻找 所以为什么会是

1

示例四:

function F1(){
    var a=100;
    return function(){
        console.log(a);
    }
}
var f1=F1();
var a=200;
f1();
           

解析:

a

是自由变量,自由变量是顺着生成当前函数作用域的父作用域寻找

所以答案是100

示例五:

function F2(){
    var b=100;
    return function(){
        console.log(b);
    }
}
var f2=F2();
function F3(fn){
    var b=300;
    fn();  //100
}
F3(f2);
           

解析:

b

F2

函数的匿名函数中是自由变量,还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找

所以

b

为100

示例六:

var a = 1
function fn1(){  
  function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2   
  return fn3   
}
var fn = fn1() 
fn() 
           

解析:

1.

fn1

函数嵌套了两个函数

f2

f3

,是两个闭包,且返回值是

f3

函数

2.

fn

调用其实就是调用

f3

函数,

f3

又调用了

f2

3.此时来到

fn2

a

是一个自由变量,还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找

创建

f2

函数的作用域是

f1

则输出

2

作用域作用域链及闭闭包作用域作用域链及闭闭包

示例七:

var a = 1        
function fn1(){
  function fn3(){  
    var a = 4
    fn2()        
  }
  var a = 2
  return fn3    
}

function fn2(){
  console.log(a)  
}
var fn = fn1()   
fn()
           

解析:

1.

fn1

嵌套了

fn3

这个函数,并且返回,调用

fn

其实就是调用了

fn3

2.

fn3

又调用了

fn2

,这是

fn2

中有自由变量

a

,遇到就还是那句话自由变量是顺着生成当前函数作用域的父作用域寻找

3.生成

fn2

函数的作用域是全局作用域,所以自由变量

a

就直接在全局中找,答案为

1

作用域作用域链及闭闭包作用域作用域链及闭闭包

示例八:

var a = 1
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
    var a = 4
  }      
  var a = 2
  return fn3
}
var fn = fn1()
fn()
           

解析:

1.

fn1

包含一个闭包

fn3

,

fn3

包含了一个闭包

fn2

,调用

fn1

其实就是调用了

fn3

2.调用了

fn3

之后,里边又调用了

fn2

3.

fn2

中有自由变量

a

,一遇到直接那句话自由变量是顺着生成当前函数作用域的父作用域寻找

4.此时注意是,是先调用

fn2

再定义变量a,此时会有一个函数内部的变量提升。所以取值应该是

undefined

函数内部变量提升应该变成这样的

var a = 1
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    var a //undefined
    fn2()
    a = 4 //先调用后赋值,所以是undefined
  }      
  var a = 2
  return fn3
}
var fn = fn1()
fn()
           

总结:

​ 遇到作用域作用域链及闭闭包的题目时候,不要慌张,记住以下几点就好了。

​ 1、先弄清楚到底调用的是哪个函数

​ 2、看看调用的哪个函数是否存在自由变量

​ 3、一遇到自由变量,直接那句话自由变量是顺着生成当前函数作用域的父作用域寻找

以上如有错误欢迎指正,一起学习 LOVE&&PEACE

继续阅读