浅析JS中的作用域链及闭包

 

前言

要知道什么是作用域链,我们首先得知道什么是作用域,简而言之,作用域就是变量起作用的范围,在es6之前作用域分为函数作用域(局部作用域)以及全局作用域。

全局作用域:即在全局中定义的变量,代码中的任何地方都能访问到的变量即为全局变量。

函数作用域:变量在函数内部声明后即拥有了函数作用域,对外部来说是无法访问的,但是如果变量在函数内部未声明直接赋值,则该变量为全局变量拥有全局作用域。

什么是作用域链

在对作用域有了一定的了解后,让我们来看看什么是作用域链。

当函数在执行的时候会生成一个[scope](作用域)对象,里面储存了函数执行时执行环境中内部和外部所有的函数和变量,根据js代码的执行顺序:

1.对基础语法进行检测,是否有语法错误。

2.全局预编译:产生一个全局的[scope]对象(简写GO),储存变量及函数声明,预编译分析变量声明及函数声明,变量会提升,即先声明,等代码执行时再赋值。(函数声明会覆盖原有的赋值)

3.函数预编译:产生一个自身的[scope](简写AO)对象,同样储存函数以及变量声明,函数的参数成为该对象的属性,如果未传递则为undefined。

4.解释执行,预编译过程已经分析过了变量及函数声明不需要再声明,只需要赋值。

之后会将预编译过程中产生的[scope]对象按照由外到里的顺序存入一个环境栈中,如下表:

2.自身的AO
1.外部函数的AO
0.全局下的GO

然后函数会对需要调用的变量按照由上到下的顺序进行查找,这种查找的方式呈链式结构,也就是作用域链。

举个栗子:

<script>
        var name = 'wuyanzhu'
        function fn1() {
            var name = 'pengyuyan'
            function fn2() {
                var name = 'liudehua'
                console.log(name);
            }
            function fn3() {
                console.log(name);
            }
            fn2();
            fn3();
        }
        fn1();
</script>

当执行fn2,fn3时,环境栈中的[scope]对象如下表:

fn2fn3

2.自身的AO

name = liudehua

2.自身的AO

未声明name

1.外部函数fn1的AO

name = pengyuyan

1.外部函数fn1的AO

name = pengyuyan

0.全局的GO

name = wuyanzhu

0.全局的GO

name = wuyanzhu

当fn2执行时,会从上往下从自身的AO开始查找name属性,故输出liudehua;

当fn3执行时,同样从自身AO中开始查找name属性,没有就往下一个继续查找,故输出pengyuyan;

一般来说,当函数执行完毕之后,所创建的[scope]对象会自动销毁,其中所保存变量及函数也会随之销毁,这也是外部函数无法访问内部函数变量的原因,但在某些情况下,我们又需要访问内部函数变量,这也就引入了闭包的概念,换句话说闭包就是可以访问其他函数内部变量的函数。

闭包

一.闭包的概念

可以访问其他函数内部变量的函数。

 

在大致了解了闭包的概念之后,让我们来看看闭包的作用具体用法。

再举个栗子:

<script>
        function outer() {
            var scope = "outer";
            function inner() {
                console.log(scope);//输出outer
            }
            inner();
        }
        var fn = outer();
        console.log(fn);//输出undefined
</script>

如上:正常情况下,内部函数可以访问外部函数的变量,但是外部函数却无法访问内部函数变量,inner正常访问scope输出outer,外部函数outer却无法访问inner中的变量,输出undefined;

那有什么办法呢,我们可以将内部函数作为返回值返回,这样外部函数就可以访问inner中的内部变量了,如下:

<script>
        function outer() {
            var scope = "outer";
            function inner() {
                console.log(scope);;
            }
            return inner;//将内部函数返回到外部
        }
        var fn = outer();
        fn();
</script>

这样就可以将内部函数的变量保存到外部,上述代码中的inner就是闭包。

再再举一个栗子:

<script>
        for (var i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i);
            })
        }
</script>

猜猜输出什么0~4?1~5?nonono,最后输出结果是5个5,这是因为js是单线程的,会先执行完循环再执行计时器,等到执行计时器的时候i已经变成了5,所有打印5个5。

但是要怎么样才能依次打印呢,我们可以使用自调用函数,将i作为参数传递,如下:

<script>
        for (var i = 0; i < 5; i++) {
            (function (i) {
                setTimeout(function () {
                    console.log(i);
                })
            })(i)
        }
</script>

这里用了闭包来保存变量i,尽管循环结束后i的值变成了5,但是函数每次执行时i的值被记录了下来,所以依次打印出0,1,2,3,4。

 

二.闭包的优缺点

优点:调用函数内的局部变量,保护函数内变量的安全,防止与其他同名变量形成冲突造成全局污染。

缺点:闭包使用后,会使得引用变量不能被销毁,一直存在内存中,造成内存泄露,我们可以在只用完之后手动为变量赋值为null。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值