JavaScript重难点解析4(作用域与作用域链、闭包详解)

作用域与作用域链

作用域

就是一块"地盘", 一个代码段所在的区域
它是静态的(相对于上下文对象), 在编写代码时就确定了
分类

  • 全局作用域
  • 函数作用域
  • 没有块作用域(ES6有了)

可以隔离变量,不同作用域下同名变量不会有冲突。

 var a = 10 //全局作用域
 if(true) {
  var b = 3 //全局作用域(没有块)
  let c = 3 //ES6块作用域
}
function fn(x) {
  var a = 100 //函数作用域
 }
 console.log( a, b) // 10 3
 console.log( c) //报错

作用域与执行上下文

区别1

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

区别2

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

联系

  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域

作用域链

理解

  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  • 查找变量时就是沿着作用域链来查找的

查找一个变量的查找规则

  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

闭包

闭包理解

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包,闭包是嵌套的内部函数,产生闭包需要两个条件:函数嵌套,内部函数引用了外部函数的数据(变量/函数)。
我们定义这样一段代码:

  function fn1 () {
    var a = 2
    var b = 'abc'
    function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
      console.log(a)
    }
    fn2()
  }
  fn1()

用浏览器打开后可以看到
闭包
简单来说,当JS代码中发函数嵌套调用时,内函数会有一个Closure对象来保存外函数作用域的变量。
理解了闭包是什么之后我们看两个例子:

将函数作为另一个函数的返回值

  function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f() // 3
  f() // 4

按照以往的理解局部在函数执行完后会被清除所以会输出3,3,但为什么会输出3,4那?
在代码执行到var f = fn1()时,函数fn1()执行时发生函数嵌套,内函数Closure对象中就保存了引用的外部变量a,它被当作返回值返回,此时f指向的就是包含了变量a的函数fn2(此时的a可以当作fn2的一个局部变量),由于f指向fn2的指针始终未断,所以a的值一直被保留。
闭包

将函数作为实参传递给另一个函数调用

  function showDelay(msg, time) {
    setTimeout(function () {
      alert(msg)
    }, time)
  }
  showDelay('atguigu', 2000)

这个相对好理解一些,showDelay函数与setTimeout里面的函数发生了嵌套,并且内部的函数运用了外函数的msg变量,所以发生了闭包。

循环遍历监听问题

这个问题学过js的同学应该非常熟悉,先看代码:

for (var i = 0,length=btns.length; i < length; i++) {
  var btn = btns[i]
  btn.onclick = function () {
    alert('第'+(i+1)+'个')
  }
}

循环遍历监听
这段代码执行后无论点击那一个按钮都会只打印4,原因很简单,当我们点击按钮时会触发onclick函数内容,但因为js中DOM操作是异步执行的,当这段函数执行时全局函数中的for循环已经执行完成,i的值已经为4,所以无论点击那一个按钮都打印4。
解决方法:
1.ES6

for (var i = 0,length=btns.length; i < length; i++) {
  let btn = btns[i]  //let支持块作用域
  btn.onclick = function () {
    alert('第'+(i+1)+'个')
  }
}

2.添加属性

for (var i = 0,length=btns.length; i < length; i++) {
 var btn = btns[i]
 //将btn所对应的下标保存在btn上
 btn.index = i
 btn.onclick = function () {
   alert('第'+(this.index+1)+'个')
 }
}

3.闭包

  for (var i = 0,length=btns.length; i < length; i++) {
   (function (j) {
     var btn = btns[j]
     btn.onclick = function () {
       alert('第'+(j+1)+'个')
     }
   })(i)
 }

针对闭包,我们将代码进行改写:

  for (var i = 0,length=btns.length; i < length; i++) {
   //内存中不存在a,b,这里为了便于理解闭包把立即执行函数拆解一下
   function a(j) { 
  	  function b(){
  	          alert('第'+(j+1)+'个')
  	  }
     var btn = btns[j]
     btn.onclick = b
   }
   a(i)
 }

由于变量提升的原因函数定义的语句会在循环之前执行,循环中执行的只有a(i),很明显函数a与函数b发生了闭包,b函数作用域中存在Closure对象保存j的值。所以尽管btn.onclick是异步调用,但由于指向函数b的指针没有丢失,b函数对象一直存在,Closure对象保存j的值也一直存在,所以可以正确弹出i的值。

闭包的生命周期与运用

闭包的生命周期

闭包产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
闭包死亡: 在嵌套的内部函数成为垃圾对象时

闭包的作用

使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
让函数外部可以操作(读写)到函数内部的数据(变量/函数)

运用
闭包主要运用在自定义JS模块中,可以看我们之前的模块化代码

//module2.js
(function () {
	let msg =  'module2';
   function foo(){
   	console.log(msg)
   }
   window.module2 = {foo}
})()

在使用时:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>02_namespace模式</title>
</head>
<body>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
   module2.foo() //打印‘module2’。
</script>
</body>
</html>

msg为module2.js中立即执行函数的私有变量,只允许外界查询,但由于它属于函数作用域,正常情况在函数执行完成后就会销毁,我们可以根据闭包的特性,通过内函数foo()来阅读这个变量,并将函数向外公布。这样msg就会存在foo()的Closure中,只要有指针指向foo,它就不会消失。

闭包的缺点

函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长,容易造成内存泄露。
内存溢出:程序运行需要的内存超过剩余内存就会抛出内存溢出错误。
内存泄露:占用的内存没有及时释放。太多的内存泄露会导致内存溢出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值