js中作用域及闭包的概念与基础应用

98 阅读4分钟

在js中,闭包是一个基础且不可不了解的要点 在此之前,我们需要了解一些前置知识,那就是作用域和作用域链 作用域就是指在js中在程序中定义变量的区域,该位置决定了变量的生命周期,也就是说作用域就是变量和函数可访问的范围,控制着变量和函数的可见性与生命周期 我们都知道,在js引擎中var有着变量提升与预编译这一特性

一、作用域和作用域链

变量提升

console.log(a)  //输出undefined  原因:在预编译阶段已经读取到a的存在,但仍未赋值,所以输出undefined
var a=1

在js引擎中执行阶段实际为

var a
console.log(a)
a=1

了解完预编译之后,接着我们再看两段代码,观察其中有多少作用域,与运行后会输出什么

var a =2
function fn(){
   var b=2
   console.log(a)
}
fn()   // 输出结果为2
var a =2
function fn(){
   var b=2
}
console.log(b) //报错,无输出结果

观察后我们可以得知,此段代码中有着全局作用域与一段fn函数作用域,但是观察代码后你可能会好奇,为何在全局作用域中用var定义的a,却可以在函数作用域中被访问,而函数作用域中定义的b却不能在全局作用域中被访问呢,这时就要引用出js中作用域链的机制了

1.作用域链的本质其实就是底层的查找机制

  • 在函数被执行时,会优先查找当前函数作用域中的变量
  • 如果当前作用域查找不到则会逐级查找父级作用域直到全局作用域
//全局作用域
let a =1
let b =2
function fn(){       //外部函数作用域
let a =1
function fn2(){                   //内部函数作用域
a=2
console.log(a)
  }
 g()                  //调用g
}
f()                    //调用f
最终输出结果为2

总结: 1.嵌套关系的作用域串联起来形成了作用域链 2.内部函数作用域能访问外部函数作用域,而外部函数作用域无法访问内部函数作用域

二、闭包(closur)

在了解了函数作用域和作用域的相关知识后,我们来看看闭包究竟是怎么一回事 简单来说,就是内层函数访问了外层函数的变量,这时便产生了一个闭包

例如

function outer(){                 
let a=1
function inner(){
   console.log(a)
  }
  return inner
}
let fun=outer()
fun()  //输出1

此时就可以通过闭包实现在外部访问函数outer()内的变量,通过闭包绕过了在作用域链中,外部函数作用域无法访问内部函数作用域的限制

闭包的作用

function outer(){
   let a=1       //根据函数作用域链的知识可知,函数outer()外部无法直接访问变量a,此时我们可以使用闭包来访问
   function inner(){
    console.log(a)
   }
   return inner    //返回函数inner()函数体
}

let fn =outer()   //使用fn接受inner()的函数体
fn()          //此时通过fn()可实现外部调用outer()内部的a的变量值

了解了闭包是什么,那让我们看看几个实际案例来看看闭包都有哪些实际用处

通过闭包,我们可以实现了解函数的被调用次数

在不用闭包的情况下,我们要了解函数的被调用次数,此时需要例如下代码

let i=0
function fn(){
    i++
    console.log(i)
}
fn()  

但是,计数所用的i是一个全局变量,意味着在代码量较大时,很容易被错误地修改 此时我们可以通过使用闭包使得计数变量为函数内变量,来实现数据的私有

function fn(){
    let i=0
    function count(){
         i++
        console.log(i) 
    }
    return count
}
fn()
fn()
fn()         //调用三次



let count_num=fn()
count_num()    //输出3

闭包的缺陷:根据js中的垃圾回收机制,我们知道,在一个函数被调用执行完毕之后,为了清理空间,函数内部的变量会被回收,然而如以下代码所示

function fn(){
    let i=0
    function count(){
         i++
        console.log(i) 
    }
    return count
}
let count_num=fn()

let count_num=fn()在全局作用域中,所以count_num()在代码执行结束前不会被回收机制回收,而count_num指向count(),count()内又调用了变量i,所以i会保存在闭包中而不被回收销毁,从而导致本应被回收的函数局部变量i不被回收,导致内存泄漏