在前端开发过程中,在处理用户交互时, 最常用的操作就是加上防抖和节流.
作用:
一是为了防止用户频繁操作;
二是为了节约一定的服务器资源,减少资源浪费的情况。
一.防抖
指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。也就是说,短时间内不重复触发一个事件
// 防抖函数debounce 利用闭包的原理
// 参数 delay 延迟, 默认1s
const debounce = function (fn, delay = 1000) {
// 1. 声明一个定时器变量
let timer = null
// 将debounce处理结果当做函数返回
// 4. 使用闭包 函数调用函数之外的一个参数 ...args可变数组 args数组
return function (...args) {
// 5. 保存当前对象的this
const that = this
// 2. 在触发请求时, 根据timer进行判断
// 当timer不为空, 需要清除定时器
if (timer) {
//清除定时器
clearTimeout(timer)
}
// 3. (清除后)重新计时
// timer = setTimeout(fn, delay)
timer = setTimeout(function(){
// 改变this指向, 改变当前对象, 一直传下去
fn.apply(that, args)
},delay)
}
}
二.节流
点击事件,在一段事件内连续点击,指定时间内只触发一次
// 节流函数throttle 利用闭包的原理
// 参数 delay 延迟, 默认1s
const throttle = function (fn, delay = 1000) {
// 1. 声明一个定时器变量
let timer = null
// 将throttle处理结果当做函数返回
//触发事件回调时执行这个返回函数
return function(){
const that = this
// 2. 存在timer, 表示时间还没到
if(timer){
return
}
// 开始设定一个新的定时器, 定时器执行结束后执行传入的函数 fn
timer = setTimeout(()=> {
timer = null
},delay)
// 相当于执行函数
fn.apply(that, args)
}
}
三.闭包
1.什么是闭包(Closure)
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。(简单理解,就是函数套函数)
2.闭包的特点
让外部访问函数内部变量成为可能
局部变量会常驻在内存中
可以避免使用全局变量,防止全局变量污染
3.闭包的好处与坏处
好处:可以读取其他函数内部的变量,并将其一直保存在内存中
坏处:可能会造成内存泄漏或溢出
// 闭包 本质上,闭包是将函数内部和函数外部连接起来的
function fun1() {
var num1 = 5
console.log(num1)
}
// console.log('fun1外部调用num1',num1); //无法访问
// 闭包(函数内部的函数) 封装到小的作用域中去
// 可解决函数体外无法访问函数体内定义的局部变量的问题, 则返回变量写在闭包中外部即可访问
function fun2() {
var num2 = 10
// 返回函数, 即可使用局部作用域中的变量
return function () {
return num2
}
}
// 调用fun2() 返回的是一个函数. 故需要再调用一下 变量的生命周期相当于没有结束
//写法1
console.log(fun2()())
//写法2
const fn = fun2() //fn是个函数
console.log(fn())
// 通过定时器也可以实现闭包 (立即执行函数)
/*
闭包特点
让外部访问函数内部的变量
局部变量会常驻与内存之中
避免使用全局变量, 防止全局变量污染
闭包优缺点
好处: 可读取其他函数内部的变量, 并将其一直保存在内存中
坏处: 可能造成内存泄漏或溢出
*/
function add(x) {
return function (y) {
return x + y
}
}
// 闭包,共享相同的函数定义, 但值互不影响, 函数作用域不同
let sum1 = add(2) // 第一个函数作用域中x=2常驻内存(一直保存在内存中),并且一致保存在内存中, 返回函数,故sum1是函数 function(y){}
let sum2 = add(10)
console.log(sum1(10)) //12 // 调用sum1,故y=12, 返回2+10
console.log(sum2(20)) //30
// 释放内存, 防止内存泄漏或污染
sum1 = null
sum2 = null
// 立即执行函数 也是一个闭包
;(function (x, y) {
return console.log(x - y) // 8
})(20, 12)
// 打印 10个10
var arr1 = []
for (var i = 0; i < 10; i++) {
// 数组中的每个元素都是函数
arr1[i] = function () {
return i
}
}
for (var j = 0; j < 10; j++) {
console.log(arr1[j]()) //打印10个10 立即执行
}
// 打印 0-9 闭包
//方式1 闭包使用立即执行函数包裹, 将i传给函数 闭包只能取得包含函数中任何变量的最后一个值
var arr2 = []
for (var i = 0; i < 10; i++) {
// 数组中的每个元素都是函数 闭包使用立即执行函数包裹, 将i传给函数
arr2[i] = (function (num) {
return function () {
return num
}
})(i)
}
for (var j = 0; j < 10; j++) {
console.log('rrrr===>', arr2[j]) //打印10个10 立即执行
}
// 方式2 立即执行函数
var arr3 = []
for (var i = 0; i < 10; i++) {
// 数组中的每个元素都是函数
arr3[i] = (function () {
return i
})()
}
for (var j = 0; j < 10; j++) {
console.log(arr3[j]) //打印10个10 立即执行
}
代码说明:
创建闭包 最常见方式,就是在一个函数内部创建另一个函数.
闭包的作用域链 包含着它自己的作用域,以及包含它的函数的作用域和全局作用域.
闭包的注意事项: 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止.
闭包只能取得包含函数中任何变量的最后一个值.
释放内存 e.g: num=null
4.闭包中的this
// 闭包中的this
const obj = {
name: '内---mm',
getName: function () {
return this.name
},
}
// 普通函数 this指向 函数调用者
console.log('getName===>', obj.getName()) // 内---mm
const getNameFun = obj.getName
// this指向window getNameFun 是一个函数 调用时,没有其他调用者,那么调用者即为Window对象, const声明的变量在window上没有,故为undefined, 若为var声明则为var声明的变量值
console.log('getNameFun==>', getNameFun()) //undefined
const obj1 = {
name: '内1111---mm',
getName: function () {
return function () {
return this.name
}
},
}
// this指向window 以下两种意思调用实则一样
// obj1.getName获得一个函数function(){ return function(){ return this.name }, ()执行一次,又得到一个函数function(){return this.name},再()执行返回this.name, 此时没有其他调用者调用
console.log('getName1===>', obj1.getName()()) //undefined
const getNameFun1 = obj1.getName()
console.log('getNameFun1==>', getNameFun1()) //undefined
const obj2 = {
name: '内1111---mm',
getName: function () {
//保存环境,保存this, 保存当前调用者 that常驻内存
const that = this
// 闭包 函数套函数, 内部函数可访问函数体外的变量
return function () {
return that.name
}
},
}
// 利用that保存this, 使得this指向obj函数调用者, 故将结果均为obj.name的值
console.log('getName2===>', obj2.getName()()) //内1111---mm
const getNameFun2 = obj2.getName()
console.log('getNameFun2==>', getNameFun2()) //内1111---mm
5.闭包的应用
应用闭包的主要场合是:设计私有的方法和变量
私有变量包括:函数的参数, 局部变量, 函数内部定义的其他函数(闭包)
特权方法(privileged method): 有权访问私有变量和私有函数的公有方法
严格来说,JavaScript没有私有成员的概念,所有对象属性都是公共的。不过JavaScript有私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外面访问这些变量。
// 私有变量 n1,n2,sum 普通函数,在外部无法访问函数内部私有变量
function add1(n1,n2){
const sum1 = n1 + n2
return sum1
}
const res = add1(3,4)
// console.log(n1,n2,sum1) 无法访问函数内部私有变量
console.log(res); // 7
//通过特权方法, 向外提供函数内部私有变量, 也可修改
function add2(n3,n4){
var n = 100
// 特权方法 通过公有方法,向外提供函数内部私有变量, 也可修改
this.getN = function(){
return n
}
this.setN = function(val){
n = val
}
const sum2 = n3 + n4
return sum2
}
// 通过new关键字,构造函数创建对象
let number = new add2()
console.log(number.getN()); // 100
// 通过特权方法, 修改函数内部私有变量
number.setN(1000000)
console.log(number.getN()); // 1000000
// 立即执行函数, !只是为了与其他分隔开
!(function(){
var uname = '' //静态变量
// 使用node运行js文件, 全局对象是global, 如果加var声明Person,反而会报错
Person = function(val){
uname = val
}
Person.prototype.getName = function(){
return uname
}
Person.prototype.setName = function(val){
uname = val
}
})()
console.log('PerSon===>',Person); //PerSon===> [Function: Person]
//通过特权方法访问函数内部的变量
const per1 = new Person('mmmm')
console.log(per1.getName()); // mmmm
const per2 = new Person('tttt')
console.log(per2.getName()); // tttt
// 两个对象共享一个方法 当赋值时, 会修改函数中静态属性
console.log(per1.getName === per2.getName); // true
const per3 = new Person('11')
const per4 = new Person('22')
console.log(per3.getName()); // 22
console.log(per4.getName()); // 22