JS-进阶-作用域-闭包

JS学习笔记12

进阶部分

作用域

局部作用域

函数作用域

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

块作用域

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}

// 超出了 age 的作用域
console.log(age) // 报错

let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常
}

// 超出了 age 的作用域
console.log(str); // 报错

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

// 超出了 t 的作用域
console.log(t); // 报错
</script>
  1. let 声明的变量会产生块作用域,var 不会产生块作用域
  2. const 声明的常量也会产生块作用域
  3. 不同代码块之间的变量无法互相访问
  4. 推荐使用 letconst

全局作用域

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

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

作用域链

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

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

总结:

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

垃圾回收机制(GC)

内存的生命周期

  1. 内存分配
  2. 内存使用
  3. 内存回收
  • 全局变量一般不会回收(页面关闭)
  • 一般局部变量的值,不用了,会被自动回收

算法说明

  1. 栈(操作系统):由操作系统自动分配释放函数的参数值,局部变量等
  2. 堆(操作系统):一般由程序员分配释放,程序员不释放,由垃圾回收机制释放

引用计数法

1.跟踪记录被引用的次数

2.如果被引用了一次,那么就记录次数1,多次引用会累加

3.如果减少一个引用就减1

4.如果引用次数是0,则释放内存

问题:如果两个对象存在相互引用(嵌套引用),那么生命周期结束后将无法被回收

标记清除法

  1. 将不再使用的对象定义为无法达到的对象

  2. 从根部扫描对象,能查到的就是使用的,查不到的就要回收

==闭包==

闭包=内层函数+外层函数的变量

有点像私有数据成员通过成员函数去访问的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<body>
<script>
// 1. 闭包 : 内层函数 + 外层函数变量
// function outer() {
// const a = 1
// function f() {
// console.log(a)
// }
// f()
// }
// outer()

// 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
// let count = 1
// function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }

// 3. 闭包的写法 统计函数的调用次数
function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`)
}
return fn //返回了内层函数
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
re()
re()
// const fn = function() { } 函数表达式
// 4. 闭包存在的问题: 可能会造成内存泄漏
</script>
</body>

2.闭包的作用?

  • 封闭数据,实现数据私有,外部也可以访问函数内部的变量
  • 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来

3.闭包可能引起的问题?

  • 内存泄漏

(根据标记清除法的机制,外层函数的变量可能被继续使用,因此可能不被清除)

变量提升

它允许变量在声明之前被访问(var)

1
2
3
4
5
6
7
<script>
// 访问变量 str
console.log(str + 'world!');

// 声明变量 str
var str = 'hello ';
</script>

==var使str声明放在了全局作用域的最上面,但是只提升声明不赋值==

总结:

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

JS-进阶-作用域-闭包
https://wjcbolg.cn/2023/04/29/JS学习笔记12/
作者
JasonWang
发布于
2023年4月29日
许可协议
BY-JW