目录
1.作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个范围便不能被访问
作用域分为:局部作用域
全局作用域
1.1局部作用域
分为:函数作用域和块级作用域
1.函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
<script>
function getSum(){
// 函数内部是函数作用域 属于局部变量
const num = 10
}
console.log(num) // 此处报错 函数外部不能使用局部作用域变量
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量 (比如说小括号里有两个形参)
- 不同函数内部声明的变量无法互相访问 (它自己内部的只能由自己访问)
- 函数执行完毕后,函数内部的变量实际被清空了 (垃圾回收机制)
2. 块级作用域
在JS中使用 { } 包裹的代码成为代码块,其内部声明的变量【有可能】无法访问
<script>
for (let i = 0; i <= 3; i++) {
// 块作用域
// i只能在该代码块中被访问
console.log(i) //正常
}
// 超出了 i 的作用域
console.log(i) //报错
// for (let i = 0; i <= 3; i++) {
// // 块作用域
// // i只能在该代码块中被访问
// console.log(i) //正常
// }
for (var i = 0; i <= 3; i++) {
// 块作用域
console.log(i) //正常
}
// 超出了 i 的作用域
console.log(i) // var没有块级作用域 它声明的变量可以访问局部作用域
</script>
控制台显示:
总结:
- let 声明的变量会产生块作用域, var 不会产生块作用域
- const 声明的常量也会产生块级作用域
- 不同代码之间的变量无法互相访问
- 推荐使用 let 或 const
1.2全局作用域
1.3 作用域链
作用域链本质上 是 底层的 变量查找机制
- 在函数被执行时,会优先查找当前函数作用域直到全局作用域
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
如图,先调用g() ,打印2;再调用f() ,打印1;
如果没有“a = 2”,它会逐级查找父级作用域,找到“let a = 1” ,打印 1;
如果没有“a = 2”和“let a = 1”, 它会逐级查找父级作用域(此时到了全局作用域“let a = 1”),打印 1。
总结:
- 嵌套关系的作用域串联起来形成了作用域
- 相同作用域中按从小到大的规则查找变量
- 子作用域能够反问父作用域,父级作用域无法访问子级作用域
1.4 JS垃圾回收机制
1. 什么是垃圾回收机制?
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做
内存泄漏
2.内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
1.
内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
2.
内存使用:即读写内存,也就是使用变量、函数等
3.
内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
4.
说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
扩展-JS垃圾回收机制-算法说明
堆栈空间分配区别:
1. 栈(操作系统): 由
操作系统自动分配释放
函数的参数值、局部变量等,基本数据类型放到栈里面。
2. 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由
垃圾回收机制
回收。
复杂数据类型
放到堆里面。
下面介绍两种常见的浏览器
垃圾回收算法
:
引用计数法
和
标记清除法
引用计数
IE采用的引用计数算法, 定义“
内存不再使用
”,就是看一个
对象
是否有指向它的引用,没有引用了就回收对象
算法:
1.
跟踪记录被
引用的次数
2.
如果被引用了一次,那么就记录次数1,多次引用会
累加 ++
3.
如果减少一个引用就
减1 --
4.
如果引用次数是
0
,则释放内存
左图,数组arr 是一个复杂数据类型,之后把 null赋值给arr ,arr成为一个基本数据类型,不再指向[1,2,3,4] ,[1,2,3,4]也不再被引用,引用次数变为0,垃圾回收机制自动回收
右图同理,person对象是一个复杂数据类型,把person的值赋给 p,之后再给person 重新赋值1,这时候 p就不指向person了,p为空。
引用计数
但它却存在一个致命的问题:
嵌套引用
(循环引用)
如果两个对象
相互引用
,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
l
标记清除法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于
标记清除算法
的某些改进算法,总体思想都是一致的。
核心:
1.
标记清除算法将“不再使用的对象”定义为“
无法达到的对象
”。
2.
就是从
根部
(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从
根部到达
的对象,都是还
需要使用
的。(核心思路)
3.
那些
无法
由根部出发触及到的
对象被标记
为不再使用,稍后进行
回收
1.5 闭包
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域简单理解:
闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:
闭包应用:实现数据的私有
比如,我们要做个统计函数调用次数,函数调用一次,就++
2.1 函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
总结: 1. 函数提升能够使函数的声明调用更灵活 2. 函数表达式不存在提升的现象 3. 函数提升出现在相同作用域当中
2.2 函数参数
函数参数的使用细节,能够提升函数应用的灵活度
动态参数arguments
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
总结: 1. arguments 是一个伪数组,只存在于函数中
2. arguments 的作用是动态获取函数的实参
3. 可以通过for循环依次得到传递过来的实参
总结:
1. 当不确定传递多少个实参的时候,我们怎么办?
Ø
arguments 动态参数
2. arguments是什么?
Ø
伪数组 Ø
它只存在函数中
剩余参数 ...
剩余参数允许我们将一个不定数量的参数表示为一个数组
1.
... 是语法符号,置于
最末函数形参之前,用于
获取多余的实参 2. 借助 ... 获取的剩余实参,是个
真数组
开发中,还是提倡多使用 剩余参数。
1. 剩余参数主要的使用场景是?
Ø 用于获取多余的实参
2. 剩余参数和动态参数区别是什么?开发中提倡使用哪一个?
Ø
动态参数是伪数组 Ø
剩余参数是真数组 Ø 开发中使用剩余参数想必也是极好的
展开运算符
展开运算符(…):将一个数组进行展开
典型运用场景: 求数组最大值(最小值)、合并数组等
展开运算符 or 剩余参数
剩余参数:
函数参数使用,得到
真数组
展开运算符:
数组中使用,
数组展开
1. 展开运算符主要的作用是?
Ø 可以把数组展开,可以利用求数组最大值以及合并数组等操作
2. 展开运算符和剩余参数有什么区别?
Ø 展开运算符主要是 数组展开 Ø 剩余参数 在函数内部使用
箭头函数(重要)
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来
需要匿名函数的地方
1.基本语法
语法一:基本写法
语法二:只有一个参数可以省略小括号
语法三:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
语法四:加括号的函数体返回对象字面量表达式
2. 箭头函数参数
1. 普通函数有arguments 动态参数
2.
箭头函数没有 arguments 动态参数,但是有
剩余参数 ..args
3. 箭头函数 this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值,非常令人讨厌。箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数
3.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。 基本语法: 1. 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量2. 变量的顺序对应数组单元值的位置依次进行赋值操作
JS前面必须加分号情况:
-
['小米']
是一个包含一个元素的数组。 -
在解构赋值语法中,
[a = '手机', b = '华为']
表示从数组中解构出两个元素,并分别赋值给变量a
和b
。如果数组中的元素数量不足,那么变量将采用默认值。 -
对于变量
a
,它被赋值为数组中的第一个元素'小米'
。 -
对于变量
b
,由于数组中没有第二个元素,所以它采用了默认值'华为'
。
总结:
- 标记清除法的核心思路:从根部扫描对象,能查找到的就是使用的,查找不到的就要回收。
- 怎么理解闭包? 闭包 = 内层函数 + 外层函数的变量
- 闭包的作用:①封闭数据,实现数据私有,外部也可以访问函数内部的变量。②闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
- 闭包可能引起的问题:内存泄漏
- 用关键字 var 声明变量会有变量提升。
- 变量提升的流程:1.先把var 变量提升到当前作用域于最前面 2.只提升变量声明, 不提升变量赋值 3.然后依次执行代码(我们不建议使用var声明变量)
-
1. arguments 是一个伪数组,只存在于函数中2. arguments 的作用是动态获取函数的实参3. 可以通过for循环依次得到传递过来的实参
-
1. 剩余参数主要的使用场景是?Ø 用于获取多余的实参2. 剩余参数和动态参数区别是什么?开发中提倡使用哪一个?Ø 动态参数是伪数组 Ø 剩余参数是真数组 Ø 开发中使用剩余参数想必也是极好的
-
1. 展开运算符主要的作用是?Ø 可以把数组展开,可以利用求数组最大值以及合并数组等操作2. 展开运算符和剩余参数有什么区别?Ø 展开运算符主要是 数组展开 Ø 剩余参数 在函数内部使用
箭头函数:1. 箭头函数属于 表达式函数,因此 不存在函数提升2. 箭头函数 只有一个参数时可以省略圆括号()3. 箭头函数函数体 只有一行代码时可以省略花括号{},并自动 作为返回值被返回4. 加括号的函数体 返回对象字面量表达式1. 箭头函数里面有this吗?Ø 箭头函数不会创建自己的this,它只会从自己的 作用域链的上一层沿用this2. DOM事件回调函数推荐使用箭头函数吗?Ø 不太推荐,特别是需要用到this的时候Ø 事件回调函数使用箭头函数时,this 为全局的window
1. 数组解构赋值的作用是什么?Ø 是将数组的单元值快速批量赋值给一系列变量的简洁语法2. Js 前面有两哪种情况需要加分号的?Ø 立即执行函数 Ø 数组解构
1. 变量的数量大于单元值数量时,多余的变量将被赋值为?Ø undefined2. 变量的数量小于单元值数量时,可以通过什么剩余获取所有的值?Ø 剩余参数... 获取剩余单元值,但只能置于 最末位