1、作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”不能被访问
作用域分为:局部作用域和全局作用域
1、局部作用域
局部作用域分为函数作用域和块作用域
1、函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
函数内部声明的变量,在函数外部无法被访问
函数的参数也是函数内部的局部变量
不同函数内部声明的变量无法互相访问
函数执行完毕后,函数内部的变量实际被清空了
2、块作用域
在JavaScript中使用{ }包裹的代码块,代码块内部声明的变量外部将无法被访问
1、let声明的变量会产生块作用域,var不会产生块作用域
2、const声明的变量也会产生块作用域
3、不同的代码块之间的变量无法互相访问
4、推荐使用let或const
2、全局作用域
<script>标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其他作用域都可以被访问
注意:
1、为window对象动态添加的属性默认也是全局的,不推荐
2、函数中未使用任何关键字声明的变量为全局变量,不推荐
3、尽可能少的声明全局变量,防止全局变量被污染
3、作用域链
作用域链本质上是底层的变量查找机制
1、在函数被执行时,会优先查找当前函数作用域中查找变量
2、如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
嵌套关系的作用域串联起来形成了作用域链,相同作用域链中按着从小到大的规则查找变量,子作用域能够访问父作用域,父级作用域无法访问子级作用域
4、垃圾回收机制
垃圾回收机制(Garbage Collection)简称GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
JS环境中分配的内存,一般有如下生命周期:
1、内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
2、内存使用:即读写内存,也就是使用变量、函数等
3、内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
说明:
全局变量一般不会回收(关闭页面回收)
一般情况下局部变量的值,不用了,会被自动回收掉
内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
* 算法说明
堆栈空间分配区别:
1、栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面
2、堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面
垃圾回收算法:引用计数法和标记清除法
* 引用计数
IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有了引用就回收对象
算法:
1、跟踪记录被引用的次数
2、如果被引用一次,那么就记录次数1,多次引用会累加++
3、如果减少一个引用就减1--
4、如果引用次数为0,则释放内存
但存在一个问题:嵌套引用(循环引用)
如果两个对象相互引用,尽管它们已不再使用,垃圾回收器不会进行回收,导致内存泄漏
例如:
function fn() {
let o1={}
let o2={}
o1.a= o2
o2.a=o1
return '引用计数无法回收'
}
fn()
* 标记清除法
现代的浏览器已经不再使用引用计数法,通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的
核心:
1、标记清除算法将“不再使用的对象”定义为“无法达到的对象”
2、就是从根部(在JS中就是全局对象)触发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的
3、那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
5、闭包
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
即 闭包=内存函数+外层函数的变量
闭包的作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式
function outer() {
let i=1
function fn() {
console.log(i)
}
return fn
}
const fun=outer()
fun()
闭包应用:实现数据的私有
比如,我们要做个统计函数调用次数,函数调用一次,就++
6、变量提升
变量提升是JavaScript中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
把var声明的变量提升到当前作用域的最前面,只提升声明,不提升赋值
注意:
1、变量在未声明即被访问时会报语法错误
2、变量在var声明之前即被访问,变量的值为underfined
3、let/const声明的变量不存在变量提升
4、变量提升出现在相同作用域当中
5、实际开发中推荐先声明再访问变量
2、函数进阶
1、函数提升
会把所有函数声明提升到当前作用域的最前面,只提升函数声明,不提升函数调用
总结:
1、函数提升能够使函数的声明调用更灵活
2、函数的表达式不存在提升的现象
3、函数提升出现在相同的作用域当中
2、函数参数
1、动态参数
arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所用实参
例如:
function sum() {
let s=0
for(let i=0;i<arguments.length;i++){
s+=arguments[i]
}
console.log(s)
}
sum(5,10) //求和5与10
sum(1,2,4) //求和1 2 4
总结:
1、arguments是一个伪数组,只存在于函数中
2、arguments的作用是动态获取函数的实参
3、可以通过for循环依次得到传递过来的实参
2、剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
例如:
function sum(...arr) {
let s=0
for(let i=0;i<arr.length;i++){
s+=arr[i]
}
console.log(s)
}
sum(5,10) //求和5与10
sum(1,2,4) //求和1 2 4
注意:
1、...是语法符号,置于最末函数形参之前,用于获取多余的实参
2、借助...获取的剩余实参,是个真数组
* 展开运算符
展开运算符(...),将一个数组进行展开
const arr=[1,2,3]
console.log(...arr) //1 2 3
说明:
1、不会修改原数组
2、常与Math中的方法求数组中的最大最小值
3、可以用来合并数组
3、箭头函数
引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来需要匿名函数的地方
语法1:基本写法
const fn=function() {
console.log('我是普通函数');
}
fn()
箭头函数
const fn =()=>{
console.log(123);
}
注意:
当只有一个形参的时候,可以省略小括号
当只有一行代码的时候,可以省略大括号,也可以省略return
箭头函数可以直接返回一个对象,对象需要用小括号包起来
箭头函数参数
1、普通函数有arguments动态参数
2、箭头参数没有arguments动态参数,但是有剩余参数...arr
箭头函数this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值
箭头函数不会创建自己的this,只会从自己的作用域链的上一层沿用this
3、解构赋值
解构赋值是将数组单元值快速批量赋值给一系列变量的简洁语法
1、数组解构
基本语法:
1、赋值运算符=左侧的[ ]用于批量声明变量,右侧数组的的单元值将被赋值给左侧的变量
2、变量的顺序对应数组单元值的位置依次进行赋值操作
const arr=[1,2,3]
const [fir,sec,thi]=arr
可以很简洁地对两个变量进行交换
注意:数组解构的前面必须加分号
变量多,单元值少的情况:多余的变量为underfined
变量少,单元值多的情况:按照顺序往后赋值,剩余参数可以解决这个问题
2、对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1、基本语法:
1、赋值运算符=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧变量
2、对象属性的值将被赋值给与属性名相同的变量
3、注意解构的变量名不要和外面的变量名冲突否则报错
4、对象中找不到与变量名一致的属性时变量值为underfined
对象解构的变量名可以重新改名,旧变量名:新变量名
* forEach方法
forEach()方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景:遍历数组的每个元素
语法:
被遍历的数组.forEach(function(当前数组的元素,当前元素索引号) {
//函数体
})
当前元素必须写,索引号可选