作用域链、闭包

一、概念

作用域链:子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

闭包就是,就是能够读取其他函数内部变量的函数。

               由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。能够读取其他函数内部变量的函数。

               所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

 

              在JS的函数执行会形成一个作用域,执行完成作用域会销毁

              但是闭包就不要一样了,由于闭包函数是函数内部的子函数,内部函数可以访问上级作用域,由于函数作用域中存在变量引用,所以执行完成不会立即销毁。

             

例如:A函数嵌套B函数,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包;闭包其实就是一个自带了执行环境(由外层函数提供,即便外层函数销毁依旧可以访问)的特殊函数。

 

概括的说:了解闭包先了解js的变量:全局/局部,全局变量可以在任何地方获取; 局部变量只能在函数内部获取,闭包的出现,可以获取函数内部的变量,且变量一直在内存当中。


 

二、闭包解决了哪些问题

  • 可以读取函数内部的变量
  • 让这些变量始终保持在内存中,函数调用后不会被清除。

 

     

解释:一般而言执行bar后,局部变量就会随着作用域销毁,但是这里返回的是一个匿名函数,匿名函数可以访问上级作用域中的变量 local

bar 的作用域发现变量有引用关系,就不会销毁了。
虽然在外部作用域中无法直接访 local,但是若要访问,只要通过这个中间函数周转一下就可以访问,之后只要调用 baz 函数就可以获取 local 变量了,不管你执行多少次,问题在于一旦有变量引用这个中间函数,这个中间函数就不会释放,同时也会使原始的作用域得不到释放。
 
 

三、经典应用
 
1、定义模块,我们将操作函数暴露给外部,而细节隐藏在模块内部👇
 
function module() {
    var arr = [];
    function add(val) {
        if (typeof val == 'number') {
            arr.push(val);
        }
    }
    function get(index) {
        if (index < arr.length) {
            return arr[index]
        } else {
            return null;
        }
    }
    return {
        add: add,
        get: get
    }
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));

2、工厂函数👇

 

3、vue源码:全局Api中的$watch方法中使用闭包👇

 

// 根据Vue的$watch原理实现的简易版$watch
Vue.prototype.$watch = function(exp, cb, options = {immediate: true, deep: false}) {
    let watcher = new Watcher(this, exp, cb, options);
    return () => {
        watcher.unWatch();
    };
}

 

 


 

四、注意事项

 

返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push( function () {
            return i * i;
        } );
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 16
f2(); // 16
f3(); // 16 原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16

⚠️ 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

解决方法👇

 

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push( (function (n) {  //⚠️注意这里 创建了匿名函数并立刻执行
            return function () {
                return n * n;
            }
        })(i) );
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

 


 

五、经典闭包面试题

 

 

 

 

参考地址:

https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

https://juejin.im/post/58f1fa6a44d904006cf25d22

https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016

https://www.cnblogs.com/echolun/p/11897004.html

 

posted @ 2020-06-11 14:28  CatherLee  阅读(453)  评论(0编辑  收藏  举报