1. 作用域
分类:
- 局部作用域
- 函数作用域: 函数执行完毕后,函数内部的变量被清空了
- 块作用域: 使用
{}
包裹的代码称为代码块,代码块内部声明的变量外部无法访问。但var声明的变量不会产生块作用,如下所示。控制台输出1, 2, 3, 4
,如果使用let则不会输出4,会报错
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
for (var i = 1; i <= 3; i++) {
console.log(i)
}
console.log(i)
</script>
</body>
</html>
- 全局作用域(全局变量):
- script标签或js文件最外层声明的变量
- 为window对象动态添加的属性
- 函数中未使用任何关键字声明的变量
作用域链的变量查找机制: 优先在当前作用域查找变量,如果查不到,则逐级查找父级作用域直到全局作用域
2. 垃圾回收机制
JS的垃圾回收(Garbage Collection/GC)是自动完成的,JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存(全局变量页面关闭才回收)
栈和堆:
- 栈:存放基础数据类型,由JS自动分配和回收
- 堆: 存放复杂数据类型,如果程序员不回收,则由JS自动回收
JS垃圾回收机制的算法:
- 引用计数法(不再使用): 一个对象被引用一次,引用数就加1,多次引用引用数会累加,减少一次引用引用数就减1,如果引用数为0,就会被回收。但循环引用的对象不会被回收
- 标记清除法: 从根部(全局对象)出发,定时扫描内存中的对象。不能从根部到达的对象,就会被回收
3. 闭包
内层函数和外层函数的变量一起构成闭包
应用:使用闭包函数,创建隔离作用域,避免全局变量污染。如下所示,使用num来对函数的调用次数进行计数(全局作用域能够访问,所以可能会引起内存泄漏),但不能在全局作用域对num进行修改
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function count() {
// 闭包开始
let num = 0 // 外层函数的变量
function count_main() { // 内层函数
num++ // 内层函数使用外层函数的变量
console.log(`函数被调用了${num}次`)
}
// 闭包结束
return count_main
}
const count_main = count()
count_main()
count_main()
</script>
</body>
</html>
4. 变量提升和函数提升
- 变量提升: var声明的变量存在变量提升。var声明变量且同时赋值即使在当前作用域的最后一行,JS执行的时候也会将变量的声明放到当前作用域的最前面,但变量的赋值还是在最后一行。示例如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 如果没有声明num会直接报错
console.log(num) // undefined
var num = 10
</script>
</body>
</html>