函数作用域查找、闭包和匿名函数整理

一、函数作用域查找

1、定义说明
1)、函数当前作用域查找不到,可以访问外层函数作用域的活动对象(参数、局部变量、定义在外层函数体里的函数)
2)、外层的外层函数。。。一直到全局

第一条说明:定义在外层函数体里的函数,包括当前函数,当前函数调用自己的时候,就是递归调用。

2、原理
执行环境、作用域链、作用域、活动对象
1)、调用内层函数,会创建一个执行环境,执行环境会关联一个作用域链
2)、调用内层函数时,所有的外层函数都已经调用完毕或者外层函数调用中,所有只要把所用外层函数作用域(包括最外层全局)的活动对象,关联到当前内层函数的作用域链上。
3)、最后创建内层函数作用域的活动对象,并且关联到作用域链的最前端。

活动对象注释:
函数参数,函数体里面定义的局部变量,函数体里面定义的函数

3、代码示例

var str1 = '全局变量';
function func(arg) {
  var str2 = '外层局部变量';
  function funcInner1() {
    console.log('外层函数的其他函数');
  }
  function funcInner2(argInner2) {
    var str3 = '内层函数变量';
    console.log(str3);
    console.log(argInner2);

    console.log(arg);
    console.log(str2);
    funcInner1();
    console.log(str1);
  }
  return funcInner2;
}
var result = func('外层函数参数')
result('内层函数参数');

执行结果:
内层函数变量
内层函数参数
外层函数参数
外层局部变量
外层函数的其他函数
全局变量

 

二、闭包

1、定义
定义在函数体里的内部函数,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

2、通俗解释
内层函数作为外层函数执行结果被返回,当外层函数执行完毕后,定义在其内部的局部变量、参数和声明的其他内部函数并没有被回收,而是可以通过返回的函数的执行被获取,即形成了闭包。

3、形成闭包的条件
1)、嵌套函数
2)、内层函数访问外层函数的活动对象
3)、内层函数子在外层函数之外的地方被调用

第三条件解释:一般是内层函数作为外层函数的返回值,外层函数之外的地方,可以先调用外层函数,拿到对应返回值,再通过返回值调用内层函数。

4、特点:
1)、作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
2)、一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

5、原理
函数作用域查找

6、代码示例

function outer() {
  var scope = 10;
  return function inner() {
    scope += 10;
    console.log(scope);
  }
}
var fn = outer();
fn();

执行结果:
20
//inner函数作为outer函数执行结果被返回,当outer函数执行完毕后,定义在其内部的变量scope并没有被回收,而是可以通过函数fn的执行被获取,这里的inner函数,即形成了闭包。

7、好处:
1)、变量长期驻扎在内存中;
2)、避免全局变量的污染;

避免全局变量的污染说明:
上面代码示例中,我们也可以把scope定义成全局变量,但是这样我们就没法控制仅允许inner函数可以修改scope的值,因为全局变量,所有地方都可以访问。

8、坏处:
1)、内存消耗
通常来说,函数的活动对象会随着执行期上下文一起销毁,但是,由于闭包引用另外一个函数的活动对象,因此这个活动对象无法被销毁,这意味着,闭包比一般的函数需要更多的内存消耗。

进一步说明:尤其在IE浏览器中需要关注。由于IE使用非原生javascript对象实现DOM对象,因此闭包会导致内存泄露问题。
2)、性能问题
使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失。

进一步说明:因此在脚本中,最好小心使用闭包,它同时会涉及到内存和速度问题。不过我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

10、闭包常用案例
1)、延迟输出
2)、累加器

代码1:
//延迟输出
for (var i = 0; i < 5; ++i) {
  (function (i) {
    setTimeout(function () {
      console.log(i + ' '); 
    }, 100); 
  })(i);
}

执行结果:
0 
1 
2 
3 
4

代码2:
//累加器
//initNum: 初始值, step: 步长
function getTotalizer(initNum, step) {
  var num = initNum;
  return function() {
    num = num + step;
    return num;
  }
}

var add = getTotalizer(10, 5);
console.log(add());
console.log(add());
console.log(add());
console.log(add());

执行结果:
15
20
25
30

 

三、匿名函数

1、定义:
匿名函数,就是没有函数名的函数

2、三种调用方法
1)、可以定义一个匿名函数,并且立即调用。
2)、可以定义一个匿名函数,赋值给一个变量,通过这个变量调用函数。
3)、匿名函数作为函数返回值返回,通过返回值调用匿名函数。

3、匿名函数与闭包关系
1)、匿名函数和闭包没有实际关系
2)、闭包的条件,需要返回一个内层函数,这个内层函数访问外层函数的活动对象,这个内层函数在外层函数之外的地方被调用。
3)、上面内层函数只能通过函数的返回值被调用,不能通过方法名直接调用,这种情况下,把这个内层函数声明成匿名函数更合理,通常也是这么用的。

4、代码示例

//1、通过表达式自我执行
(function() {
  console.log('执行');
})();

//2、将匿名函数赋给变量
var result = function () { 
  console.log('执行');
};
result();

//3、匿名函数作为函数返回值
function func() {
  var i = '局部变量i';
  return function () {     
    return i;
  };
}
console.log(func()());

 

posted @ 2019-08-02 13:42  bravo2012  阅读(365)  评论(1编辑  收藏  举报