数据结构与算法训练

实现防抖和节流

(1)防抖:可以使事件被触发不立即执行,n秒之后再进行处理,如果n秒内事件再次被触发,则重新计时

使用场景:一些点击提交请求上,防止重复请求

例如,当我们在搜索框中输入关键词时,输入框会不断触发 oninput 事件,如果每次输入都去请求服务器获取数据,会造成不必要的请求浪费。此时就可以使用防抖技术,将一定时间内的多次触发合并为一次操作,只请求一次服务器数据,减少了请求次数和服务器负载。

分析:

  • 1.需要一个定时器
  • 2.将定时器设置成指定间隔时间后执行
  • 3.中途如果再次触发,则清空重新计时
js 复制代码
// 输入:防抖函数fn;等待的秒数
// 输出:函数的执行
function debounce(fn,wait){
    // 1.需要一个定时器
    let timer = null
    return function(){
        let _this = this
        let args = arguments
        
        // 3.中途如果再次触发,则清空重新计时
        if(timer){
            clearTimeout(timer)
            timer = null
        }
        
        // 2.将定时器设置成指定间隔时间后执行
        timer = setTimeout(()=>{
            fn.apply(_this, args)
        })
    }
}
js 复制代码
function debounce(func, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用防抖优化搜索框输入
const searchInput = document.getElementById('search-input');
const searchBtn = document.getElementById('search-btn');
function search() {
  console.log('searching...');
  // 发送请求获取搜索结果
}

searchInput.addEventListener('input', debounce(search, 500));
searchBtn.addEventListener('click', search);

(2)节流:指的是规定的一个时间,触发一次之后,如果在规定的时间内重复触发了,只有第一次生效

场景:使用在scroll函数事件的监听

例如,当我们拖动网页上的滚动条时,会不断触发 onscroll 事件,如果每次触发都去计算滚动距离,会造成浏览器性能下降。此时就可以使用节流技术,将一定时间内的多次触发限制为一次操作,只计算一次滚动距离,提高了浏览器性能和用户体验。

分析:

  • 1.获取执行时间的时间点
  • 2.获取当前时间点
  • 3.两次重复操作的时间间隔与节流延时的关系
js 复制代码
// 输入:节流函数fn;节流延时
// 输出:函数的执行
function throttle(fn, delay){
    // 1.获取执行时间的时间点
    let currentTime = Date.now()
    
    return function(){
        // 2.获取当前时间点
        let nowTime = Date.now()
        let _this = this
        let args = arguments
        
        // 3.两次重复操作的时间间隔与节流延时的关系
        if(nowTime - currentTime >= delay){
            currentTime = Date.now()
            return fn.apply(_this, args)
        }
    }
}
javascript 复制代码
function throttle(func, delay) {
  let timer = null;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

// 使用节流优化滚动事件
window.addEventListener('scroll', throttle(function() {
  console.log('scrolling...');
  // 计算滚动距离
}, 500));

手写call、apply、bind

每个Function对象都存在apply()、call()、bind() 方法,其作用都是可以在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。

三者异同:

    1. 三者都可以改变this的指向,第一个参数都是this,如果指向是null或者undefined则指向window
    1. apply的参数是数组,call是列表,而bind可以多次传入
    1. apply和call是改变this的指向之后直接运行函数,而bind则是返回绑定之后的函数

(1)手写call:

js 复制代码
// 输入:上下文,执行函数的参数
// 输出:执行结果
Function.prototype.myCall = function(context){
    // 1.判断执行对象是否为函数
    if(typeof this !== 'function'){
        console.error('this is not a function')
    }
    // 2.获取执行函数的参数
    let args = [...arguments].slice(1)
    let result = null
    // 3.传入值判断,是否有值,如果没有,默认为全局,即window
    if(!context){
        context = window
    }
    // 4.执行对象挂载在上下文之上
    context.fn = this
    // 5.在上下文中调用执行对象并且传入执行参数
    result = context.fn(...args)
    // 6.将上下文复原,删除新增临时属性
    delete context.fn
    // 7.返回结果
    return result
}
js 复制代码
var name = 'waq';
    var obj = {
        name: 'goodLuck'
    }
    function sayName(){
        console.log(this.name);
    }
    
    //1、改变this指向函数
    Function.prototype.myCall = function (context){
        // context 为可选参数,如果不传的话默认上下文是 window
        context = context || window;
        // 因为 call 可以传入多个参数作为调用函数的参数,所以将参数单独抽取出来
        var args = [...arguments].slice(1);  //第一个参数为context
        // 给 context 创建一个 fn 属性,并将值设置为需要调用的函数
        context.fn = this;   //因为call的调用方式形如:sayName.call(obj),因此此时call方法的this指向为sayName,因此context.fn = this即为context.fn = sayName
        var result = context.fn(...args); 
        // 删除对象上的函数,释放内存空间。返回结果
        delete context.fn;
        return result;
    }
    //2、验证
    //没有改变this指向时
    sayName(); //waq
    //改变this指向后
    sayName.myCall(obj);  //goodLuck

(2)手写apply:apply 和 call 实现类似,不同的是参数处理,apply 传入的是数组。

js 复制代码
// 输入:上下文,执行函数的参数
// 输出:执行结果
Function.prototype.myApply = function(context){
    // 1.判断执行对象是否为函数
    if(typeof this !== 'function'){
        console.error('this is not a function')
    }
    // 2.获取执行函数的参数
    let args = arguments[1]
    let result = null
    // 3.传入值判断,是否有值,如果没有,默认为全局,即window
    if(!context){
        context = window
    }
    // 4.执行对象挂载在上下文之上
    context.fn = this
    // 5.在上下文中调用执行对象并且传入执行参数
    if(args){
        result = context.fn(...args)
    }else{
        result = context.fn()
    }
    // 6.将上下文复原,删除新增临时属性
    delete context.fn
    // 7.返回结果
    return result
}
js 复制代码
    Function.prototype.myApply = function (context){
        context = context || window;
        context.fn = this;
        var result;
        if (arguments[1]){
            result = context.fn(...arguments[1]);
        }else{
            result = context.fn();
        }
        delete context.fn;
        return result;
    }

(3)手写bind:bind 会创建一个新函数,不会立即执行。bind 后面传入的这个参数列表可以分多次传入,call 和 apply 则必须一次性传入所有参数。

js 复制代码
Function.prototype.myBind = function(context){
    // 1.判断执行对象是否为函数
    if(typeof this !== 'function'){
        console.log('this is not a function')
    }
    // 2.获取参数
    let args = [...arguments].slice(1)
    let fn = this
    
    return function Fn(){
        // 根据调用方,确定最终返回值
        return fn.apply(
            // 判断this是否为Fn的实例对象
            this instanceof Fn ? this : context,
            args.concat(...arguments)
        )
    }
}
js 复制代码
    Function.prototype.myBind = function (context){
        context = context || window;
        //返回一个绑定this的函数,这里我们需要保存this,具体保存的this如同call
        let self = this;
        let args = [...arguments].slice(1);
        //返回一个函数
        return function () {
            let newArgs = [...arguments];
            return self.apply(context, args.concat(newArgs));
        }
    }

函数柯里化

柯里化:是把接受多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数、并且返回结果的新函数

不会立即求值,而是到了需要的时候再去求值

js 复制代码
function curry(fn, ...args){
    return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args)
}

复用参数:

js 复制代码
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(check(/[a-z]+/g, 'test')); // true

// Currying后
function curryingCheck(reg) {
    return function (txt) {
        return reg.test(txt)
    }
}

// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest'));  // false
console.log(hasLetter('21212')); // false

链式处理,使用promise封装一个ajax

js 复制代码
function fetchData(url){
    let promise = new Promise(function(resolve, reject){
        let xhr = new XMLHttpRequest()
        // 新建一个http请求
        xhr.open('GET', url, true)
        // 监听状态的改变流转
        xhr.onreadystatechange = function(){
            if(this.readyState === 4){
                if(this.status === 200){
                    resolve(this.response)
                }else{
                    reject(new Error(this.statusText))
                }
            }
        }
        xhr.onerror = function(){...}
        xhr.responseType = 'xxx'
        
        xhr.send()
    })
    return promise
}

// 调用
fetchData('xxxx').then(res=>{....})

手写浅拷贝和深拷贝

(1)浅拷贝的实现

js 复制代码
// 快速方式,将obj2的属性给到obj1
Object.assign(obj1, obj2)
// 浅拷贝数组
arr.slice()
// 手写实现
function shallowCopy(object){
    if(!object || (typeof object !== 'object') ){
        return
    }
    let result = Array.isArray(object) ? [] : {}
    for(let key in object){
        if(object.hasOwnProperty(key)){
            result[key] = object[key]
        }
    }
    return result
}

(2)深拷贝

js 复制代码
// 快速方式 或者第三方库,lodashde的cloneDeep()
JSON.parse(JSON.stringify(obj))
// 手写深拷贝
function deepCopy(object){
    if(!object || (typeof object !== 'object') ){
        return
    }
    let result = Array.isArray(object) ? [] : {}
    for(let key in object){
        if(object.hasOwnProperty(key)){
            result[key] = typeof object[key] === 'object' ? deepCopy(object[key]) : object[key]
        }
    }
    return result
}

数组操作题

(1)数组拍平

js 复制代码
let arr = [1,[2,[3,4,5]]]
function flatten(arr){
    let result = []
    for(let i = 0;i<arr.length;i++){
        if(Array.isArray(arr[i])){
            result = result.concat(flatten(arr[i]))
        }else{
            result.push(arr[i])
        }
    }
    return result
}

// 使用
flatten(arr)

(2)数组乱序输出

js 复制代码
// 1.取出数组的一个元素 => 0 和 第一个随机
// 2.以此第二个元素 => 1 和 另一个随机索引进行交换
// 3.依次遍历
let arr1 = [1,2,3,4,5,6]
function disOrder(arr){
    for (let i = 0; i < arr.length; i++) {
      const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
      [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
    }
    console.log(arr)
}
disOrder(arr1)

(3)类数组到数组的转化

js 复制代码
Array.from(a_arr)
Array.prototype.slice.call(a_arr)
Array.prototype.splice.call(a_arr,0)
Array.prototype.concat.call([],a_arr)

(4)转换类型:对象=>树

js 复制代码
function arrayToTree(list, parentId) {
  const arr = []
  list.forEach(item => {
    if (item.parentId === parentId) {
      // 找到了匹配的节点
      // 当前节点的id 和 当前节点的子节点的pid是相等的
      const children = arrayToTree(list, item.id) 
      // 找到的节点的子节点
      item.children = children
      arr.push(item)
    }
  })
  return arr
}
// 使用示例
const array = [
  { id: 1, name: 'A', parentId: null },
  { id: 2, name: 'B', parentId: 1 },
  { id: 3, name: 'C', parentId: 1 },
  { id: 4, name: 'D', parentId: 2 },
  { id: 5, name: 'E', parentId: 2 },
  { id: 6, name: 'F', parentId: 3 },
  { id: 7, name: 'G', parentId: null }
];
const tree = arrayToTree(array, null);
console.log(tree);

(5)数组去重

  • 利用Array.from() Set是es6新增的数据结构,似于数组,但它的一大特性就是所有元素都是唯一的,没有重复的值,我们一般称为集合

Array.from()就是将一个类数组对象或者可遍历对象转换成一个真正的数组,也是ES6的新增方法

  • 利用includes
  • 利用map
  • 利用单层for循环
  • 利用Array.filter和map
相关推荐
布瑞泽的童话21 分钟前
无需切换平台?TuneFree如何搜罗所有你爱的音乐
前端·vue.js·后端·开源
白鹭凡34 分钟前
react 甘特图之旅
前端·react.js·甘特图
2401_8628867838 分钟前
蓝禾,汤臣倍健,三七互娱,得物,顺丰,快手,游卡,oppo,康冠科技,途游游戏,埃科光电25秋招内推
前端·c++·python·算法·游戏
书中自有妍如玉1 小时前
layui时间选择器选择周 日月季度年
前端·javascript·layui
Riesenzahn1 小时前
canvas生成图片有没有跨域问题?如果有如何解决?
前端·javascript
f8979070701 小时前
layui 可以使点击图片放大
前端·javascript·layui
忘不了情1 小时前
左键选择v-html绑定的文本内容,松开鼠标后出现复制弹窗
前端·javascript·html
世界尽头与你1 小时前
HTML常见语法设计
前端·html
写bug如流水1 小时前
【Git】Git Commit Angular规范详解
前端·git·angular.js