• 前端面试手撕编程之ES+算法


    js中字符串,json数据的处理【匹配url、邮箱、电话,版本号,千位分割,判断回问】-CSDN博客


    手写实现js类/方法【改变this、map、Object、发布订阅、promise】-CSDN博客

    目录

    防抖debounce

    被打断的时间+原定time执行一次

    并且执行的是最后一次触发的事件

    应用

    search搜索联想

    window触发resize

    节流throttle

    间隔至少time执行

    无视打断,time内执行的是第一次触发的事件

    应用

    鼠标不断点击触发

    监听滚动事件

    深拷贝

    A.JSON.parse() 和 JSON.stringify()

    B.遍历+递归

    arr instanceof Array ?  [] :  {}

    C.改进版

    Symbol属性:Object.getOwnPropertySymbols

    不可枚举属性:Reflect.ownKeys

    循环引用:WeakMap

    setTimeout()

    倒计时

    js:递归调用

    react:useEffect(()=>{},[cnt])

    requestAnimationFrame代替settimeout更精准

    setTimeout、setInterval最短时长为4ms

    setTimeout固定时长后执行

    setInterval间隔固定时间重复执行

    setTimeout模拟实现setInterval

    setInterval模拟实现setTimeout

    数组去重

    [...new Set(arr)]

    arr.filter((value, index, self) => self.indexOf(value) === index)

    浮点数不精确性

    原因:部分十进制小数在二进制中无限循环

    解决

    面试:toFixed+精度

    业务:第三方库

    bignumber.js:处理大数字和高精度运算

    Object

    new :Fn=[...arguments].shift()

    *寄生组合式继承:call,create,constructor

    Object.defineProperty(obj, prop, descriptor)


    防抖debounce

    被打断的时间+原定time执行一次

    并且执行的是最后一次触发的事件

    触发事件后在 n 秒内函数至多只能执行一次,如果在 n 秒内又触发了事件,会重计算函数执行时间,延迟执行

    本该在-----执行

    但是--再次触发

    于是-------执行

    必须等待完time,即才能执行,time期间触发都会从0计时

    1. function debounce(fun,time) {
    2. let flag // 定义状态
    3. return function () {
    4. clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
    5. flag = setTimeout(() => {
    6. fun.call(this,arguments)//拿到正确的this对象,即事件发生的dom
    7. }, time)
    8. }
    9. }

    应用

    search搜索联想

    用户在不断输入值时,用防抖来节约请求资源

    window触发resize

    不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

    节流throttle

    间隔至少time执行

    无视打断,time内执行的是第一次触发的事件

    连续触发事件但是在 n 秒中只执行一次函数。

    定时在-----执行

    在--再次触发,无效

    最终在-----执行

    两种方式可以实现,分别是时间戳版和定时器版。

    1. function throttle(fun, time) {
    2. let flag // 定义一个空状态
    3. return function () { // 内部函数访问外部函数形成闭包
    4. if (!flag) { // 状态为空执行
    5. flag = setTimeout(() => {
    6. fns.apply(this, arguments) // 改变this指向 把 event 事件对象传出去
    7. flag = null // 状态为空
    8. }, time)
    9. }
    10. }
    11. }

    应用

    鼠标不断点击触发

    mousedown(单位时间内只触发一次)

    监听滚动事件

    比如是否滑到底部自动加载更多,用throttle来判断

    深拷贝

    A.JSON.parse() 和 JSON.stringify()

    • 数据类型限制:不能复制、undefinedDateSymbol函数等等其他的js数据类型,其实也很好理解,因为JSON只支持以上列举的数据类型,其他要么被忽略要么报错
    • 丢失原型的信息:最终拷贝的对象原型只会是Object.prototype
    • 不能处理循环引用:对象包含循环引用,JSON.stringify() 将会导致错误。
    • 性能问题:对于大型复杂对象深度嵌套的对象,容易产生性能问题
    1. const originalObject = { a: 1, b: { c: 2 } };
    2. // 通过将对象转换为字符串,再将其解析回对象来实现深拷贝
    3. const deepCopy = JSON.parse(JSON.stringify(originalObject));
    4. console.log(deepCopy); // 输出深拷贝后的对象

    B.遍历+递归

    arr instanceof Array ?  [] :  {}

    1. function deepClone(obj) {
    2. if (typeof obj !== "object" || obj === null) {
    3. return obj;
    4. }
    5. const res = Array.isArray(obj) ? [] : {};
    6. for (const key in obj) {
    7. if (obj.hasOwnProperty(key)) {
    8. res[key] = deepClone(obj[key]);
    9. }
    10. }
    11. return res;
    12. }
    1. 要拷贝的属性只能是对象自身的,不能是原型链上的

    2. 拷贝的属性不仅仅是string类型的,还有symbol类型的

    3. 对象身上可能会有循环引用,需要处理,而不是陷入死循环

    C.改进版

    Symbol属性:Object.getOwnPropertySymbols

    Symbols 在 for...in 迭代中不可枚举。

    Object.getOwnPropertyNames() 不会返回 symbol 对象的属性

    1. // 复制 Symbol 类型属性
    2. const symbols = Object.getOwnPropertySymbols(obj);
    3. for (const symbolKey of symbols) {
    4. res[symbolKey] = deepClone(obj[symbolKey]);
    5. }

    不可枚举属性:Reflect.ownKeys

    Reflect.ownKeys简单来说会返回一个数组,属性包括:对象自身的所有属性(包括所有可枚举的不可枚举的stringsymbol类型

    1. // 拿到对象身上所有的属性,返回一个数组
    2. const keys = Reflect.ownKeys(obj);
    3. for (const key of keys) {
    4. res[key] = deepClone(obj[key]);
    5. }

    循环引用:WeakMap

    在每次对复杂数据类型进行深拷贝前保存其值,如果下次又出现了该值,就不再进行拷贝,直接截止。

    WeakMap 中的键是弱引用,当键对象在其他地方没有被引用时,它可以被垃圾回收,这有助于防止内存泄漏

    1. function deepClone(obj, clones = new WeakMap()) {
    2. // 如果是原始类型或 null,直接返回
    3. if (typeof obj !== "object" || obj === null) {
    4. return obj;
    5. }
    6. // 检查是否已经克隆过该对象,防止循环引用
    7. if (clones.has(obj)) {
    8. return clones.get(obj);
    9. }
    10. // 判断是否数组还是普通对象
    11. const res = Array.isArray(obj) ? [] : {};
    12. // 将当前对象添加到克隆Map中
    13. clones.set(obj, res);
    14. // 拿到所有 key
    15. const keys = Reflect.ownKeys(obj);
    16. for (const key of keys) {
    17. res[key] = deepClone(obj[key], clones);
    18. }
    19. return res;
    20. }
    21. // test
    22. const obj = {
    23. foo: "bar",
    24. num: 42,
    25. arr: [1, 2, 3],
    26. obj: { dd: true },
    27. [Symbol("symbol属性")]: "hello",
    28. };
    29. // 循环引用
    30. obj.newObj = obj;
    31. const copy = deepClone(obj);
    32. console.log(copy);

    setTimeout()

    倒计时

    1. //返回值timeoutID是一个正整数,表示定时器的编号。
    2. let timeoutID = scope.setTimeout(function[, delay]),//delay表示最小等待时间,真正等待时间取决于前面排队的消息
    3. clearTimeout(timeoutID) //取消该定时器。

    js:递归调用

    1. var c = 10; // 设置初始倒计时时间为10秒
    2. var t; // 声明一个变量用来存储 setTimeout 的返回值
    3. function timedCount() {
    4. c -= 1; // 每次调用函数,倒计时时间减一秒
    5. if (c === 0) {
    6. clearTimeout(t); // 当倒计时为零时,清除定时器
    7. console.log("倒计时结束"); // 可以在这里添加任何你想要执行的操作,比如提示用户倒计时结束
    8. return;
    9. }
    10. console.log("剩余时间:" + c + "秒"); // 在控制台打印剩余时间,你也可以将其显示在页面上的某个元素中
    11. t = setTimeout(function () {
    12. timedCount();
    13. }, 1000); // 每隔一秒调用一次自身,实现倒计时
    14. }
    15. timedCount(); // 调用函数开始倒计时

    react:useEffect(()=>{},[cnt])

    1. import React, { useState, useEffect } from 'react';
    2. const Countdown = ({ initialCount }) => {
    3. const [count, setCount] = useState(initialCount);
    4. useEffect(() => {
    5. // 如果倒计时已经结束,则不再减少计时
    6. if (count === 0) return;
    7. // 设置定时器
    8. const timerId = setTimeout(() => {
    9. setCount(count - 1);
    10. }, 1000);
    11. // 清理函数
    12. return () => clearTimeout(timerId);
    13. }, [count]);
    14. return (
    15. <div>
    16. <h1>倒计时: {count} 秒h1>
    17. div>
    18. );
    19. };
    20. export default Countdown;

    requestAnimationFrame代替settimeout更精准

    1. startTime = Date.now();
    2. requestAnimationFrame(function update() {
    3. var currentTime = Date.now();
    4. var deltaTime = currentTime - startTime;
    5. if (deltaTime >= 1000) { // 每秒更新一次
    6. startTime = currentTime;
    7. timedCount(); // 递归调用自身,实现倒计时
    8. } else {
    9. requestAnimationFrame(update); // 继续请求下一帧
    10. }
    11. });

    setTimeout、setInterval最短时长为4ms

    setTimeout固定时长后执行

    setInterval间隔固定时间重复执行

    setTimeout模拟实现setInterval

    1. // 使用闭包实现
    2. function mySetInterval(fn, t) {
    3. let timer = null;
    4. function interval() {
    5. fn();
    6. timer = setTimeout(interval, t);
    7. }
    8. interval();
    9. return {
    10. // cancel用来清除定时器
    11. cancel() {
    12. clearTimeout(timer);
    13. }
    14. };
    15. }

    setInterval模拟实现setTimeout

    1. function mySetTimeout(fn, time) {
    2. let timer = setInterval(() => {
    3. clearInterval(timer);
    4. fn();
    5. }, time);
    6. }
    7. // 使用
    8. mySetTimeout(() => {
    9. console.log(1);
    10. }, 2000);

    数组去重

    [...new Set(arr)]

    arr.filter((value, index, self) => self.indexOf(value) === index)

    浮点数不精确性

    原因:部分十进制小数在二进制中无限循环

    Java、Python、C++、JS都是基于 IEEE 754 标准的双精度浮点数来表示数字

    0.2的二进制表示是无限循环的,近似等于0.0011001100110011001100

     

     IEEE-754 标准下双精度浮点数由三部分组成,分别如下:

    • sign(符号): 占 1 bit, 表示正负;
    • exponent(指数): 占 11 bit,表示范围;
    • mantissa(尾数): 占 52 bit,表示精度,多出的末尾如果是 1 需要进位;

    阅读 JavaScript 浮点数陷阱及解法可以了解到公式的由来。

    解决

    面试:toFixed+精度

    1. /*** method **
    2. * add / subtract / multiply /divide
    3. * floatObj.add(0.1, 0.2) >> 0.3
    4. * floatObj.multiply(19.9, 100) >> 1990
    5. *
    6. */
    7. var floatObj = function() {
    8. /*
    9. * 判断obj是否为一个整数
    10. */
    11. function isInteger(obj) {
    12. return Math.floor(obj) === obj
    13. }
    14. /*
    15. * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
    16. * @param floatNum {number} 小数
    17. * @return {object}
    18. * {times:100, num: 314}
    19. */
    20. function toInteger(floatNum) {
    21. var ret = {times: 1, num: 0}
    22. if (isInteger(floatNum)) {
    23. ret.num = floatNum
    24. return ret
    25. }
    26. var strfi = floatNum + ''
    27. var dotPos = strfi.indexOf('.')
    28. var len = strfi.substr(dotPos+1).length
    29. var times = Math.pow(10, len)
    30. var intNum = Number(floatNum.toString().replace('.',''))
    31. ret.times = times
    32. ret.num = intNum
    33. return ret
    34. }
    35. /*
    36. * 核心方法,实现加减乘除运算,确保不丢失精度
    37. * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
    38. *
    39. * @param a {number} 运算数1
    40. * @param b {number} 运算数2
    41. * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
    42. * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
    43. *
    44. */
    45. function operation(a, b, digits, op) {
    46. var o1 = toInteger(a)
    47. var o2 = toInteger(b)
    48. var n1 = o1.num
    49. var n2 = o2.num
    50. var t1 = o1.times
    51. var t2 = o2.times
    52. var max = t1 > t2 ? t1 : t2
    53. var result = null
    54. switch (op) {
    55. case 'add':
    56. if (t1 === t2) { // 两个小数位数相同
    57. result = n1 + n2
    58. } else if (t1 > t2) { // o1 小数位 大于 o2
    59. result = n1 + n2 * (t1 / t2)
    60. } else { // o1 小数位 小于 o2
    61. result = n1 * (t2 / t1) + n2
    62. }
    63. return result / max
    64. case 'subtract':
    65. if (t1 === t2) {
    66. result = n1 - n2
    67. } else if (t1 > t2) {
    68. result = n1 - n2 * (t1 / t2)
    69. } else {
    70. result = n1 * (t2 / t1) - n2
    71. }
    72. return result / max
    73. case 'multiply':
    74. result = (n1 * n2) / (t1 * t2)
    75. return result
    76. case 'divide':
    77. result = (n1 / n2) * (t2 / t1)
    78. return result
    79. }
    80. }
    81. // 加减乘除的四个接口
    82. function add(a, b, digits) {
    83. return operation(a, b, digits, 'add')
    84. }
    85. function subtract(a, b, digits) {
    86. return operation(a, b, digits, 'subtract')
    87. }
    88. function multiply(a, b, digits) {
    89. return operation(a, b, digits, 'multiply')
    90. }
    91. function divide(a, b, digits) {
    92. return operation(a, b, digits, 'divide')
    93. }
    94. // exports
    95. return {
    96. add: add,
    97. subtract: subtract,
    98. multiply: multiply,
    99. divide: divide
    100. }
    101. }();

    业务:第三方库

    bignumber.js:处理大数字和高精度运算

    Object

    new :Fn=[...arguments].shift()

    "_new"函数,该函数会返回一个对象,

    该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:

    1. 创建一个新对象
    2. 获取函数参数
    3. 将新对象的原型对象和函数参数的原型连接起来
    4. 将新对象和参数传给构造器执行
    5. 如果构造器返回的不是对象,那么就返回第一个新对象
    1. const _new = function() {
    2. const object1 = {}
    3. const Fn = [...arguments].shift()
    4. object1.__proto__ = Fn.prototype
    5. const object2 = Fn.apply(object1, arguments)
    6. return object2 instanceof Object ? object2 : object1
    7. }

    *寄生组合式继承:call,create,constructor

    通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
    1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
    2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性

    1. 在"Human"构造函数的原型上添加"getName"函数
    2. 在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性
    3. Object.create函数返回一个对象,该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来
    4. 最后修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
    5. 在”Chinese“构造函数的原型上添加”getAge“函数
    1. function Human(name) {
    2. this.name = name
    3. this.kingdom = 'animal'
    4. this.color = ['yellow', 'white', 'brown', 'black']
    5. }
    6. Human.prototype.getName = function() {
    7. return this.name
    8. }
    9. function Chinese(name,age) {
    10. Human.call(this,name)//call函数借助”Human“的构造器来获得通用属性
    11. this.age = age
    12. this.color = 'yellow'
    13. }
    14. //返回的对象__proto__属性为对象参数的原型
    15. Chinese.prototype = Object.create(Human.prototype)//使用现有的对象来作为新创建对象的原型
    16. //修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
    17. Chinese.prototype.constructor = Chinese
    18. Chinese.prototype.getAge = function() {
    19. return this.age
    20. }

    Object.defineProperty(obj, prop, descriptor)

  • 相关阅读:
    UI设计公司成长日记2:修身及持之以恒不断学习是要务
    MIT6.S081Lab1: Xv6 and Unix utilities
    登录拦截器从session中获取信息失败(session失效问题)
    纯函数 和 函数柯里化 ( 函数式编程 )05
    反向输出一个三位数
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcsncmp
    UDP 编程不能太随意
    SWUST OJ#99 欧几里得博弈
    C++多重继承
    MySQL之DQL
  • 原文地址:https://blog.csdn.net/qq_28838891/article/details/133134051