一、了解函数
函数的定义阶段
1 在内存中开辟一个存储空间
2 把函数体内的代码当作“字符串”一摸一样的放在这个空间中
==碰到的所有变量都不进行解析
3 把这个空间地址赋值给函数名
函数调用阶段
1 每一个函数调用的时候都会开辟一个执行空间
2 调用一次就开辟一个执行空间,且都不一样
3 执行完毕,执行空间销毁
4 再次调用的时候,再开辟一个新的执行空间
5 执行完毕,执行空间再次销毁
var num = 10
function fn() {//存储空间xxff00
console.log('数字' + num)
}
fn()//执行空间xxff11
fn()//执行空间xxff22
// 函数调用阶段
// 按照fn里面存储的空间地址,找到xxff00这个函数的存储空间
// 开辟一个函数执行空间xxff11,在内存里面执行
// 再xxff11这个空间里面进行形参的复制和预解析
// 把存储空间xxff00里面存储的代码复制一份到执行空间执行
// 等到代码执行完毕,这个开辟的xxff11空间就销毁了
// 函数再次调用
// 按照fn里面存储的空间地址,找到xxff00这个函数的存储空间
// 开辟一个函数执行空间xxff22,在内存里面执行
// 再xxff22这个空间里面进行形参的复制和预解析
// 把存储空间xxff00里面存储的代码复制一份到执行空间执行
// 等到代码执行完毕,这个开辟的xxff22空间就销毁了
二 、 不会销毁的执行空间
在一个特殊的情况下,函数的执行空间不会销毁
什么是特殊情况
当函数内部返回一个‘复杂数据类型’
并且在外部有变量接受这个‘复杂数据类型’的时候
这个时候函数的执行空间不能被销毁(不会被销毁)
- 变量的赋值关系
基本数据类型赋值时,就是直接进行赋值(null undefinded 数字number 字符串string 布尔boolean)
赋值以后两个变量就没有关系了
let a = 100
let b = a
a = 200
console.log(a) //100
console.log(b) //200
复杂数据类型赋值时,是进行存储地址的赋值
赋值以后两个变量操作的是同一个空间
let obj={name:'海绵宝宝'}
let obj2 =obj
obj.name = '派大星'
console.log(obj.name)//派大星
console.log(obj2.name)//派大星
这个不会销毁的执行空间 什么时候被销毁
当外部接受的那个变量不在引用函数内部的返回值的时候
这个函数执行空间就销毁了
普通数据类型赋值的是值,销不销毁无所谓
但是复杂数据类型赋值的是地址,销毁了,其他变量指向的地址就都没了
所以不会被销毁
三、闭包的定义
+ 必报的生成有三个必要条件(缺一不可)
1 在函数A内部直接或间接返回一个函数B
2 B函数内部使用A函数的私有变量(函数内的局部变量)
3 A函数外部有一个变量接受函数B
就形成一个不会销毁的函数空间
我们管这个不会销毁的A函数的执行空间叫闭包
+ 把函数A里面返回的函数B,叫做函数A的闭包函数
+ 官方给的定义有一句话,闭包=>函数内部的函数
四、闭包的使用
直接返回一个函数 return function(){}
间接返回一个函数 return 一个对象或数组, 这个对象或数组里面有多个函数
当只需要访问一个私有变量的时候可以使用直接或间接
当访问多个私有变量的时候,需要使用间接返回的方式
五、闭包的特点(优点和缺点并存)
1 延长了变量的生命周期
+ 优点:因为执行空间不会销毁,变量也不会销毁
+ 缺点:因为执行空间不会销毁,所以变量一直存在内存中
2 可以访问函数内部的私有变量
+ 优点:利用闭包函数可以访问函数内部的私有变量
+缺点:因为执行空间不会销毁,所以变量一直存在内存中
闭包函数的缺点是“致命的”
因为当一段内存中有一个不能被销毁的东西一直存在的时候
那么就会出现内存占用,如果过多的内存占用,就会导致内存溢出
也就是内存泄漏
所以闭包要慎用
记得用完闭包后,把承接闭包的变量的值改变成nul
六、闭包的作用:
就是当需要延长变量的生命周期的时候
或者需要访问某一个函数内部的私有变量的时候
就可以使用闭包来解决问题
前提
如果有别的办法,优先使用别的方法
没别的方法,就用闭包
七、 柯里化函数
柯里化又称部分求值
接受了这些参数后,函数不会立即求值
而是继续返回另一个函数
刚才传入的参数在函数形成闭包中被保存起来
待到函数被真正需要求值的时候,之前传入的所有参数都被一次性用于求值
// 需求:打印一个我们班学员的信息:我是xxx学科 xx班的xxx xxx岁
function printInfo(xueke,banji,name,age){
console.log(`我是${xueke} ${banji}班级 我叫${name}${age}岁了`)
}
printInfo('机智院','软件工程','李明',18)
printInfo('机智院', '软件工程', '叶琪琪',18)
// 改写上面的这个不好用的函数
function printInfo2(xueke, banji){
return function(name,age){
console.log(`我是${xueke} ${banji}班级 我叫${name} ${age}岁了`)
}
}
// 生产出一个固定学科和班级的函数
let print2020 =printInfo2('机智院','软件工程')
// 开辟了一个printInfo空间
// 在这个空间里面进行形参的赋值
// 定义函数function(name,age){}
// 把执行空间里面定义的这个函数地址赋值给print2020
print2020('叶琪琪',18)
print2020('海绵宝宝',8)
// 执行print2020函数
八、节流与防抖
1 节流
在一段时间内触发一次事件函数
如果前一次触发的事件函数还没有执行,则再次触发的事件不做任何操作
在单位时间内触发多次,只会执行第一次触发的操作
let inp = document.querySelector('input')
inp.oninput = (function (flag) {
return function () {
if (!flag) return // flag=false后续操作不再执行
flag = false
setTimeout(() => {
console.log(inp.value)
flag = true
}, 3000)
}
})(true)
2 防抖
在一段时间内触发一次事件函数
如果单位时间还没有到达,再次触发这个事件函数
那么就立刻停止上一次的事件直接重新计算间隔时间
let inp = document.querySelector('input')
inp.oninput = (function(timer){
return function(){
clearTimeout(timer) //清除正在进行定时器,再开一个新的
timer = setTimeout(()=>{
console.log(inp.value)
},1000)
}
})