数组扁平化 ES6的flat方法 flat() 方法将以指定的深度递归遍历数组,并将所有元素与遍历的子数组中的元素合并到一个新数组中以返回。
1 2 3 4 const  arr = [1 ,[2 ,[3 ,[4 ,5 ]]],6 ]const  res = JSON .stringify (arr).replace (/\[|\]/g ,'' )const  res2 = JSON .parse ('['  + res + ']' )console .log (res2)
 
使用正则 首先是使用 JSON.stringify 把 arr转为字符串接着使用正则把字符串里面的 [ 和 ] 去掉然后再拼接数组括号转为数组对象。
1 2 3 4 const  arr = [1 ,[2 ,[3 ,[4 ,5 ]]],6 ]const  res = JSON .stringify (arr).replace (/\[|\]/g ,'' )const  res2 = JSON .parse ('['  + res + ']' )console .log (res2)
 
递归 1 2 3 4 5 6 7 8 9 10 11 12 13 const  array = []const   fn  = (arr )=>{    for (let  i = 0 ;i<arr.length ; i++){         if (Array .isArray (arr[i])){             fn (arr[i])         }         else  {             array.push (arr[i])         }     } } fn (arr)console .log (array)
 
reduce reduce 方法: 可以用来给数组求和
concat() 方法用于连接两个或多个数组。
concat() 方法不会更改现有数组,而是返回一个新数组,其中包含已连接数组的值。
1 2 3 4 5 6 const  newArr  = (arr )=>{            return  arr.reduce ((pre,cur )=> {                 return  pre.concat (Array .isArray (cur) ? newArr (cur) : cur)             },[])         } console .log (newArr (arr),"reduce方法" )
 
使用栈的思想实现 flat 函数 栈(stack)又名堆栈 ,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15   const  newArr = [];   const  stack = [].concat (arr);       const  val = stack.pop ();        stack.push (...val);      } else  {       newArr.unshift (val);      }   }   return  newArr; } let  arr = [12 , 23 , [34 , 56 , [78 , 90 , 100 , [110 , 120 , 130 , 140 ]]]];console .log (flat (arr));
 
深拷贝和浅拷贝 知识点讲解 1、什么是数据类型? 数据分为基本数据类型 (String, Number, Boolean, Null, Undefined,Symbol) 和引用数据类型。
基本数据类型的特点:直接存储在栈 (stack) 中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
2、ES11新增了什么数据类型? bigint!
bigint使用方式:
1 2 3 4 5 let  max=Number .MAX_SAFE_INTEGER console .log (BigInt (max))console .log (BigInt (max)+BigInt (1 ))console .log (BigInt (max)+BigInt (2 ))console .log (BigInt (max)+BigInt (4 ))
 
3、为什么要使用深拷贝? 我们希望在改变新的数组(对象)的时候,不改变原数组(对象)
4、赋值和浅拷贝的区别? 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
JavaScript 数组复制操作创建浅拷贝。(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)。 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。 
浅拷贝 浅拷贝是一点一点地拷贝一个对象。它将创建一个新对象。此对象具有原始对象属性值的精确副本。如果属性是基本类型,它将复制基本类型的值;如果属性是内存地址(引用类型),则复制内存地址。因此,如果一个对象更改地址,另一个对象将受到影响。也就是说,默认复制构造函数只复制浅层复制中的对象(依次复制成员),即只复制对象空间,而不复制资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var obj1 ={  name:'张三',  age:8,  pal:['王五','王六','王七'] } var obj3 = shallowCopy(obj1) function shallowCopy (src){  var newObj = {};  for(var prop in src ){      console.log(prop)      if(src.hasOwnProperty(prop)){          newObj[prop] = src[prop]      }  }  return newObj }  obj3.name = '李四'  obj3.pal[0] = '王麻子'     console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']} console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']} 
 
深拷贝 方法一:递归实现深拷贝 实现深拷贝 原理的递归方法:遍历对象、数组甚至内部都是基本的数据类型,然后复制它们,即深度复制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var  obj = {          name :"test" ,        main :{            a :1 ,            b :2         },        fn :function ( ){                    },         friends :[1 ,2 ,3 ,[22 ,33 ]]    }      function  copy (obj ){         let  newobj = null ;                                 if (typeof (obj) == 'object'  && obj !== null ){                                newobj = obj instanceof  Array ? [] : {};                                      for (var  i in  obj){                   newobj[i] = copy (obj[i])             }         }else {             newobj = obj         }             console .log ('77' ,newobj)       return  newobj;        }       var  obj2 = copy (obj)     obj2.name  = '修改成功'      obj2.main .a  = 100     console .log (obj)    console .log (obj2) 
 
方法二:递归实现深拷贝 
1 2 3 4 5 6 7 8 9 10 11 function  deepCopy (target ) {        if  (typeof  target === 'object' ) {                 const  newTarget = Array .isArray (target) ? [] : Object .create (null )                 for  (const  key in  target) {                         newTarget[key] = deepCopy (target[key])                 }                 return  newTarget         } else  {                 return  target         } } 
 
方法三:递归实现深拷贝 
1 2 3 4 5 6 7 8 9 10 11 12 13 function  deepCopy (target, h = new  Map  ) {        if  (typeof  target === 'object' ) {                 if  (h.has (target)) return  h.get (target)                 const  newTarget = Array .isArray (target) ? [] : Object .create (null )                 for  (const  key in  target) {                         newTarget[key] = deepCopy (target[key], h)                 }                 h.set (target, newTarget)                 return  newTarget         } else  {                 return  target         } } 
 
方法四:递归实现深拷贝 哈希表 WeakMap 代替 Map
WeakMap 的键是弱引用,告诉 JS 垃圾回收机制,当键回收时,对应 WeakMap 也可以回收,更适合大量数据深拷
链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dq08bm/ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function  deepCopy (target, h = new  WeakMap  ) {    if  (typeof  target === 'object' ) {       if  (h.has (target)) return  h.get (target)       const  newTarget = Array .isArray (target) ? [] : Object .create (null )       for  (const  key in  target) {         newTarget[key] = deepCopy (target[key], h)       }       h.set (target, newTarget)       return  newTarget     } else  {       return  target     } } 
 
防抖节流 防抖 防抖是指短时间内大量触发同一事件,只会在最后一次事件完成后延迟执行一次函数。例如,在输入用户名的过程中,需要反复验证用户名。此时,您应该等待用户停止输入,然后进行验证,否则将影响用户体验。 防抖实现的原理是在触发事件后设置计时器。在计时器延迟过程中,如果事件再次触发,则重置计时器。在没有触发事件之前,计时器将再次触发并执行相应的功能。
声明定时器
返回函数
一定时间间隔,执行回调函数
回调函数
已执行:清空定时器 未执行:重置定时器
1 2 3 4 5 6 7 8 9 10 function  debounce (fn, delay ) {        let  timer = null          return  function  (...args ) {                 if  (timer) clearTimeout (timer)                 timer = setTimeout (() =>  {                         timer = null                          fn.apply (this , args)                 }, (delay + '' ) | 0  || 1000  / 60 )         } } 
 
节流 节流是指每隔一段时间就执行一次函数。就像未拧紧的水龙头一样,水龙头每隔一段时间就会滴水。即使在这段时间管道里有更多的水,水龙头也不会掉更多的水。
节流的原理是在触发事件后设置计时器。在计时器延迟过程中,即使事件再次触发,计时器的延迟时间也不会改变。在计时器执行功能之前,计时器不会复位。
声明定时器
返回函数
一定时间间隔,执行回调函数
回调函数
已执行:清空定时器 未执行:返回
1 2 3 4 5 6 7 8 9 10 11 function  throttle (fn, interval ) {        let  timer = null          return  function  (...args ) {                 if  (timer) return                  timer = setTimeout (() =>  {                         timer = null                          fn.apply (this , args)                 }, (interval +'' )| 0  || 1000  / 60 )         } } 
 
第四章:for..in 和 for..of 用法 for..in for…in 循环只遍历可枚举属性(包括它的原型链上的可枚举属性)。像 Array 和 Object 使用内置构造函数所创建的对象都会继承自 Object.prototype 和 String.prototype 的不可枚举属性,例如 String 的 indexOf() 方法或 Object 的 toString() 方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。
for of Q: ES6 的 for of 可以遍历对象吗?
A: ES6 的 “for of” 不能遍历对象。
原因:ES6 中引入了 Iterator 接口,只有提供了 Iterator 接口的数据类型才可以使用 “for-of” 来循环遍历;而普通对象默认没有提供 Iterator 接口,因此无法用 “for-of” 来进行遍历。
第五章 数组 从数组 [1,2,3,4,5,6] 中找出值为 2 的元素 本章讲实现数组中找值问题的 2 种方法。
方法一:filter() filter() 方法创建一个新数组, 包含通过所提供函数实现的测试的所有元素。
1 2 3 4 function  isBigEnough (element ) {  return  element == 2 ; } var  filtered = [1 ,2 ,3 ,4 ,5 ,6 ].filter (isBigEnough);
 
方法二:find() find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
1 2 3 4 5 var  arr = [1 , 2 , 3 , 4 , 5 , 6 ];function  finds (x ) {    return  x === '2' ; } console .log (arr.find (finds));
 
常用方法总结: 
方法名 
参数 
描述 
返回值 
 
 
includes 
searchElement,fromlndex 
判断数组中是否包含指定的值 
布尔值 
 
indexOf 
searchElement,fromlndex 
查找元素在数组中首次出现的索引值 
索引值,或者-1 
 
lastIndexOf 
searchElement,fromlndex 
查找元素在数组中最后一次出现的索引值 
索引值,或者-1 
 
some 
callback[,thisArg] 
判断数组中是否有符合条件的元素 
布尔值 
 
every 
callback[,thisArg] 
判断数组中是否每个元素都符合条件 
布尔值 
 
filter 
callback[,thisArg] 
返回符合条件的所有元素组成的数组 
数组 
 
find 
callback[,thisArg] 
返回数组中符合条件的第一个元素 
数组中的元素,或者undefined 
 
findIndex 
callback[,thisArg] 
返回符合条件的第一个元素的索引 
索引值,或者-1 
 
不使用filter如何实现筛选元素? 在 JavaScript 中,如果你想筛选数据但不想使用内置的 filter() 函数,你同样可以使用多种方法来实现。以下是几种常见的替代方案:
使用 for 循环 : 你可以通过基本的 for 循环来遍历数组,并在循环内部使用条件语句来决定是否将元素添加到新数组中。
1 2 3 4 5 6 7 8 let  numbers = [1 , 2 , 3 , 4 , 5 , 6 ];let  evenNumbers = [];for  (let  i = 0 ; i < numbers.length ; i++) {    if  (numbers[i] % 2  === 0 ) {         evenNumbers.push (numbers[i]);     } } console .log (evenNumbers);  
 
 
使用 forEach() 方法 :forEach() 方法允许你对数组的每个元素执行一次提供的函数。这不是一个返回新数组的方法,但你可以在内部构建一个新数组。
1 2 3 4 5 6 7 8 let  numbers = [1 , 2 , 3 , 4 , 5 , 6 ];let  evenNumbers = [];numbers.forEach (function (number ) {     if  (number % 2  === 0 ) {         evenNumbers.push (number);     } }); console .log (evenNumbers);  
 
 
使用 for…of 循环 :for...of 循环提供了一种简洁的方法来迭代数组(和其他可迭代对象)的值。
1 2 3 4 5 6 7 8 let  numbers = [1 , 2 , 3 , 4 , 5 , 6 ];let  evenNumbers = [];for  (let  number of  numbers) {    if  (number % 2  === 0 ) {         evenNumbers.push (number);     } } console .log (evenNumbers);  
 
 
使用 reduce() 方法 : 虽然 reduce() 方法通常用于从数组中派生一个单一值(如总和),但它也可以用来构建新数组,通过累加符合条件的元素。
1 2 3 4 5 6 7 8 let  numbers = [1 , 2 , 3 , 4 , 5 , 6 ];let  evenNumbers = numbers.reduce ((accumulator, current ) =>  {    if  (current % 2  === 0 ) {         accumulator.push (current);     }     return  accumulator; }, []); console .log (evenNumbers);  
 
 
这些方法提供了灵活性来选择适合不同情景的数据筛选方式,就像在 Python 中一样,你可以根据实际的应用需求和性能考量来选择最合适的方法。
第六章:两栏布局和三栏布局 两栏布局 两栏布局非常常见,往往是以一个定宽栏和一个自适应的栏并排展示存在。
方法一:浮动布局 
使用 float 左浮左边栏 
右边模块使用 margin-left 撑出内容块做内容展示 
为父级元素添加 BFC,防止下方元素飞到上方内容 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <style >     .box {         overflow : hidden; 添加BFC     }     .left  {         float : left;         width : 200px ;         background-color : gray;         height : 400px ;     }     .right  {         margin-left : 210px ;         background-color : lightgray;         height : 200px ;     } </style > <div  class ="box" >     <div  class ="left" > 左边</div >      <div  class ="right" > 右边</div >  </div > 链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dq4vvd/ 
 
方法二:flex 弹性布局 flex 容器的一个默认属性值: align-items: stretch;
这个属性导致了列等高的效果。 为了让两个盒子高度自动,需要设置: align-items: flex-start。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <style >     .box {         display : flex;     }     .left  {         width : 100px ;     }     .right  {         flex : 1 ;     } </style > <div  class ="box" >     <div  class ="left" > 左边</div >      <div  class ="right" > 右边</div >  </div > 
 
三栏布局 
实现三栏布局中间自适应的布局方式有:
两边使用 float,中间使用 margin 
两边使用 absolute,中间使用 margin 
两边使用 float 和负 margin 
flex 实现 
grid 网格布局 
 
方法一:两边使用 float,中间使用 margin 需要将中间的内容放在 html 结构最后,否则右侧会臣在中间内容的下方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <style >     .wrap  {         background : #eee ;         overflow : hidden; <!-- 生成BFC,计算高度时考虑浮动的元素 -->         padding : 20px ;         height : 200px ;     }     .left  {         width : 200px ;         height : 200px ;         float : left;         background : coral;     }     .right  {         width : 120px ;         height : 200px ;         float : right;         background : lightblue;     }     .middle  {         margin-left : 220px ;         height : 200px ;         background : lightpink;         margin-right : 140px ;     } </style > <div  class ="wrap" >     <div  class ="left" > 左侧</div >      <div  class ="right" > 右侧</div >      <div  class ="middle" > 中间</div >  </div > 
 
方法二:两边使用 absolute,中间使用 margin 基于绝对定位的三栏布局:注意绝对定位的元素脱离文档流,相对于最近的已经定位的祖先元素进行定位。无需考虑 HTML 中结构的顺序
左右两边使用绝对定位,固定在两侧。
中间占满一行,但通过 margin 和左右两边留出 10px 的间隔
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <style >   .container  {     position : relative;   }      .left ,   .right ,   .main  {     height : 200px ;     line-height : 200px ;     text-align : center;   }   .left  {     position : absolute;     top : 0 ;     left : 0 ;     width : 100px ;     background : green;   }   .right  {     position : absolute;     top : 0 ;     right : 0 ;     width : 100px ;     background : green;   }   .main  {     margin : 0  110px ;     background : black;     color : white;   } </style > <div  class ="container" >   <div  class ="left" > 左边固定宽度</div >    <div  class ="right" > 右边固定宽度</div >    <div  class ="main" > 中间自适应</div >  </div > 
 
方法三:两边使用 float 和负 margin 实现过程:
中间使用了双层标签,外层是浮动的,以便左中右能在同一行展示
左边通过使用负 margin-left:-100%,相当于中间的宽度,所以向上偏移到左侧
右边通过使用负 margin-left:-100px,相当于自身宽度,所以向上偏移到最右侧
缺点:
增加了 .main-wrapper 一层,结构变复杂
使用负 margin,调试也相对麻烦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <style >   .left ,   .right ,   .main  {     height : 200px ;     line-height : 200px ;     text-align : center;   }   .main-wrapper  {     float : left;     width : 100% ;   }   .main  {     margin : 0  110px ;     background : black;     color : white;   }   .left ,   .right  {     float : left;     width : 100px ;     margin-left : -100% ;     background : green;   }   .right  {     margin-left : -100px ;    } </style > <div  class ="main-wrapper" >   <div  class ="main" > 中间自适应</div >  </div > <div  class ="left" > 左边固定宽度</div > <div  class ="right" > 右边固定宽度</div > 链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/d6h6x2/ 
 
方法四:使用 flex 实现 利用 flex 弹性布局,可以简单实现中间自适应。
实现过程:
仅需将容器设置为 display:flex; ,
盒内元素两端对其,将中间元素设置为 100% 宽度,或者设为 flex:1 ,即可填充空白
盒内元素的高度撑开容器的高度
优点:
结构简单直观
可以结合 flex 的其他功能实现更多效果,例如使用 order 属性调整显示顺序,让主体内容优先加载,但展示在中间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <style  type ="text/css" >     .wrap  {         display : flex;         justify-content : space-between;     }     .left ,     .right ,     .middle  {         height : 100px ;     }     .left  {         width : 200px ;         background : coral;     }     .right  {         width : 120px ;         background : lightblue;     }     .middle  {         background : #555 ;         width : 100% ;         margin : 0  20px ;     } </style > <div  class ="wrap" >     <div  class ="left" > 左侧</div >      <div  class ="middle" > 中间</div >      <div  class ="right" > 右侧</div >  </div > 
 
方法五:grid 网格布局 跟 flex 弹性布局一样的简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <style >     .wrap  {         display : grid;         width : 100% ;         grid-template-columns : 300px  auto 300px ;     }     .left ,     .right ,     .middle  {         height : 100px ;     }     .left  {         background : coral;     }     .right  {         background : lightblue;     }     .middle  {         background : #555 ;     } </style > <div  class ="wrap" >     <div  class ="left" > 左侧</div >      <div  class ="middle" > 中间</div >      <div  class ="right" > 右侧</div >  </div > 
 
第七章:实现多行文本溢出的省略 本章讲实现多行文本溢出的省略问题的 2 种方法。
方法一:使用定位伪元素遮盖末尾文字 给父元素设置: 
1 2 3 4 5 overflow : hidden;line-height : 20px ;text-align : justify;position : relative;   
 
给父元素设置伪元素 ::after ,并为其设置属性: 
1 2 3 4 5 6 7 8 9 10      content: "...";/* 省略号是放在文本最后面的 */      width: 1em;/* 设置伪元素的宽度为1em,是为了遮盖的时候正好遮盖中原来的一个字的大小*/      background-color: pink;/* 设置与父元素相同的背景颜色,同理,也是为了和原来的内容样式保持一致*/      position: absolute;/*设置定位,其位置就是文本的右下角 */      right: 0;bottom: 0; 链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dg4wsi/ 
 
方法二: 利用旧版弹性盒 步骤:
给容器元素类型转换为 display:-webkit-box ;
设置弹性盒子垂直排列 -webkit-box-orient:vertical ;
控制要显示的行数 -webkit-line-clamp:数值 ;
溢出隐藏 overflow:hidden ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <meta  http-equiv ="X-UA-Compatible"  content ="IE=edge" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0" >      <title > Document</title >      <style >                   .box {             width : 200px ;                          background-color : pink;                          line-height : 20px ;                          display : -webkit-box;                          -webkit-box-orient: vertical;                          -webkit-line-clamp: 3 ;             overflow : hidden;         }               </style > </head > <body >     <div  class ="box" >          我是文本我是文本我是文本我是文我是文我是文我是文我是文我是文我是文我是是文本我是文本我是文本我是文本我是文本我是文本我是文本我是文本我是文本     </div >  </body > </html > 
 
第八章:用 CSS 实现三角符号 本章讲用 CSS 实现三角符号问题的 8 种类型。
类型一:上三角 
类型二:下三角 
类型三:左三角 
类型四:右三角 
类型五:左下三角 
类型六:右下三角 
类型七:右上三角 
类型八:左上三角 
 
第九章:实现九宫格布局 本章讲实现九宫格布局的 4 种方法。
方法一:flex 方法二:float 方法三:grid [方法四:table
第十章:单行多行文字隐藏显示省略号 本章讲解实现单行多行文字隐藏显示省略号的不同方法。
单行文字实现 文字单行隐藏:给它设定一个宽和高,对于文字超出部分进行隐藏,多余的部分用省略号来表示。
1 2 3 text-overflow : ellipsis;overflow : hidden ;white-space : nowrap;
 
多行文字实现 多行隐藏:多行隐藏的 div 的高度不能设置,让其自动 3 行隐藏即可。
1 2 3 4 overflow  : hidden;-webkit-line-clamp: 3 ; -webkit-box-orient: vertical; display : -webkit-box;
 
第十一章 函数柯里化 本章讲解实现函数柯里化的不同方法。
什么是函数柯里化 函数柯里化是指将使用多个参数的函数转化成一系列使用一个参数的函数的技术, 它返回一个新的函数, 这个新函数去处理剩余的参数
1 2 3 4 5 6 function  add (a, b ) {    return  a + b; } add (1 , 2 ) 
 
函数柯里化的实现 实现思路: 通过函数的 length 属性获取函数的形参个数, 形参的个数就是所需参数的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function  curry (fn ) {  return  _curry.call (this , fn, fn.length ); } function  _curry (fn, len, ...args ) {  return  function (...params ) {     const  _args = args.concat (params);     if  (_args.length  >= len) {       return  fn.apply (this , _args);     } else  {       return  _curry.call (this , fn, len, ..._args);     }   } } function  add  (a, b, c, d) {  return  a + b + c + d; } const  addCurry = curry (add);console .log (addCurry (1 )(2 )(3 )(4 )) console .log (addCurry (1 , 2 , 3 )(4 )) 
 
函数柯里化的作用 1、参数复用: 本质上来说就是降低通用性, 提高适用性
假如一个函数需要两个参数, 其中一个参数可能多次调用并不会发生更改, 比如商品打折的力度, 此时可以根据折扣进行封装
2、提前返回
经典实例: 元素绑定事件监听器, 区分 IE 浏览器的 attachEvent 方法
3、延迟计算: 柯里化函数不会立即执行计算,第一次只是返回一个函数,后面的调用才会进行计算
经典面试题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 console .log (add (1 )(2 )(3 )) console .log (add (1 , 2 , 3 )(4 )) console .log (add (1 )(2 )(3 )(4 )(5 )) function  add ( ) {  let  args = [...arguments ];   function  _add ( ) {     args = args.concat ([...arguments ]);     return  _add;   }   _add.toString  = function ( ) {     return  args.reduce ((pre, cur ) =>  {       return  pre + cur;     })   }   return  _add; } 
 
第十二章 图片懒加载 本章讲实现图片懒加载问题的 3 种方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <meta  http-equiv ="X-UA-Compatible"  content ="IE=edge" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0" >      <title > Document</title >      <style >          img  {             background : url ('./img/loading.gif' ) no-repeat center;             width : 250px ;             height : 250px ;             display : block;         }      </style > </head > <body >     <img  src ="./img/pixel.gif"  data-url ="./img/1.jpeg" >      <img  src ="./img/pixel.gif"  data-url ="./img/2.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/3.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/4.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/5.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/6.webp" >      <script >          let  imgs = document .getElementsByTagName ('img' )                  fn ()                  window .onscroll  = lazyload (fn, true )         function  fn ( ) {                          let  clietH = window .innerHeight  || document .documentElement .clientHeight  || document .body .clientHeight ;             var  scrollTop = window .pageYOffset  || document .documentElement .scrollTop  || document .body .scrollTop ;             console .log (clietH, scrollTop);             for  (let  i = 0 ; i < imgs.length ; i++) {                 let  x = scrollTop + clietH - imgs[i].offsetTop                   if  (x > 0 ) {                     imgs[i].src  = imgs[i].getAttribute ('data-url' );                  }             }         }                    function  lazyload (fn, immediate ) {             let  timer = null              return  function  ( ) {                 let  context = this ;                 if  (!timer) {                     timer = setTimeout (() =>  {                         fn.apply (this )                         timer = null                      }, 200 )                 }             }         }      </script > </body > </html > 链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dzv8fv/ 
 
方法二:滚动监听 + getBoundingClientRect() 
rectObject.top:元素上边到视窗上边的距离;
 
rectObject.right:元素右边到视窗左边的距离;
 
rectObject.bottom:元素下边到视窗上边的距离;
 
rectObject.left:元素左边到视窗左边的距离;
 
rectObject.width:元素自身的宽度;
 
rectObject.height:元素自身的高度;
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <meta  http-equiv ="X-UA-Compatible"  content ="IE=edge" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0" >      <title > Document</title >      <style >          * {             margin : 0 ;             padding : 0 ;         }         img  {             background : url ('./img/loading.gif' ) no-repeat center;             width : 250px ;             height : 250px ;             display : block;         }      </style > </head > <body >     <img  src ="./img/pixel.gif"  data-url ="./img/1.jpeg" >      <img  src ="./img/pixel.gif"  data-url ="./img/2.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/3.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/4.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/5.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/6.webp" >      <script >          let  imgs = document .getElementsByTagName ('img' )                  fn ()                  window .onscroll  = lazyload (fn, true )         function  fn ( ) {                          let  offsetHeight = window .innerHeight  || document .documentElement .clientHeight              Array .from (imgs).forEach ((item, index ) =>  {                 let  oBounding = item.getBoundingClientRect ()                  console .log (index, oBounding.top , offsetHeight);                 if  (0  <= oBounding.top  && oBounding.top  <= offsetHeight) {                     item.setAttribute ('src' , item.getAttribute ('data-url' ))                 }             })         }                  function  lazyload (fn, immediate ) {             let  timer = null              return  function  ( ) {                 let  context = this ;                 if  (!timer) {                     timer = setTimeout (() =>  {                         fn.apply (this )                         timer = null                      }, 200 )                 }             }         }      </script > </body > </html > 链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dzwibj/ 
 
方法三: intersectionObserve() 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <meta  http-equiv ="X-UA-Compatible"  content ="IE=edge" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0" >      <title > Document</title >      <style >          img  {             background : url ('./img/loading.gif' ) no-repeat center;             width : 250px ;             height : 250px ;             display : block;         }      </style > </head > <body >     <img  src ="./img/pixel.gif"  data-url ="./img/1.jpeg" >      <img  src ="./img/pixel.gif"  data-url ="./img/2.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/3.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/4.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/5.jfif" >      <img  src ="./img/pixel.gif"  data-url ="./img/6.webp" >      <script >          let  imgs = document .getElementsByTagName ('img' )                  let  io = new  IntersectionObserver (function  (entires ) {                          entires.forEach (item  =>  {                                  let  oImg = item.target                                                    if  (item.intersectionRatio  > 0  && item.intersectionRatio  <= 1 ) {                     oImg.setAttribute ('src' , oImg.getAttribute ('data-url' ))                 }             })         })         Array .from (imgs).forEach (element  =>  {             io.observe (element)           });      </script > </body > </html > 
 
第十三章:bind、apply、call 的用法 本章讲解 call、apply、bind 的相同点和不同点以及手撕代码。
相同点和不同点 相同点 
三个都是用于改变 this 指向;
 
接收的第一个参数都是 this 要指向的对象;
 
都可以利用后续参数传参。
 
 
不同点 
call 和 bind 传参相同,多个参数依次传入的;
 
apply 只有两个参数,第二个参数为数组;
 
call 和 apply 都是对函数进行直接调用,而 bind 方法不会立即调用函数,而是返回一个修改 this 后的函数。
 
 
call的用法 1 fn.call (thisArg, arg1, arg2, arg3, ...) 
 
调用 fn.call 时会将 fn 中的 this 指向修改为传入的第一个参数 thisArg;将后面的参数传入给 fn,并立即执行函数 fn。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let  obj = {        name : "xiaoming" ,         age : 24 ,         sayHello : function  (job, hobby ) {             console .log (`我叫${this .name} ,今年${this .age} 岁。我的工作是: ${job} ,我的爱好是: ${hobby} 。` );         }     }     obj.sayHello ('程序员' , '看美女' );      let  obj1 = {         name : "lihua" ,         age : 30      }          obj.sayHello .call (obj1, '设计师' , '画画' );  
 
手撕call的写法:
1 2 3 4 5 6 7 8 9 10 11 12 Function.prototype.myCall = function(_this, ...args) {         if (!_this) _this = Object.create(null)         _this.fn = this         const res = _this.fn(...args)         delete _this.fn         return res } // 使用 function sum (a, b) {         return this.v + a + b } sum.myCall({v: 1}, 2, 3) // 6 
 
apply的用法 1 apply (thisArg, [argsArr])
 
fn.apply 的作用和 call 相同:修改 this 指向,并立即执行 fn。区别在于传参形式不同, apply 接受两个参数,第一个参数是要指向的 this 对象,第二个参数是一个数组,数组里面的元素会被展开传入 fn , 作为 fn 的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 let  obj = {        name : "xiaoming" ,         age : 24 ,         sayHello : function  (job, hobby ) {             console .log (`我叫${this .name} ,今年${this .age} 岁。我的工作是: ${job} ,我的爱好是: ${hobby} 。` );         }     }     obj.sayHello ('程序员' , '看美女' );      let  obj1 = {         name : "lihua" ,         age : 30      }     obj.sayHello .apply (obj1, ['设计师' , '画画' ]);  
 
手撕 apply 的写法:
1 2 3 4 5 6 7 8 9 10 11 12 Function .prototype  .myApply  = function (_this, args = [] ) {        if  (!_this) _this = Object .create (null )         _this.fn  =this          const  res = _this.fn (...args)         delete  _this.fn          return  res } function  sum  (a, b) {        return  this .v  + a + b } sum.myApply ({v : 1 }, [2 , 3 ])  
 
bind 的用法 1 bind (thisArg, arg1, arg2, arg3, ...)
 
fn.bind 的作用是只修改 this 指向,但不会立即执行 fn ;会返回一个修改了 this 指向后的 fn 。需要调用才会执行: bind(thisArg, arg1, arg2, arg3, …)()。bind 的传参和 call 相同。
手撕bind的写法:
第一个参数接收 this 对象
 
返回函数,根据使用方式
 
 
链接:https://leetcode.cn/leetbook/read/interview-coding-frontend/dzi9zh/ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Function .prototype  .myBind  = function (_this, ...args ) {        const  fn = this          return  function  F (...args2 ) {                 return  this  instanceof  F ? new  fn (...args, ...args2)                 : fn.apply (_this, args.concat (args2))         } } function  Sum  (a, b) {        this .v = (this .v  || 0 )+ a + b         return  this  } const  NewSum  = Sum .myBind ({v : 1 }, 2 )NewSum (3 ) new  NewSum (3 ) 
 
第十四章:手写 new 
第一参数作为构造函数,其余参数作为构造函数参数 
继承构造函数原型创建新对象 
执行构造函数 
结果为对象,返回结果,反之,返回新对象 
 
1 2 3 4 5 6 7 8 9 10 11 function  myNew (...args ) {        const  Constructor  = args[0 ]         const  o = Object .create (Constructor .prototype  )         const  res = Constructor .apply (o, args.slice (1 ))         return  res instanceof  Object  ? res : o } function  P (v ) {        this .v  = v } const  p = myNew (P, 1 ) 
 
第十五章:promise 的用法 本章讲解 promise 的用法及对应函数的手撕代码。
promise 相关概念 回调方法:就是将一个方法 func2 作为参数传入另一个方法 func1 中,当 func1 执行到某一步或者满足某种条件的时候才执行传入的参数 func2
Promise 是 ES6 引入的异步编程的新解决方案。
Promise 对象三种状态:初始化、成功、失败 pending-进行中、resolved-已完成、rejected-已失败
就好像,你跟你女朋友求婚,她跟你说她要考虑一下,明天才能给你答案,这就是承诺(promise)。同时,这也是一个等待的过程(pending),然后你就等,等到明天你女朋友给你答复,同意(resolved)或者拒绝(rejected),如果同意就准备结婚了,如果不同意就等下次再求婚,哈哈哈。
promise 是用来解决两个问题的:
回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
 
promise 可以支持多个并发的请求,获取并发请求中的数据
 
这个 promise 可以解决异步的问题,本身不能说 promise 是异步的
 
 
promise 基本用法 这样构造 promise 实例,然后调用 .then.then.then 的编写代码方式,就是 promise。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let  p = new  Promise ((resolve, reject ) =>  {              if  () {     resolve ()   } else  {     reject ()   } }) p.then (() =>  {                                       }, () =>  {      }) 
 
声明一个 Promise 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 new  Promise ((resolve, reject ) =>  {         console .log ("开始求婚。" )     console .log ("。。。。。" )     console .log ("考虑一下。" )     setTimeout (() =>  {         if  (isHandsome || isRich) {              resolve ('我同意!' )         } else  {              reject ("拒绝:我们八字不合" )         }     }, 2000 ) }) 
 
Promise.prototype.then 方法 已成功 resolved 的回调和已失败 rejected 的回调 
1 2 3 4 5 6 p.then (function (value ){       console .log (value); }, function (season ){       console .log (season); }); 
 
getNumber()
1 2 3 4 5 6 7 8 9 getNumber ().then (function (data ){     console .log ('resolved' );     console .log (data); }) .catch (function (reason ){     console .log ('rejected' );     console .log (reason); }); 
 
Promise.prototype.catch 方法 catch() 的作用是捕获 Promise 的错误
其实它和 then 的第二个参数一样,用来指定 reject 的回调,用法是这样:
在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。请看下面的代码:
1 2 3 4 5 promise.then (     () =>  { console .log ('this is success callback' ) } ).catch (     (err ) =>  { console .log (err) } ) 
 
效果和写在 then 的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js ,而是会进到这个 catch 方法中。请看下面的代码:
1 2 3 4 5 6 7 8 9 10 getNumber ().then (function (data ){     console .log ('resolved' );     console .log (data);     console .log (somedata);   }) .catch (function (reason ){     console .log ('rejected' );     console .log (reason); }); 
 
Promise.all() 方法 有了 all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
「谁跑的慢,以谁为准执行回调」
Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调
1 2 3 4 5 Promise .all ([runAsync1 (), runAsync2 (), runAsync3 ()]) .then (function (results ){     console .log (results); }); 
 
Promise.all 手撕代码题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const  myPromiseAll  =(arr )=>{     let  result = [];      return  new  Peomise  ((resolve,reject )=> {          for  (let  i=0 ;i<arr.length ;i++){              arr[i].then (data => {                  result[i] = data;                  if (result.length  === arr.length )                  {                      resolve (result)                    }              },reject)          }      }) } 
 
Promise.race() 方法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28   var  p = new  Promise (function (resolve, reject ){         var  img = new  Image ();         img.onload  = function ( ){             resolve (img);         }         img.src  = 'xxxxxx' ;     });     return  p; } function  timeout ( ){    var  p = new  Promise (function (resolve, reject ){         setTimeout (function ( ){             reject ('图片请求超时' );         }, 5000 );     });     return  p; } Promise .race ([requestImg (), timeout ()]) .then (function (results ){     console .log (results); }) .catch (function (reason ){     console .log (reason); }); 
 
Promise.race()手撕代码用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function  promiseRace (promises ) {     if  (!Array .isArray (promises)) {          throw  new  Error ("promises must be an array" )      }     return  new  Promise (function  (resolve, reject ) {          promises.forEach (p  =>              Promise .resolve (p).then (data  =>  {                  resolve (data)                  }, err  =>  {                  reject (err)              })          )      })  } 
 
Promise.prototype.finally() 方法 finally 方法用于指定无论 Promise 对象的最终状态如何,都将执行 finally。Finally 不接受参数。Finally 独立于先前的执行状态,不依赖于先前的运行结果。
1 2 3 4 5 6 7 const  promise4 = new  Promise ((resolve, reject ) =>  {  console .log (x + 1 );}); promise4   .then (() =>  {     console .log ("你好" );}).catch ((err ) =>  {     console .log (err);}).finally (() =>  {     console .log ("finally" );}); 
 
第十六章:解析 URL 什么是解析URL? js解析url,就是将如下url: const url = https://www.baidu.com/m?f=8&ie=utf-8&rsv_bp=1&tn=monline_3_dg&wd=session 解析为:
1 2 3 4 5 {f :'8' , ie :'utf-8' rsv_bp :'1' ,tn :'monline_3_dg' ,wd :'session' }
 
利用 splice 分割 + 循环依次取出 
解析 url 并将其存储在新对象中,因此初始化一个空对象,让 obj = {} 
首先判断 url 后面是否有 ?传参, 如果没有 ? 传参,直接返回空对象 
 
if (url.indexOf(‘?’) < 0) return obj
用 ? 进行参数分割 let arr = url.split(‘?’) 
 
此时的效果是将 ? 前后,一分为二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function  queryURLparams (url ) {    let  obj = {}     if  (url.indexOf ('?' ) < 0 ) return  obj     let  arr = url.split ('?' )     url = arr[1 ]     let  array = url.split ('&' )     for  (let  i = 0 ; i < array.length ; i++) {         let  arr2 = array[i]         let  arr3 = arr2.split ('=' )         obj[arr3[0 ]] = arr3[1 ]     }     return  obj } console .log (queryURLparams (url)); var  url = "https://www.baidu.com/m?f=8&ie=utf-8&rsv_bp=1&tn=monline_3_dg&wd=session" ;    function  getURL (url ){       let  str = url.split ("?" )[1 ];       let  str1 = str.split ("&" );       let  obj = {};       for (let  i = 0 ; i<str1.length ; i++){           let  str2 = str1[i].split ("=" );           let  key = str2[0 ];           let  value = str2[1 ];           obj[key] = value;       }       return  obj;   }   console .log (getURL (url)) 
 
正则 + arguments 
正则匹配规则 /([^?=&]+)=([^?=&]+)/g 
利用 replace 替换 
用伪数组进行键值对拼接 
 
1 2 3 4 5 6 7 8 function  queryURLparamsRegEs5 (url ) {    let  obj = {}     let  reg = /([^?=&]+)=([^?=&]+)/g      url.replace (reg, function ( ) {         obj[arguments [1 ]] = arguments [2 ]     })     return  obj } 
 
正则 + ..arg 
就是用 ES6 的 …arg 
其实和 arguments 差不多 ,就是 arguments 是伪数组,…arg 是真数组 
 
1 2 3 4 5 6 7 8 function  queryURLparamsRegEs6 (url ) {    let  obj = {}     let  reg = /([^?=&]+)=([^?=&]+)/g      url.replace (reg, (...arg ) =>  {         obj[arg[1 ]] = arg[2 ]     })     return  obj } 
 
利用URL构造函数解析整个URL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function  parseUrlParams (url ) {     const  params = {};         const  urlObject = new  URL (url);   const  queryString = urlObject.search ;      if  (queryString) {     const  searchParams = new  URLSearchParams (queryString);          searchParams.forEach ((value, key ) =>  {              if  (params.hasOwnProperty (key)) {                  if  (Array .isArray (params[key])) {           params[key].push (value);         } else  {            params[key] = [params[key], value];         }       } else  {                  params[key] = value;       }     });   }   return  params; } const  url = 'https://example.com/?name=John&age=30&hobby=reading&hobby=cycling' ;const  params = parseUrlParams (url);console .log (params);
 
技术栈:react+next.js+qiankun+vue2.6+vue-router+vuex+websocket+node.js+axios+echarts 描述:项目包括轻量看板、瀑布模式、迭代模式三种项目管理方式,涵盖项目管理全生命周期的综合平台,包括工作台、项目管理、需求池、统计报表、甘特图等模块,实现从市场需求、项目管理到应用上线交付的业务闭环。 运用qiankun构建微前端,使用前端路由守卫进行权限管理,实现子应用间状态的有效共享和同步更新; 实现权限控制系统,实现了精细化的权限校验逻辑,前端采用recoil全局状态管理,高效管理用户状态信息,支持基于角色的ui显示逻辑、动态控制操作权限; 开发需求管理组件和实现需求评审流程配置功能,运用vue.js、vuex等技术实现复杂表单的动态生成和校验,处理多层嵌套组件的通信和状态管理,优化异步数据的加载和处理逻辑,实现了需求数据的动态加载和实时更新; 封装富文本编辑器组件,通过vue的响应式原理和web存储api实现内容的实时捕获、安全存储和快速恢复,大幅提升用户编辑体验,并有效防止内容丢失; 开发文件批量上传与管理组件,支持文件多种操作(如上传、预览、关联文档等),通过客户端验证减少服务器压力,提高项目文档管理的效率和用户体验; 开发甘特图视图,包括精确的时间线对齐、动态数据绑定、以及用户交互的拖拽和缩放功能。采用虚拟滚动和延迟加载技术解决大规模数据渲染带来的性能挑战,开发自定义列配置组件,利用localstorage实现了用户配置的跨会话持久化,从而提升了用户体验。实现的视图切换和动态数据过滤功能,提高甘特图的查看和操作效率。 技术栈:react+redux+nextjs+typescript+webpack+antd+axios+websocket+sentry 描述:基于开源开发的数据管理平台,允许用户创建、管理和共享数据表、表单和仪表板。项目采用了模块化的设计和现代web技术,提供了强大的数据操作和协作功能。支持多种视图类型和扩展性,包含个人待办事项、团队待办事项和模板库,通过引入自定义待办事项和丰富的模板库,极大提升了用户在项目管理、数据分析和团队协作方面的效率。 登录系统集成无痕验证码技术,通过服务器端自动执行,减少了传统验证码对用户体验的干扰。该系统全面涵盖了用户数据处理、前端校验、无痕验证,以及服务器端的认证与状态管理,确保用户操作的便捷性和系统的安全性; 实现动态的页面导航,通过postmessage实现了iframe中的应用与主页面的安全通信,根据不同的应用场景,如分享视图、嵌入视图、个人路径等,实现了条件性导航逻辑,在不同上下文中都能够正确地引导用户到期望的视图; 通过sentry用于错误监控和性能优化,提高错误追踪的准确性和应用性能监控的效率,设计自定义hooks用于封装复杂的业务逻辑和api请求,包括节点的增删改查、个人待办事项的管理、共享和收藏节点操作等; 负责甘特图视图内任务内容的动态渲染组件开发,使用延迟加载和按需渲染的策略,减少了页面的初始加载时间和运行时的性能开销,采用 react和 konva库实现客户端绘制,通过 next.js动态导入技术优化加载速度; 开发个人待办事项管理组件,通过hooks和高阶组件等技术实现功能模块的解耦和复用,利用自定义hooks处理数据请求和副作用,在处理共享目录的逻辑时,通过redux和封装自定义hooks,实现了状态的更新和数据的同步; 负责轻任务应用的主导航栏组件,支持在不同设备和屏幕尺寸上的操作体验,处理了用户登录、空间选择、通知计数等多个状态的同步和更新,根据用户权限动态调整导航栏展示的功能模块,对于非管理员用户隐藏空间管理入口,对于删除空间的用户限制访问等,实现了细粒度的功能控制。