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 3console.log( c) //报错

作用域与执行上下文

区别1

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

区别2

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

联系

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

作用域链

理解

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

查找一个变量的查找规则

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

闭包

闭包理解

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

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

用浏览器打开后可以看到

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

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

  function fn1() {var a = 2function fn2() {a++console.log(a)}return fn2}var f = fn1()f() // 3f() // 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 = ibtn.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,它就不会消失。

闭包的缺点

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

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

  1. JavaScript重难点解析2(立即执行函数IIFE,this关键字)

    JavaScript重难点解析2(立即执行函数IIFE,this关键字) 立即执行函数 this关键字 立即执行函数 也叫做匿名函数自调用,可以在定义一段函数之后直接让其执行. ;(function ...

  2. JavaScript重难点解析1(数据类型——var、let、const区别,类型补充,“===”、“typeof”、“instanceof”区别,Symbol数据类型)

    JavaScript重难点解析1(数据类型) var.let.const区别: 类型补充 "==="."typeof"."instanceof&quo ...

  3. JavaScript重难点解析6(Promise)

    JavaScript重难点解析6(Promise 概念 为什么要使用Promise Promise 的状态 Promise 对象的值 Promise工作流程 基本用法 Promise其他方法 asyn ...

  4. JavaScript重难点解析5(对象高级、浏览器内核与事件循环模型(js异步机制))

    JavaScript重难点解析5(对象高级.浏览器内核与事件循环模型(js异步机制) 对象高级 对象创建模式 Object构造函数模式 对象字面量模式 工厂模式 自定义构造函数模式 构造函数+原型的组 ...

  5. JavaScript重难点解析3(原型与原型链、执行上下文与执行上下文栈)

    JavaScript重难点解析3(原型与原型链.执行上下文与执行上下文栈) 原型与原型链 原型(prototype) 显示原型与隐式原型 原型链 instanceof是如何判断 执行上下文与执行上下文 ...

  6. 快速排序的难点_数据结构考研重难点解析:快速排序

    数据结构是计算机专业考研重点内容,大部分院校都是考到了数据结构,其中快速排序是其中的重点难点内容,因此中公考研计算机教研室为大家整理的"数据结构考研重难点解析:快速排序",希望对大 ...

  7. 跨考408计算机学科专业基础综合,考研北京航空航天大学计算机学科专业基础综合(408)重难点解析.doc...

    2010年考研北京航空航天大学计算机学科专业基础综合(408)重难点解析 跨考专业课特别奉献,为广大考研学子加油助力! 1.操作系统 今天我们来解析一下计算统考大纲操作系统部分的知识点.操作系统的研发 ...

  8. 计算机学科专业基础综合简称,2010年考研北京大学计算机学科专业基础综合(408)重难点解析...

    2010年考研北京大学计算机学科专业基础综合(408)重难点解析 考研全程辅导专家 2010年考研北京大学计算机学科专业基础综合(408)重难点解析 跨考专业课特别奉献,为广大考研学子加油助力! 计算 ...

  9. JavaScript闭包详解及案例

    JavaScript闭包详解及案例 一. 变量作用域 函数内部可以使用全局变量 函数外部不可以使用局部变量 当函数执行完毕时,本作用域内的局部变量会被销毁 二. 闭包 闭包:有权访问另一个函数作用域中 ...

最新文章

  1. Wireshark命令行工具tshark使用小记
  2. 皮一皮:这是结婚还是华山论剑...
  3. Java中return结束循环,Java中break、continue、return在for循环中的使用
  4. 【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)
  5. Android性能优化——内存泄漏优化
  6. 计算机科学导论第二章,计算机科学导论第二章.doc
  7. Python开发基础 day5 函数的定义与使用
  8. 现代软件工程 第一章 四则运算的实现--栈实现
  9. 2021年三月中旬推荐阅读文章
  10. XStream将XML转换为JAVA对象快速指南
  11. 有个做微商的兄弟,他是卖品牌运动鞋的,最近很苦恼
  12. 当浙江“十万企业上云“遇见中国软件生态大会
  13. unet服务器向客户端发消息,unet
  14. 利用 Finder 清理Mac旧档案,释放空间
  15. taocat服务器的作用,随笔2_tww
  16. python中output使用_如何在Python中使用subprocess.check_output()?
  17. 3D游戏引擎设计与实现1-15
  18. 华为畅享20plus能更鸿蒙不,甘南收购华为畅享20Plus尾插排线数据线耳机
  19. 深入理解机器学习——概率图模型(Probabilistic Graphical Model):马尔可夫随机场(Markov Random Field,MRF)
  20. COGS 201. [BYVoid S1] 埃雷萨拉斯的宝藏

热门文章

  1. at java.net.url init,java.net 基本测试
  2. “HTTPS”安全在哪里?
  3. Android 中像素px和dp的转化
  4. 编程习题——Maximum Subarray
  5. c# 基本语法(转)
  6. magento导入导出Custom Options, Tier Prices and Grouped Products
  7. android 闪屏页处理_Android应用闪屏页延迟跳转的三种写法
  8. 2018推荐的android手机,外媒推荐:2018年下半年最值得期待的5款安卓手机
  9. linux用户空间寄存器,在Linux用户空间中访问硬件寄存器
  10. (51)Verilog HDL上升沿检测