- 作用域
- 闭包
- 用闭包模拟私有方法
- 使用闭包对数据进行缓存
- 防抖
- 节流(throttle)
- 防抖和节流的应用场景
~ [实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E5%AE%9E%E6%97%B6%E6%9F%A5%E8%AF%A2.html">实时搜索框</a>](#实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E5%AE%9E%E6%97%B6%E6%9F%A5%E8%AF%A2.html">实时搜索框</a>) ~ [app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8%E5%8A%A0%E8%BD%BD.html">无限加载实例</a>](#app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。<a href="https://github.com/MacMang/JSBOOK/blob/master/%E9%97%AD%E5%8C%85/%E6%97%A0%E9%99%90%E6%BB%9A%E5%8A%A8%E5%8A%A0%E8%BD%BD.html">无限加载实例</a>)
作用域
在前面的函数章节里,我们知道当一个函数在使用一个变量的时候,它首先会在自己的作用域里查看是否有该变量,如果没有,则继续往上级作用域查找是否有该变量
闭包
- 闭包本质是个函数,当它访问父级函数的局部变量的时候机会产生闭包.
- 作用: 将父级函数的局部变量状态存储到内存中
用闭包模拟私有方法
var returnNum = (function () {
var num = 0;
function changeNum(value) {
num = value;
}
return {
add: function () {
changeNum(10);
},
delete: function () {
changeNum(-10);
},
getNum: function () {
return num;
}
}
})();
使用闭包对数据进行缓存
//计算斐波那契数列
function fn(n){
if(n==1||n==2){
return 1;
}
return fn(n-1)+fn(n-2)
}
//保存计算结果
function save(){
//该对象用于存储计算的结果
/*
例如:求第45个
result = {45:"计算结果"}
然后再求 第40个
result = {45:"45的计算结果",40:"40的计算结果"}
*/
var result = {};
return function(n){
/*
判断传递进来的n值是否为result对象的属性
如果result[n]存在结果,则直接从对象中获取结果,
如果不存在这先计算fn(n),然后再将fn(n)的结果赋值
给result[n];
*/
if(result[n]){
return result[n]
}else{
return result[n]=fn(n)
}
}
}
var count = save();
//计算第45个斐波那契数列
// 第一次计算的时候,耗时非常久
count(45);
//第二次求第45个斐波那契数列,能够秒得结果
count(45);
防抖
触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
/*
@description 防抖
@params fn: 待执行的行为,n:预定执行的事件时间
*/
function debounce(fn, n) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, n);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', debounce(handle, 1000));
上面的代码中, 当给window对象注册scroll事件的时候,会马上触发debounce函数,该函数有两个参数,fn是待执行的行为,也就是当浏览器滚动的时候,你要做什么。n是你打算在浏览器滚动之后,多长时间触发fn待执行行为。
debounce函数体内部:
- 在debounce首次被触发的时候,在内存中定义了一个 timeout变量。
- 将一个函数作为返回值返回给函数调用者,当浏览器滚动的时候就会触发该函数。在该函数体内,首先判断内存中的timeout变量的值是否为空,如果不为空,就表示在之前已经有待执行事件。我们通过clearTimeout清除这个定时器,把该事件从待执行中移除。
- 重新用一个定时器,决定在n毫秒之后执行fn函数。并将生成的定时器赋值给timeout
节流(throttle)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数
/*
@description 节流
@params func 待执行的行为,delay: 预定执行的事件时间,以毫秒计算
*/
var throttle = function(func, delay) {
//在内存中保留一个时间戳变量
var saveDate = Date.now();
return function() {
var _this = this;
var args = arguments;
// 获取回调函数执行时的事件
var now = Date.now();
// 判断回调函数执行时的时间戳和保留的时间戳之间的差是否大于delay的值,如果大于,则表示超过了节流控制的时间范围,func可以重新执行了
if (now - saveDate >= delay) {
//重新执行func函数
func.apply(_this, args);
//将func函数执行时的时间戳保留在内存中,用于下次判断
saveDate = Date.now();
}
}
}
function buySth() {
console.log("抢购商品了");
}
window.addEventListener('scroll', throttle(buySth, 10000));
防抖和节流的应用场景
以下例子,都在标题里有相应的下载链接,使用前端vue框架开发的,如果看不懂,可以暂时跳过,以后再回头阅读
实时搜索框。为了减少用户在输入的过程中不停地向后台发起请求。实时搜索框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
#app {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: scroll;
}
#app ul {
width: 100%;
}
#app ul li {
width: 100%;
height: 100px;
border-bottom: 1px solid lightgray;
padding: 10px;
box-sizing: border-box;
display: flex;
align-items: center;
}
.searchInput {
width: 100%;
height: 50px;
border: 1px solid lightgray;
padding: 10px;
box-sizing: border-box;
}
.searchInput input {
width: 100%;
height: 100%;
}
</style>
<script src="../bower_components/mockjs/dist/mock.js"></script>
<script src="../bower_components/axios/dist/axios.js"></script>
<script src="../bower_components/vue/dist/vue.js"></script>
<script>
const url = 'http://localhost:9999/table';
const reg = /\/table(\/\w)*/
const data = Mock.mock({
'dataSource|100': [{
'key|+1': 1,
'name': '@name'
}]
})
Mock.mock(reg, "get", (options) => {
const searchValue = options.body?JSON.parse(options.body).searchValue: "";
const dataSource = data.dataSource.filter((item)=>{
const str = item.name.toLowerCase();
return str.indexOf(searchValue.toLowerCase())!==-1
})
return {dataSource:dataSource};
})
</script>
</head>
<body>
<div id="app">
<div class="searchInput">
<input type="text" v-model="searchValue" placeholder="搜索框" @input="startSearch">
</div>
<ul>
<li v-for="(item,key) in dataSource" :key="key">
{{item.key}}-{{item.name}}
</li>
</ul>
</div>
</body>
</html>
<script>
new Vue({
el: '#app',
data: {
dataSource: [],
searchValue: '',
timer: null, //记录定时器的ID
},
mounted() {
this.addData();
},
methods: {
addData() {
axios.get(url).then((resp) => {
this.dataSource = [...this.dataSource, ...resp.data.dataSource]
});
},
startSearch() {
/*
当在搜索框输入内容的时候,每输入一个字符就会发起一次请求,这会给服务器造成很大的压力
解决方案:
1. 监听onchange方法,在输入完成失去光标之后再查询
2. 提供一个查询按钮,只有点击查询按钮的时候才开始查询
但是上面的两种的方案,都满足不了我们实时查询的需求,所以需要在监听oninput事件的同时
提供解决方案
1. 通过防抖,如果短时间内输入多个字符,以最后一次输入为准进行请求
*/
console.log(this.timer);
//先清空上次生成的定时器
clearTimeout(this.timer)
//重新生成新的定时器,并将定时器的ID赋值给timer进行记录
this.timer = setTimeout(()=>{
//发起网络请求
axios({
method: "get",
url: "/table",
data: {
searchValue: this.searchValue
}
}).then(resp => {
//成功的回调函数,返回的是增加的数据
this.dataSource = [...resp.data.dataSource]
// 清空定时器
clearTimeout(this.timer);
this.timer = null;
});
},500)
}
},
})
</script>
app列表使用向上滑动的过程中,向服务器发送请求。同样是为了避免多次请求,这里可以根据时间或者滑动的距离来作为防抖的判断条件。无限加载实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
#app {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: scroll;
}
#app ul {
width: 100%;
}
#app ul li {
width: 100%;
height: 100px;
border-bottom: 1px solid lightgray;
}
</style>
<script src="../bower_components/mockjs/dist/mock.js"></script>
<script src="../bower_components/axios/dist/axios.js"></script>
<script src="../bower_components/vue/dist/vue.js"></script>
<script>
const url = 'http://localhost:9999/table';
Mock.mock(url, {
'dataSource|10': [{
'key|+1': 1,
'name': '@name'
}]
})
</script>
</head>
<body>
<div id="app" ref="app">
<ul>
<li v-for="item in dataSource" :key="item.key">
{{item.key}}-{{item.name}}
</li>
</ul>
<!-- <button @click="addData">加载</button> -->
</div>
</body>
</html>
<script>
new Vue({
el: '#app',
data: {
dataSource: []
},
mounted() {
this.addData();
const loadData = this.scroll().loadData.bind(this);
app.addEventListener('scroll',loadData)
},
methods: {
addData() {
console.log("发起网络请求");
axios.get(url).then((resp) => {
this.dataSource = [...this.dataSource, ...resp.data.dataSource]
});
},
scroll() {
// 计算每个表有多高
let ulHeight = 10 * 101;
//app框的高度
let appHeight = this.$refs['app'].clientHeight;
//页码
let liNum = 1;
let min = Math.abs((appHeight * liNum - ulHeight));
let saved = false;
return {
loadData(ev) {
const scrollTop = ev.target.scrollTop;
/*
第一种情况,滑动到底部的时候再进行加载
if(Math.abs(min)===scrollTop){
liNum+=1;
min += ulHeight;
this.addData();
}
*/
/*
第二种情况:滚动距离min还有200的时候就发起请求
*/
if (min === scrollTop) {
saved = false
}
if (min <= (scrollTop + 200)) {
//判断是否已经请求过数据,并且
if (saved) {
console.log("已经发起请求过了,不需要重新发起请求");
return;
}
this.addData();
saved = true;
min += ulHeight;
}
},
// 手动释放 闭包所占用的内存
cleanHeap(){
ulHeight = null;
appHeight = null;
min = null;
saved = null;
}
}
}
},
})
</script>
- 浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
- 表单校验。
- 一定时间范围内,只允许用户点击一次按钮。过了这个时间范围,用户可以继续点击。