JavaScript---函数柯里化、扁平化(数组、对象)、防抖、节流、浅拷贝、深拷贝

31 阅读5分钟

函数柯里化

定义:函数柯里化是一种将一个接受多个参数的函数转化为一系列接受一个参数的函数。

柯里化函数具有以下好处

  • 参数复用
  • 函数组合
  • 延迟执行
function curry(fn) {
  return function curried(...args) {
    // 如果参数数量足够,直接调用原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } 
    // 否则返回一个新函数继续收集参数
    else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

// 使用示例
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);

console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6

数组扁平化

flat()方法

  • 不传参数或传 1 时,默认扁平化第一层嵌套数组。
  • 传入数字 n 表示将数组扁平化到第 n 层深度。
  • 如果传入 Infinity 作为参数,那么不管嵌套多深,都会被扁平化为一维数组。
let arr = [1,[2,[3,[4,5]]]];
// 只会扁平化一层
console.log(arr.flat(1));//[1,2,[3,[4,5]]]
// 扁平化两层
console.log(arr.flat(2));//[1,2,3,[4,5]]
// 全部扁平化
console.log(arr.flat(Infinity));//[1,2,3,4,5]

toString()方法 + split()

let arr = [1,[2,[3,[4,5]]]];
//toString方法变成字符串
console.log(arr.toString());//'1,2,3,4,5'
arr = arr.toString().split(",").map(function (item){
    return +item;
})
console.log(arr)//[1,2,3,4,5]

reduce方法递归

function myFlat(arr){
 return arr.reduce((result,item)=>{
   return result.concat(Array.isArray(item)?myFlat(item):item)
 },[])
}

对象扁平化

给出flattenObject具体实现
const nestedObj = {
    a: 1,
    b: {
        c: 2,
        d: { e: 3}
    }
};
const flattenedObj = flattenObject(nestedObj);
console.log(flattenedObj); 
// 输出: { 'a': 1, 'b.c': 2, 'b.d.e': 3 }



function flattenObject(obj, parentKey = '', result = {}) {
    // 遍历对象 obj 的所有可枚举属性
    for (const key in obj) {
        // 检查属性是否是对象自身的属性,而非原型链上的属性
        if (obj.hasOwnProperty(key)) {
            // 构建新的键名,如果 parentKey 存在,使用点号连接 parentKey 和当前 key
            const newKey = parentKey ? `${parentKey}.${key}` : key;

            // 检查当前属性值是否为非 null 的对象,并且不是数组
            if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
                // 递归调用 flattenObject 处理嵌套对象,更新 parentKey 和 result
                flattenObject(obj[key], newKey, result);
            } else {
                // 如果是基本类型值,直接将新键和对应的值添加到 result 对象中
                result[newKey] = obj[key];
            }
        }
    }
    // 返回扁平化后的对象
    return result;
}
console.log( flattenObject(nestedObj)) //{ a: 1, 'b.c': 2, 'b.d.e': 3 }

数组排序

防抖(指触发事件后在N秒内函数只能执行一次,如果在N秒内又触发了事件,则会重新计算函数执行时间。)

使用场景:(适合多次事件一次响应的情况)

  • 按钮绑定表单提交的post事件,用户或许又再次点击按钮提交,防止多次提交的发生
  • 对于输入框连续输入进行AJAX验证时,用函数防抖能有效减少请求次数
  • 判断scroll是否滑到底部,滚动事件+函数防抖
function debounce(fn,delay){
  let timer
  return function (){
   let context = this
   timer && clearInterval(timer)
   timer = setTimeout(function(){
     fn.call(context)
   },delay)
  }
}

节流----(连续触发事件,但是在N秒中只执行一次)

使用场景:(适合大量事件按事件做平均分配触发)

  • 游戏中的刷新率
  • DOM元素拖拽
  • Canvas画笔功能
function throttle(fun, delay) {
    let timeout;
    return function () {
        let _this = this;
        let arg = arguments;

        if(!timeout){
            timeout = setTimeout(()=>{
                timeout = null;
                fun.apply(_this,arguments);
            },delay)
        }
    }
}

浅拷贝、深拷贝

赋值操作

  • 基本数据类型赋值传递的是存放在栈内的数据
  • 引用类型赋值传递的是他们存放在栈内的地址,他们的数据存放在这个地址指向的堆内存里

浅拷贝(只复制对象的一层属性)

定义:在浅拷贝中,创建一个新对象,然后将原始对象的属性值复制到新对象中

  • 属性值为基本数据类型,直接复制其值
  • 属性值时对象或数组等引用类型,复制其引用,新对象和原始对象共享这些引用

浅拷贝的方法

  • 对于对象类型的浅拷贝
    • Object.assign(target,...sources)
    • 展开运算符{...sources}
  • 对于数组的浅拷贝的方法
    • Array.prototype.concat()
    • Array.prototype.slice()
    • Array.from()方法
/***/  对象浅拷贝
var obj_old = {
  name:'test',
  age:12,
  like:{
    food:'bread',
    drink:'milk'
  }
}
/**/ 第一种
let obj_new = Object.assgin({},obj_old) 
/**/ 第二种
let obj_new = {...obj_old}
console.log(obj_old === obj_new)  // false(新旧对象的地址不一样)
console.log(obj_old.name === obj_new.name) // true(基本数据类型的值是相同的)
console.log(obj_old.like === obj_new.like)  // true(共享同一块内存空间)


/***/数组浅拷贝
let arr_old = [1,2,{name:'test'}]
// 第一种
let arr_new = arr_old.concat()
// 第二种
let arr_new = arr_old.slice()
console.log(arr_old === arr_new) // false
console.log(arr_old[2] === arr_new[2]) // true

深拷贝(层层拷贝,拷贝完后,两个对象互不影响)

定义:递归地复制一个对象及其嵌套的所有属性,直到所有嵌套的属性都是基本类型为止。

深拷贝的方法

  • JSON.parse(JSON.stringify(obj))
    • 缺陷:1: 拷贝Date会变成字符串,2:拷贝RegExp会变成空对象{}3:拷贝对象的值中如果有'函数',
    • ‘undefined’,'symbol',键值对丢失
  • lodash工具包提供的_.cloneDeep函数
  • 全局structedClone()方法
  • 递归实现
const obj_old = {
  name: 'Tom',
  age: 15,
  hobby: ['eat', 'game'],
  favorite: {
      food: 'bread',
      drink: {
        dname: 'milk',
        color: 'white',
      },
  }
}
// 第一种
const obj_new = JSON.parse(JSON.stringify(obj_old))
// 第二种
let obj_new = _.cloneDeep(obj_old)
// 第三种
const obj_new = {} 
function deepClone(newObj, oldObj){ 
    for (const key in oldObj) { 
        if (oldObj[key] instanceof Object) { 
            newObj[key] = {} 
            deepClone(newObj[key], oldObj[key]) 
        } else if (oldObj[key] instanceof Array) { 
            newObj[key] = [] deepClone(newObj[key], oldObj[key]) 
        } else { 
            newObj[key] = oldObj[key] 
        } 
    } 
} 
deepClone(obj_new, obj_old)

console.log(obj_old)
console.log(obj_new)
console.log(obj_old.name === obj_new.name)  // true
console.log(obj_old.favorite === obj_new.favorite)  //false
console.log(obj_old.favorite.drink === obj_new.favorite.drink) // false

JS设计任务队列,控制请求最大并发数

场景:前端页面需要同时发送20个请求,服务端有限制,需要前端控制并发数,保证最多同时发送10个请求

class RequestQueue {
  constructor(maxConcurrent) {
    this.maxConcurrent = maxConcurrent; // 设置最大并发数
    this.currentRunning = 0; // 当前正在运行的请求数
    this.queue = []; // 等待执行的请求队列
  }

  // 将请求封装成一个函数,推入队列,并尝试执行
  enqueue(url) {
    return new Promise((resolve, reject) => {
      const task = () => {
        // 当请求开始时,currentRunning 加 1
        this.currentRunning++;
        sendRequest(url).then(resolve).catch(reject).finally(() => {
          // 请求结束后,currentRunning 减 1,并尝试执行下一个请求
          this.currentRunning--;
          this.dequeue();
        });
      };
      this.queue.push(task);
      this.dequeue(); // 每次添加请求后尝试执行请求
    });
  }

  dequeue() {
    // 如果当前运行的请求小于最大并发数,并且队列中有待执行的请求
    if (this.currentRunning < this.maxConcurrent && this.queue.length) {
      // 从队列中取出一个请求并执行
      const task = this.queue.shift();
      task();
    }
  }
}

// 这个函数是模拟发送请求的,实际中你可能需要替换成真实的请求操作
function sendRequest(url) {
  console.log(`Sending request to ${url}`);
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`Response received from ${url}`);
      resolve(`Result from ${url}`);
    }, Math.random() * 2000); // 随机延时以模拟请求处理时间
  });
}

// 使用 RequestQueue
const requestQueue = new RequestQueue(3); // 假设我们限制最大并发数为3

// 模拟批量请求
const urls = ['url1', 'url2', 'url3', 'url4', 'url5', 'url6'];
urls.forEach(url => {
  requestQueue.enqueue(url).then(result => {
    console.log(result);
  });
});