JS唬住面试官拿高薪 一 函数篇(下)

笔者最近在对原生JS的知识做系统梳理,因为我觉得JS作为前端工程师的根本技术,学再多遍都不为过。打算来做一个系列,以一系列的问题为驱动,当然也会有追问和扩展,内容系统且完整,对初中级选手会有很好的提升,高级选手也会得到复习和巩固。

第一章: 防抖(debounce)与节流(throttle)区别与实现

我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频繁触发事件,咋办呢?这时候就应该用到函数防抖和函数节流了
1230971-20190507185520397-1412613733.png

防抖 (debounce)

概念 触发高频事件后n毫秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
特点 使用防抖处理之后,500毫秒内如果再次输入,会重新开始计时
通语 频繁操作不予执行,直到有间隔操作间断大于n毫秒才可以进行第二次操作执行 间隔时间 >= n
通俗核心思想 每次事件触发则删除原来的定时器,建立新的定时器**。跟王者荣耀的回城功能类似**,你反复触发回城功能,那么只认最后一次,从最后一次触发开始计时
防抖效果图.gif
![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 分钟一到我就要发车走人!
节流效果图.gif
![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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值