笔者最近在对原生JS的知识做系统梳理,因为我觉得JS作为前端工程师的根本技术,学再多遍都不为过。打算来做一个系列,以一系列的问题为驱动,当然也会有追问和扩展,内容系统且完整,对初中级选手会有很好的提升,高级选手也会得到复习和巩固。
第一章: 防抖(debounce)与节流(throttle)区别与实现
我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频繁触发事件,咋办呢?这时候就应该用到函数防抖和函数节流了
防抖 (debounce)
概念 触发高频事件后n毫秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
特点 使用防抖处理之后,500毫秒内如果再次输入,会重新开始计时
通语 频繁操作不予执行,直到有间隔操作间断大于n毫秒才可以进行第二次操作执行 间隔时间 >= n
通俗核心思想 每次事件触发则删除原来的定时器,建立新的定时器**。跟王者荣耀的回城功能类似**,你反复触发回城功能,那么只认最后一次,从最后一次触发开始计时
![debounce 防抖.png](https://img-blog.csdnimg.cn/img_convert/63a5d82e661db679b71efea3e7e436c3.png#height=304&id=WrjvZ&name=debounce 防抖.png&originHeight=304&originWidth=622&originalType=binary&ratio=1&size=19307&status=done&style=none&width=622)
function debounce(fn, delay) {
let timer = null;
return function (...args) {
let context = this;
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
场景应用实例
<body>
<input type="text" value="">
</body>
<script>
document.querySelector("input").oninput = debounce(function (data) { console.log(this.value,data); })
function debounce(fn,time=500) {
let timer = null // 用来存放定时器的返回值
return function () {
clearTimeout(timer) // 每次触发防抖的时候,把前面一个定时器清除掉
timer = setTimeout(() => {
fn.apply(this, arguments) // 为了却表上下文环境为当前的this
}, time)
}
}
</script>
适用场景, 比如 用户搜索框输入内容, 比如用户搜索框输入内容,不使用防抖用户每个输入的内容都会向后台发送请求,使用防抖之后,等到用户输入完毕后才会发送请求,减少服务器压力
节流 (throttle)
概念 高频事件触发,但在n秒内执行一次,所以节流会稀释函数的执行频率
特点 无论你怎么输,每隔xxx毫秒才触发一次函数
通语 间隔n毫秒执行一次 间隔时间 = n毫秒
通俗核心思想 如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不管,10 分钟一到我就要发车走人!
![throttle 节流.png](https://img-blog.csdnimg.cn/img_convert/0d12090086707b1c45cae250a5430fba.png#height=323&id=YlV6t&name=throttle 节流.png&originHeight=323&originWidth=717&originalType=binary&ratio=1&size=18281&status=done&style=none&width=717)
function throttle(fn, interval = 500) {
let flag = true;
return function (...args) {
let context = this;
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(context, args);
flag = true;
}, interval);
};
};
// 写成下面的方式也是表达一样的意思:
const throttle = function (fn, interval = 500) {
let last = 0;
return function (...args) {
let context = this;
let now = +new Date();
// 还没到时间
if (now - last < interval) return;
last = now;
fn.apply(this, args)
}
}
加强版节流(防抖+节流)
function throttle(fn, delay = 500) {
let last = 0, timer = null;
return function (...args) {
let context = this;
let now = new Date();
if (now - last < delay) {
clearTimeout(timer);
timer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, delay);
} else {
// 这个时候表示时间到了,必须给响应
last = now;
fn.apply(context, args);
}
}
}
场景应用实例
<input type="text" value="">
<script>
document.querySelector("input").oninput = throttle(function (data) { console.log(this.value, data); })
function throttle(fn) {
let canRun = false //记录一个是否有要执行的函数
return function () {
if (canRun) return // 如果在定时器的周期内直接返回,不执行
canRun = true
setTimeout(() => {
fn.apply(this, arguments)
canRun = false // 运行完毕后设置为false,说明函数运行完毕,定时器为空,可以进行下次函数
}, 1000)
}
}
</script>
<body style="height: 10000px;"> </body>
<script>
window.addEventListener('scroll', throttle(handle(), 1000))
function throttle(fn, delay) {
var timer = null, startTime = Date.now() //设置开始时间
return function () {
var currentTime = Date.now()
var context = this
var args = arguments
var remaining = delay - (currentTime - startTime) //剩余时间
clearTimeout(timer)
if (remaining <= 0) { // 当间隔大于设置的delay延迟时候
fn.apply(context, args)
startTime = currentTime
} else {
timer = setTimeout(function () {
fn.apply(context, args)
}, remaining) //取消当前计数器并计算新的remaining
}
}
}
function handle() {
var count = 1;
return function (e) {
console.log(++count, e)
}
}
</script>
- 适用场景
- 懒加载监听浏览器滚动位置
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
第二章: 正则-多关键词高亮
<body> </body>
<script>
function format(text, formatText) {
var a = formatText.sort((a, b) => b.length - a.length);
return text.replace(new RegExp('(' + a.join('|') + ')', 'g'), '<span class="red">$1</span>')
}
// const formatText = ["腾讯云", "云", "云服务", "云端"]
// const text = "腾讯云,是腾讯推出的云端运算服务,发布全球云服务云朵版图,构建覆盖全球的数据中心节点,在更多地区跟全球的合作伙伴共同构建,为中国出海企业以及海外本土企业提供云服务。"
const formatText = ["开发", "前端开发", "开发程序员","前端开发程序员"]
const text = "一缕清风,是JavaScript技术大牛,他开发了很多项目,是个前端开发程序员,从事前端开发工作,他是个开发程序员"
var div = document.createElement('div')
div.innerHTML = format(text, formatText)
document.body.appendChild(div);
</script>
<style>
.red {
color: red;
}
</style>
第三章:字符串模板
function render(template, data) {
const reg = /\{\{(\w+)\}\}/ // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1] // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]) // 将第一个模板字符串渲染
return render(template, data) // 递归的渲染并返回渲染后的结构
}
return template // 如果模板没有模板字符串直接返回
}
let template = '我是{{name}},年龄{{age}},性别{{sex}}'
let person = {
name: '布兰',
age: 12,
sex: '女'
}
console.log(render(template, person)) // 我是布兰,年龄12,性别女
第四章:解析 URL 参数为对象
//获url的?后的所有参数
export function getURLLocationSearch() {
var adataSource = location.search.substr(1).split('&');
var obj = {};
adataSource.forEach(function(item, i) {
var pairList = item.split('=');
obj[pairList[0]] = pairList[1];
});
return obj;
}
// 获取URL地址?后的参数,返回一份字典
export function decodeLocationSearch() {
var search = decodeURI(document.location.search);
return search
.replace(/(^\?)/, '')
.split('&')
.reduce(function(result, item) {
var values = item.split('=');
result[values[0]] = values[1];
return result;
}, {}); //后面的{} result是后面的{} renduce第二个参数,item是遍历的
}
// 获取URL地址?后的参数,返回一份字典
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
把字典的键值对转换拼接url
//把字典的键值对转换拼接url {key1:value1,key2:value2} ==> ?key1=value1&key2=value2
export function encodeLocationSearch(params: any) {
let data_arr = [];
for (let key in params) {
data_arr.push(key + '=' + params[key]);
}
return '?' + data_arr.join('&');
}
正则匹配URL地址?后的单个参数
//todo kim-stamp 正则匹配URL地址?后的单个参数
// i 不区分(ignore)大小写;
// g 全局(global)匹配;
// m行多(more)匹配 ;
// s 特殊字符圆点 . 中包含换行符;
// U 只匹配最近的一个字符串;不重复匹配;
// 默认的圆点 . 是 匹配除换行符 \n 之外的任何单字符,加上s之后, . 中包含换行符
// 【修正符】
// x 将模式中的空白忽略;
// A 强制从目标字符串开头匹配;
// D 如果使用$限制结尾字符,则不允许结尾有换行;
// e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
export function getParameter(params: any, datasource = location.search) {
var re = new RegExp(params + '=([^&]*)', 'i');
var a = re.exec(datasource);
if (a == null) {
return null;
}
return a[1];
}
第五章:UUID编码
export function generateUUID() {
var d = new Date().getTime();
if (window.performance && typeof window.performance.now === 'function') {
d += performance.now(); //use high-precision timer if available
}
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
return uuid;
}
第六章:正则获取cookie
//正则获取cookie
export function getCookie(name: any) {
//var res = document.cookie.match(/\bcsrf_token=([^;]*)\b/)
var res = document.cookie.match('\\b' + name + '=([^;]*)\\b');
return res ? res[1] : undefined;
}