首页 > 其他分享 >作用域和作用域链的相关知识

作用域和作用域链的相关知识

时间:2023-09-19 21:57:50浏览次数:33  
标签:变量 作用域 引用 知识 num 内存 相关 函数

作用域

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。
作用域分为:

  • 局部作用域
  • 全局作用域

局部作用域

局部作用域分为函数作用域和块作用域。

函数作用域

在函数内部声明的变量只能在函数内被访问,外部无法直接访问。

function foo(){
    const bar = 1;
}

console.log(bar); // ReferenceError: bar is not defined

总结

  • 函数内部声明的变量,在函数外部无法被访问;
  • 函数的参数也是函数内部的局部变量;
  • 不同函数内部声明的变量无法互相访问;
  • 函数执行完毕后,函数内部的变量实际被清空了。

块作用域

在JavaScript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法被访问。

有可能:取决于使用let还是var

for (let i=1; i<=5; i++){
    // i 只能在该代码块中被访问
    console.log(i); // 正常
}

// 超出了 i 的作用域
console.log(i); // 报错
for (var i=1; i<=5; i++){
    // i 能在该代码块中被访问
    console.log(i); // 正常
}

// 超出了 i 的作用域
console.log(i); // 不会报错,因为 i 是使用var声明的

总结

  • let声明的变量会产生块作用域,var不会产生块作用域;
  • const声明的常量也会产生块作用域;
  • 不同代码块之间的变量无法互相访问;
  • 推荐使用let或const。

全局作用域

<script>标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问。

注意

  • 为window对象动态添加的属性默认也是全局的,不推荐!
  • 函数中未使用任何关键字声明的变量为全局变量,不推荐!!
  • 尽可能少的声明全局变量,防止全局变量被污染。

作用域链

作用域链本质上是底层的变量查找机制

  • 在函数被执行时,会优先查找当前函数作用域中查找变量;
  • 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域。

总结

  • 嵌套关系的作用域串联起来形成了作用域链
  • 相同作用域链中按着从小到大的规则查找变量
  • 子作用域能够访问父作用域,父级作用域无法访问子级作用域

垃圾回收机制

内存的生命周期

JS环境中分配的内存,一般有如下生命周期

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
// 为变量分配内存
const num = 10;

// 为对象分配内存
const obj = {
    num: 10
}

// 为函数分配内存
function fn(){
    const num = 10;
    console.log(num);
}

说明

  • 全局变量一般不会回收(关闭页面回收)
  • 一般情况下局部变量的值,不用了,会被自动回收

内存泄漏:程序中分配的内存由于某种原因程序未释放无法释放叫做内存泄漏

算法说明

这一部分介绍JS引擎是如何回收内存的。

复习

堆栈空间分配区别:

  1. 栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
  2. 堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。

下面介绍两种常见的浏览器垃圾回收算法引用计数法标记清除法

引用计数法

IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法

  1. 跟踪记录被引用的次数
  2. 如果被引用了一次,那么就记录次数1,多次引用会累加
  3. 如果减少一个引用就减1
  4. 如果引用次数是0,则释放内存。

引用计数算法是个简单有效的算法,但是现在已经很少使用,因为它存在一个致命的问题:嵌套引用(循环引用)

如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏。

function fn(){
    let o1 = {}
    let o2 = {}
    o1.a = o2
    o2.a = o1
    return '引用计数无法回收'
}
fn()
image-20230919210142437

如上图,函数执行结束后,局部变量都被取消,但是由于对象相互引用,内存无法被回收。

并且,每执行一次函数,就会导致一次内存泄漏。

标记清除法

现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心

  1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
  2. 就是从根部(在S中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收

image-20230919210704609

如图,标记清除法可以解决引用计数法无法解决的相互引用的问题。

闭包

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包 = 内层函数 + 外层函数的变量

function outer(){
    const num = 10;
    function fn(){
        console.log(num);
    }
    fn();
}
outer(); // 10

内函数fn使用了外函数的变量num,形成闭包。

另一个例子:统计函数调用次数,函数调用一次,就加1。

function counter(){
    let num = 0;
    return function(){
        console.log(num++);
    }
}

const add = counter();
add(); // 0
add(); // 1
add(); // 2

闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量。

闭包应用:实现数据的私有。

变量提升

变量提升是JavaScript中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)

console.log(num); // undefined
var num = 10;

在这个案例中,使用var声明的num会存在变量提升的现象。提前输出不会报错,是因为这个变量已经声明了。虽然声明会被提升,但是赋值操作不会被提升,所以num还是undefined

注意

  1. 变量在未声明即被访问时会报语法错误;
  2. 变量在var声明之前即被访问,变量的值为undefined;
  3. let/const声明的变量不存在变量提升;
  4. 变量提升出现在相同作用域当中;
  5. 实际开发中推荐先声明再访问变量

标签:变量,作用域,引用,知识,num,内存,相关,函数
From: https://www.cnblogs.com/feixianxing/p/scope-chain.html

相关文章

  • k8s相关
    https://www.sealyun.com/zh-Hans/docs/lifecycle-management/quick-start/kubernetesinstall#在线安装sealosrunlabring/kubernetes-docker:v1.25.0labring/helm:v3.8.2labring/calico:v3.24.1\--masters172.24.102.131,172.24.101.154,172.24.100.211--debu......
  • 知识付费平台开发技术实践:构建数字学习的未来
    引言知识付费平台的兴起正在塑造着数字学习的未来。本文将介绍一些关键的技术实践,帮助开发者构建强大的知识付费平台,提供出色的数字学习体验。1.选择适当的技术栈在开始知识付费平台的开发之前,首要任务是选择适当的技术栈。这包括后端开发语言、数据库、前端框架等。常用的后端语......
  • 嵌入式三级知识点总结最终章
    181. 操作系统为软件系统提供了多任务运行环境等等而不是板级支持包,BSP运行之前,调试工具不能够用,BSP调试分两步 最小系统和外围设备驱动程序调试。182. RAM访问速度要比ROM快很多。183. U-Boot能够支持多种体系结构的处理器但是每种结构有其自身的版本。184. VXworks(微内核)......
  • 迭代器、生成器、模块和包知识点总结
    第一部分:迭代器 例1. for....in运行机制li=[1,2,3,4]#在列表中取值从第一个取到最后一个结束#foriinli:#print(i)#1,2,3,4i=0whilei<len(li):#索引#print(i)#输出索引0,1,2,3print(li[i])#取列表值i+=1print(i)#i=4的时......
  • 【原创】ospf入门知识三
        很高兴抽取一点时间为大家说下ospf中需要注意的一些基础性知识,大神可以飘过。开始如下:    (一)在一个MA网络中DR和BDR的个数有规定么?    在一个MA多路访问网络中,DR和BDR的个数是有规定的,具体的为:1)DR和BDR有且仅有一个;2)BDR可以没有,但DR必须要有一个;3......
  • 【原创】ospf入门知识二
        在上次写了ospf入门知识一,这次我继续写点关于ospf的几点零散知识,希望对大家有点帮助,也是对自己的一次回顾。    (一)ospf和RIP、EIgrp的宣告路由方式有什么不同?ospf是基于接口进行宣告的,它宣告的是接口路由;Rip宣告的是主网,特殊区域的网段;Eigrp宣告的是VLSM子网......
  • 前端相关字符串
    Unicode是国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。Unicode只是一个符号集,它只规定了每个符号的二进制值,但是符号具体如何存储它并没有规定。因此,Unicode出现了多种存储方式,常见的有UTF-8、UTF-16、UTF-32,它们分别用......
  • Shell的一些零碎知识,包含jq
    HelloWorldshell中拼接两个变量的方法#1var1="Hello"var2="World"result="${var1}${var2}"echo$result#2var1="Hello"var2="World"result="$var1$var2"echo$result#3var1="Hello"var2="......
  • Java语言基础知识全总结
    一.Java的优点1.      跨平台性。一次编译,到处运行。Java编译器会将Java代码编译成能在JVM上直接运行的字节码文件,C++会将源代码编译成可执行的二进制代码文件,所以C++执行速度快2.      纯面向对象。Java所有的代码都必须在类中书写。C++兼具面向对象和面向过程的特......
  • Python变量:创建、类型、命名规则和作用域详解
    变量变量是用于存储数据值的容器。创建变量Python没有用于声明变量的命令。变量在您第一次为其分配值时被创建。示例x=5y="John"print(x)print(y)变量不需要声明为特定类型,并且甚至在设置后可以更改类型。示例x=4#x的类型为intx="Sally"#现在x的......