学习目标:
- 掌握作用域
学习内容:
- 作用域
- 局部作用域
- 全局作用域
- 作用域链
- JS垃圾回收机制
- 拓展-JS垃圾回收机制-算法说明
- 闭包
- 变量提升
作用域:
作用域规定了变量能够被访问的"范围",离开了这个"范围"变量便不能被访问。
作用域分为:
- 局部作用域
- 全局作用域
局部作用域:
局部作用域分为函数作用域和块作用域。
- 函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script>
function getSum() {
//函数内部是函数作用域 属于局部变量
const num = 10
}
console.log(num) //此处报错,函数外部不能使用局部作用域变量
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问。
- 函数的参数也是函数内部的局部变量。
- 不同函数内部声明的变量无法互相访问。
- 函数执行完毕后,函数内部的变量实际被清空了。
- 块级作用域:
在JavaScript中使用{ }
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能
】无法被访问。
<script>
// for (var i = 1; i <= 3; i++) {
// console.log(i)
// }
// console.log(i)
// for (let i = 1; i <= 3; i++) {
// // 块作用域
// console.log(i)
// }
// for (let i = 1; i <= 3; i++) {
// // 块作用域
// console.log(i)
// }
if (true) {
let i = 10
}
console.log(i) //报错,外部无法被访问到
</script>
总结:
let
声明的变量会产生块级作用域,var
不会产生块级作用域。const
声明的常量也会产生块作用域。- 不同代码之间的变量无法互相访问。
- 推荐使用
let
或const
。
- 小结:
1.局部作用域分为哪两种?
局部作用域 | 说明 |
---|---|
函数作用域 | 函数内部 |
块级作用域 | { } |
2.局部作用域声明的变量外部能使用吗?
不能
全局作用域:
<script>标签
和.js文件
的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问。
<script>
//全局作用域
//全局作用域下声明了num变量
const num = 10
function fn() {
//函数内部可以使用全局作用域的变量
console.log(num)
}
//此处全局作用域
</script>
注意:
- 为window对象动态添加的属性默认也是全局的,不推荐!!!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!!
- 尽可能少的声明全局变量,防止全局变量被污染。
- 小结:
1.全局作用域有哪些?
<script>标签内部 | - |
---|---|
.js文件 | - |
2.全局作用域声明的变量其他作用域能使用吗?
相当能
JavaScript中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链:
作用域本质上是底层的变量查找机制
。
在函数被执行时,会优先查找当前
函数作用域中查找变量。
如果当前作用域查找不到则会依次逐级查找父级作用域
直到全局作用域。
<script>
//全局作用域
let a = 1
let b = 2
//局部作用域
function f() {
let a = 1
//局部作用域
function g() {
a = 2
console.log(a)
}
g() //调用g
}
f() //调用f
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链。
- 相同作用域链中按着
从小到大
的规则查找变量。 - 子作用域能够访问父作用域,父级作用域无法访问子级作用域。
- 小结:
1.作用域链本质是什么?
作用域本质上是底层的变量查找机制。
2.作用域查找的规则是什么?
会优先查找当前函数作用域中查找变量。
查找不到则会依次逐级查找父级作用域直到全局作用域。
JS垃圾回收机制:
垃圾回收机制简称GC。
JS中内存
的分配和回收都是自动完成
的,内存在不使用的时候会被垃圾回收器
自动回收。
- 内存的生命周期:
JS环境中分配的内存,一般有如下生命周期
:
内存分配
:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。内存使用
:即读写内存,也就是使用变量、函数等。内存回收
:使用完毕,由垃圾回收器
自动回收不再使用的内存。
- 说明:
全局变量一般不会回收(关闭页面回收)。
一般情况下局部变量的值
,不用了,会被自动回收
掉。
- 内存泄漏:程序中分配的
内存
由于某种原因程序未释放
或无法释放
叫做内存泄漏
。
<script>
for (let i = 1; i <= 3; i++) {
}
let num = 10
function fn() {
const str = 'andy'
// str = 'lily'
console.log(str)
}
fn()
fn()
fn()
</script>
- 小结:
1.什么是垃圾回收机制?
简称GC。
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
2.什么是内存泄漏?
不再用到的内存,没有及时释放,就叫做内存泄漏。
3.内存的生命周期是什么样的?
内存分配、内存使用、内存回收。
全局变量一般不会回收;一般情况下局部变量的值,不用了,会被自动回收掉。
拓展-JS垃圾回收机制-算法说明:
堆栈空间分配区别:
- 栈(操作系统):由
操作系统自动分配释放
函数的参数值、局部变量等,基本数据类型
放到栈里面。 - 堆(操作系统):一般由程序员分配释放,若程序员不释放,由
垃圾回收机制
回收。复杂数据类型
放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法
:引用计数法
和标记清除法
。
- 引用计数:
IE采用的引用计数算法,定义“内存不再使用
”,就是看一个对象
是否有指向它的引用,没有引用了就回收对象。
算法:
- 跟踪记录被
引用的次数
。 - 如果被引用了一次,那么就记录次数1,多次引用会
累加 ++
。 - 如果减少一个引用就
减1 --
。 - 如果引用次数是
0
,则释放内存。
由上面可以看出,引用计数算法是个简单有效的算法。
但它却存在一个致命的问题:嵌套引用
(循环引用)。
如果两个对象相互引用
,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏。
因为他们的引用次数永不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄漏。
- 标记清除法:
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法
的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将“不再使用的对象”定义为“
无法达到的对象
”。 - 就是从
根部
(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达
的对象,都是还需要使用
的。 - 那些
无法
由根部出发触及到的对象被标记
为不再使用,稍后进行回收
。
根部已经访问不到,所以自动清除。
- 小结:
1.标记清除法核心思路是什么?
从根部扫描对象,能查找到的就是使用的,查找不到的就要回收。
闭包:
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。
简单理解:闭包 = 内层函数 + 外层函数的变量
。
- 闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量。
<script>
// //简单的写法
// function outer() {
// let a = 10
// function fn() {
// console.log(a)
// }
// fn()
// }
// outer()
//常见的闭包的形式 外部可以访问使用 函数内部的变量
function outer() {
let a = 10
function fn() {
console.log(a)
}
return fn
}
// outer() === fn === function fn() {}
// const fun = function fn(){}
// const fun = outer()
//常见的写法2
// function outer() {
// let a = 100
// return function () {
// console.log(a)
// }
// }
// const fun = outer()
// fun() // 调用函数
//外面要使用这个10
</script>
- 闭包应用:实现数据的私有。
<script>
//闭包的应用
//普通形式 统计函数调用的次数
// let i = 0
// function fn() {
// i++
// console.log(`函数被调用了${i}次`)
// }
//因为i是全局变量,容易被修改
//闭包形式 统计函数调用的次数
function count() {
let i = 0
function fn() {
i++
console.log(`函数被调用了${i}次`)
}
return fn
}
const fun = count()
</script>
- 小结:
1.怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量。
2.闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量。
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。
3.闭包也可能引起的问题?
内存泄漏。
变量提升:
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)。
- 注意:
- 变量在未声明即被访问时会报语法错误。
- 变量在
var声明之前
即被访问,变量的值为undefined
。 let/const
声明的变量不存在变量提升。- 变量提升出现在相同作用域当中。
实际开发中推荐先声明再访问变量
。
<script>
// 1. 把所有var声明的变量提升到 当前作用域的最前面
// 2. 只提升声明, 不提升赋值
// var num
// console.log(num + '件')
// num = 10
// console.log(num)
function fn() {
console.log(num)
var num = 10
}
fn()
</script>
- 说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域,用let 或者 const声明变量,让代码写法更加规范和人性化。
- 小结:
1.用哪个关键字声明变量会有变量提升?
var
2.变量提升是什么流程?
先把var变量提升到当前作用域于最前面。
只提升变量,不提升变量赋值。
然后依次执行代码。
我们不建议
使用var
声明变量。