学习记录-JS作用域和闭包

作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。

细分作用域

  1. 全局作用域:在全局作用域定义的变量可以在任何地方被访问。
  2. 函数作用域: 某一函数拥有的作用域
  3. 块级作用域:在某一区域块能访问的作用域
        var b=10;
            function test(){
                var a=10;
            }
        console.log(b==a)
       //ReferenceError: a is not defined
复制代码

可见,函数外面是不能访问函数内的变量,a所在的作用域与b所在的作用域不同。b处于全局作用域中,a处于函数作用域。

   var b=10
   function test(){
      var a=10;
      console.log(b==a)
   }
  test()//true
复制代码

因为b是全局作用域,所以即使是在函数中也能访问到。

if(ture){
    var a=10
}
console.log(a)//10
复制代码

在块级作用域定义的a变量,怎么能在全局中访问?

  • 这是因为用 var 定义的变量并不具有块级作用域

如果块级作用域无效很容易造成变量的命名冲突。为了解决该问题, es6 之后JavaScript 提供了 let、const 关键字来让变量具有块级作用域。

if(ture){
    let a=10
}
console.log(a)//ReferenceError: a is not defined
复制代码

小提升

先看题目、求输出结果

var arr = [];
for(var i = 0;i < 10;i++){
    arr[i] = function(){
        console.log(i)
    };
}
for(var j = 0;j < 10;j++){
    arr[j]()
}

复制代码

大家觉得输出得是0-9,还是10个10呢?
我们先分析之后再求结果。

先引入异步的概念,异步:

指的是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

这里面的函数执行就是异步操作。
因为我们得输出i。所以我们先看看哪里是执行输出i的代码
在第一个for循环中,函数作用域中是没有i变量的,遵循作用域的原则,向上寻找变量 i,当这个函数需要执行的时候,因为是异步操作此时的i已经for循环完了值为10.所以当执行函数时输出的i=10.
结果就是10个10.

显然我们希望得到的是0-9,那我们该如何改进呢?

var arr = [];
for(let i = 0;i < 10;i++){
    arr[i] = function(){
        console.log(i)
    };
}
for(var j = 0;j < 10;j++){
    arr[j]()
}

复制代码

把var变成let就行了,因为let定义的变量会独立创建块级作用域,每次let都会将此时i定义到此时块级作用域,所以输出结果为0-9.

  1. 即然上面的函数是异步操作,那我们可以让它立即执行。
var arr = [];
for(let i = 0;i < 10;i++){
    arr[i] = (function(n){
        console.log(n)
    })(i)
}


//0-9
复制代码

闭包

什么叫闭包

闭包就是能够读取其他函数内部变量的函数.
???
简单的理解
当一个函数被保存到外部是,产生闭包。

客官,您点的小李子来了:
如果老师布置作业,需要两个人一起完成,其中你需要写个功能,计算运行某个函数的次数。
大部分人都能想到,下面的方法:

var count = 0
function test(){
    count ++
}
test()
复制代码

显而易见,当test函数执行,count就加一。不幸的是,你搭伴的同学也定义了个变量count,变量名冲突了,这时候该怎么办?有没有一个方法能保留住函数内部的某个变量。

  • 看代码
function add(){
    var num=0
 function a(){
     num++
     console.log(num)
 }
 return a
}

var result =add()
 result()//1
复制代码

我们先解析下改代码:

  1. 先调用 add函数,定义变量result 保存其返回值。
  2. add函数 的返回值也是一个函数对象:a函数。所以我们才可以在外部调用 a函数。
  3. 执行a函数,num++,得先找到num,找找找,a函数作用域没有,往上找,add函数中找到了。但add函数已经执行了呀?当一个函数执行完后,它本身作用域也不复存在了,那为什么还能得到num?

当一函数执行结束后,回收机制会将其回收。

但add函数执行后,回收机制刚想将其回收,函数会对回收机制说,大哥虽然我是执行完了,但result有访问权限,并且调用了我返回的函数a,我也不知道什么时候result执行a函数,所以我并没有执行结束。所以仍然能在add函数中找到num。

就好比,我之前很有钱,并将一部分钱买了基金。有一天我赌博把钱全输了,那你能说我破产了嘛?显然没有,我还有一些基金,虽然我不知道这基金到底能赚多少,但我确实没破产。

其实这就是闭包的概念,a函数属于add函数作用域,但却保存到了全局中。

闭包的缺陷

1. 内存泄漏:内存可用空间变小了,引擎可用空间不够用

2. 浏览器负荷加重,需要更多的资源去执行

闭包的缺陷很好理解,上面例子中,因为回收机制不能将函数add回收,所以add函数就会长时间占用内存可用空间,导致引擎可用空间不够用,浏览器负荷加重,需要更多的资源去执行。

闭包的优点

存在即合理,即然有闭包,那就有它存在的好处:

1. 实现共有变量

上面例子就将num变成了共有变量

**2. 缓存 **

先看代码

function book(){
    var bo = '灌篮高手'
    var a= new Object
    a.readbook=function read(){
        console.log("我在看 "+bo)
        }
    a.buybook=function buy(){
        bo='三国演义'
        console.log('我买了'+bo)
    }
    return a
}
var me=book()
me.readbook()
me.buybook()
me.readbook()
//我在看 灌篮高手
//我买了三国演义
//我在看 三国演义
复制代码

将bo进行了缓存

3. 实现封装

var call=function(){
    var name ='范佐'
    var me=function(){
    console.log('我的名字是:'+name)
    }
    return me
}
var callname= call()
callname()
复制代码

闭包使得方法实例能够正常调用到方法外部的变量,当call函数执行后,call返回的函数实例
callname仍能访问到上一级函数中的name。相当于callname调用了call里面的变量,即callname于name绑定在一起了。闭包将外部变量和方法实例封装。

好了,本次的学习分享就到这了?。若文章有错,还请各位点名批评!!!
下次见?‍♂️

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享