函数作用域查找、闭包和匿名函数整理
一、函数作用域查找
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()());