实际应用相关手写代码题(防抖节流,懒加载,Url参数,异步任务调度器)

1,075 阅读2分钟

防抖

防抖思路:在规定时间内未触发第二次,则执行

function debounce(fn, delay) {
  let timer = null;
  //利用闭包保存定时器
  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      //   fn();
    }, delay);
  };
}
function fn () {
  console.log('防抖---')
}
addEventListener('scroll', debounce2(fn, 1000))

节流

触发高频事件,且 N 秒内只执行一次。

function throttle(fn, delay) {
  let lastTime = 0;
  return function () {
    let nowTime = new Date();
    if (nowTime - lastTime > delay) {
      fn.apply(this, arguments);
      lastTime = nowTime;
    }
  };
}
function fn() {
  console.log("防抖---",new Date());
}
addEventListener("scroll", throttle(fn, 3000));
// 基础版1:时间戳(第一次触发会执行,但不排除不执行的可能,请思考一下哦)
function throttle(fn, delay) {
  var prev = Date.now()
  return function(...args) {
    var dist = Date.now() - prev
    if (dist >= delay) {
      fn.apply(this, args)
      prev = Date.now()
    }
  }
}

// 基础版2:定时器(最后一次也会执行)
function throttle(fn, delay) {
  var timer = null
  return function(...args) {
    var that = this
    if(!timer) {
      timer = setTimeout(function() {
        fn.apply(this, args)
        timer = null
      }, delay)
    }
  }
}

// 进阶版:开始执行、结束执行
function throttle(fn, delay) {
  var timer = null
  var prev = Date.now()
  return function(...args) {
    var that = this
    var remaining = delay - (Date.now() - prev)  // 剩余时间
    if (remaining <= 0) {  // 第 1 次触发
      fn.apply(that, args)
      prev = Date.now()
    } else { // 第 1 次之后触发
      timer && clearTimeout(timer)
      timer = setTimeout(function() {
        fn.apply(that, args)
      }, remaining)
    }
  }
}

function fn () {
  console.log('节流')
}
addEventListener('scroll', throttle(fn, 1000))

懒加载

  • 在页面放置img标签
  • 给img图片加上alt, width, height 和 data-src
  • 通过js判断页面是否滚动到某张图片需要显示的位置,这时将src赋值为data-src
  • 判断图片距离顶部的距离大于可视区域和滚动区域之和时懒加载。
<ul>
  <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
</ul>
let imgs =  document.querySelectorAll('img')
// 可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
function lazyLoad () {
  // 滚动卷去的高度
  let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  for (let i = 0; i < imgs.length; i ++) {
    // 图片在可视区冒出的高度
    let x = clientHeight + scrollTop - imgs[i].offsetTop
    // 图片在可视区内
    if (x > 0 && x < clientHeight+imgs[i].height) {
      imgs[i].src = imgs[i].getAttribute('data')
    }
  }
}
// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)

与普通的图片懒加载不同,如下这个多做了 2 个精心处理:

  • 图片全部加载完成后移除事件监听;
  • 加载完的图片,从 imgList 移除;
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
// 修正错误,需要加上自执行
  const imgLazyLoad = (function() {
   let count = 0
   return function() {
        let deleteIndexList = []
        imgList.forEach((img, index) => {
            let rect = img.getBoundingClientRect()
            if (rect.top < window.innerHeight) {
                img.src = img.dataset.src
                deleteIndexList.push(index)
                count++
                if (count === length) {
                    document.removeEventListener('scroll', imgLazyLoad)
                }
            }
        })
        imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
   }
   })()
// 这里最好加上防抖处理
document.addEventListener('scroll', imgLazyLoad)

实现url参数转json

var getQueryJson = function (url) {
  let arr = [];
  let res = {};
  let jsonstr = url.split("?")[1].split("#")[0]; //选取?和#中间的json串
  arr = jsonstr.split("&"); //获取浏览器地址栏中的参数
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].indexOf("=") != -1) {
      let str = arr[i].split("=");
      res[str[0]] = str[1];
    } else {
      res[arr[i]] = "";
    }
  }
  return res;
};

var getQueryJson2 = function (url) {
  let param = {}; // 存储最终JSON结果对象
  url.replace(/([^?&=]+)=([^?&=]*)/g, function (s, v, k) {
    param[v] = decodeURIComponent(k); //解析字符为中文
    return k + "=" + v;
  });
  return param;
};
console.log(
  getQueryJson(
    "http://127.0.0.1:8080/html/urltojson.html?id=1&name=good&price=#hash"
  )
);

异步任务调度器

//调度器类
class Scheduler {
  constructor(maxnum) {
    this.taskList = []; //承载还未执行的异步任务
    this.count = 0; //计数
    this.maxNum = maxnum; //允许同时运行的异步函数的最大个数
  }
  async add(fn) {
    if (this.count >= this.maxNum) {
      await new Promise((resolve) => {
        this.taskList.push(resolve);
      });
    }
    this.count++;
    const result = await fn();
    this.count--;
    if (this.taskList.length > 0) {
      this.taskList.shift()();
    }
    return result;
  }
}
const schedule = new Scheduler(2); //最多同一时间让它执行2个异步函数
//定义定时器
const timeOut=(time)=>{
    return new Promise(resolve=>{
        setTimeout(resolve,time)
    })
}
//定义异步任务执行器
const addTask=(time,order)=>{
    schedule.add(()=>{return timeOut(time).then(()=>{
        console.log('异步任务',order)
    })})
}
//输出顺序:2 3 1 4
addTask(1000,'1')
addTask(500,'2')
addTask(300,'3')
addTask(400,'4')
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4

后两个函数写在一起

//定时器在内部的异步任务执行器(写在一起)
const addTaskByTime = (time, order) => {
  schedule.add(() => {
    return new Promise((resolve) => {
      setTimeout(resolve, time);
    }).then(() => {
      console.log("异步任务", order);
    });
  });
};
//输出顺序:2 3 1 4
addTaskByTime(1000, "1");
addTaskByTime(500, "2");
addTaskByTime(300, "3");
addTaskByTime(400, "4");