• 2022前端常考手写面试题总结


    实现JSON.parse

    var json = '{"name":"cxk", "age":25}';
    var obj = eval("(" + json + ")");
    
    
    • 1
    • 2
    • 3

    此方法属于黑魔法,极易容易被xss攻击,还有一种new Function大同小异。

    封装异步的fetch,使用async await方式来使用

    (async () => {
        class HttpRequestUtil {
            async get(url) {
                const res = await fetch(url);
                const data = await res.json();
                return data;
            }
            async post(url, data) {
                const res = await fetch(url, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });
                const result = await res.json();
                return result;
            }
            async put(url, data) {
                const res = await fetch(url, {
                    method: 'PUT',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(data)
                });
                const result = await res.json();
                return result;
            }
            async delete(url, data) {
                const res = await fetch(url, {
                    method: 'DELETE',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(data)
                });
                const result = await res.json();
                return result;
            }
        }
        const httpRequestUtil = new HttpRequestUtil();
        const res = await httpRequestUtil.get('http://golderbrother.cn/');
        console.log(res);
    })();
    
    
    • 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

    树形结构转成列表(处理菜单)

    [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]
    
    • 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

    实现代码如下:

    function treeToList(data) {
      let res = [];
      const dfs = (tree) => {
        tree.forEach((item) => {
          if (item.children) {
            dfs(item.children);
            delete item.children;
          }
          res.push(item);
        });
      };
      dfs(data);
      return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Function.prototype.call

    call唯一不同的是,call()方法接受的是一个参数列表

    Function.prototype.call = function(context = window, ...args) {
      if (typeof this !== 'function') {
        throw new TypeError('Type Error');
      }
      const fn = Symbol('fn');
      context[fn] = this;
    
      const res = context[fn](...args);
      delete context[fn];
      return res;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    参考:前端手写面试题详细解答

    实现发布-订阅模式

    class EventCenter{
      // 1. 定义事件容器,用来装事件数组
        let handlers = {}
    
      // 2. 添加事件方法,参数:事件名 事件方法
      addEventListener(type, handler) {
        // 创建新数组容器
        if (!this.handlers[type]) {
          this.handlers[type] = []
        }
        // 存入事件
        this.handlers[type].push(handler)
      }
    
      // 3. 触发事件,参数:事件名 事件参数
      dispatchEvent(type, params) {
        // 若没有注册该事件则抛出错误
        if (!this.handlers[type]) {
          return new Error('该事件未注册')
        }
        // 触发事件
        this.handlers[type].forEach(handler => {
          handler(...params)
        })
      }
    
      // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
      removeEventListener(type, handler) {
        if (!this.handlers[type]) {
          return new Error('事件无效')
        }
        if (!handler) {
          // 移除事件
          delete this.handlers[type]
        } else {
          const index = this.handlers[type].findIndex(el => el === handler)
          if (index === -1) {
            return new Error('无该绑定事件')
          }
          // 移除事件
          this.handlers[type].splice(index, 1)
          if (this.handlers[type].length === 0) {
            delete this.handlers[type]
          }
        }
      }
    }
    
    
    • 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

    验证是否是邮箱

    function isEmail(email) {
        var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
        return regx.test(email);
    }
    
    • 1
    • 2
    • 3
    • 4

    Function.prototype.bind

    Function.prototype.bind = function(context, ...args) {
      if (typeof this !== 'function') {
        throw new Error("Type Error");
      }
      // 保存this的值
      var self = this;
    
      return function F() {
        // 考虑new的情况
        if(this instanceof F) {
          return new self(...args, ...arguments)
        }
        return self.apply(context, [...args, ...arguments])
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现每隔一秒打印 1,2,3,4

    // 使用闭包实现
    for (var i = 0; i < 5; i++) {
      (function(i) {
        setTimeout(function() {
          console.log(i);
        }, i * 1000);
      })(i);
    }
    // 使用 let 块级作用域
    for (let i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      }, i * 1000);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    手写常见排序

    冒泡排序

    冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到 length - 1 的位置。

    function bubbleSort(list) {
      var n = list.length;
      if (!n) return [];
    
      for (var i = 0; i < n; i++) {
        // 注意这里需要 n - i - 1
        for (var j = 0; j < n - i - 1; j++) {
          if (list[j] > list[j + 1]) {
            var temp = list[j + 1];
            list[j + 1] = list[j];
            list[j] = temp;
          }
        }
      }
      return list;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    快速排序

    快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作

    ffunction quickSort(arr) {
      if (arr.length<=1){
        return arr;
      }
      var baseIndex = Math.floor(arr.length/2);//向下取整,选取基准点
      var base = arr.splice(baseIndex,1)[0];//取出基准点的值,
      // splice 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
      // slice方法返回一个新的数组对象,不会更改原数组
      //这里不能直接base=arr[baseIndex],因为base代表的每次都删除的那个数
      var left=[];
      var right=[];
      for (var i = 0; i<arr.length; i++){
        // 这里的length是变化的,因为splice会改变原数组。
        if (arr[i] < base){
          left.push(arr[i]);//比基准点小的放在左边数组,
        }
      }else{
        right.push(arr[i]);//比基准点大的放在右边数组,
      }
      return quickSort(left).concat([base],quickSort(right));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    选择排序

    function selectSort(arr) {
      // 缓存数组长度
      const len = arr.length;
      // 定义 minIndex,缓存当前区间最小值的索引,注意是索引
      let minIndex;
      // i 是当前排序区间的起点
      for (let i = 0; i < len - 1; i++) {
        // 初始化 minIndex 为当前区间第一个元素
        minIndex = i;
        // i、j分别定义当前区间的上下界,i是左边界,j是右边界
        for (let j = i; j < len; j++) {
          // 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j
          if (arr[j] < arr[minIndex]) {
            minIndex = j;
          }
        }
        // 如果 minIndex 对应元素不是目前的头部元素,则交换两者
        if (minIndex !== i) {
          [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
        }
      }
      return arr;
    }
    // console.log(selectSort([3, 6, 2, 4, 1]));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    插入排序

    function insertSort(arr) {
      for (let i = 1; i < arr.length; i++) {
        let j = i;
        let target = arr[j];
        while (j > 0 && arr[j - 1] > target) {
          arr[j] = arr[j - 1];
          j--;
        }
        arr[j] = target;
      }
      return arr;
    }
    // console.log(insertSort([3, 6, 2, 4, 1]));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    递归反转链表

    // node节点
    class Node {
      constructor(element,next) {
        this.element = element
        this.next = next
      } 
    }
    
    class LinkedList {
     constructor() {
       this.head = null // 默认应该指向第一个节点
       this.size = 0 // 通过这个长度可以遍历这个链表
     }
     // 增加O(n)
     add(index,element) {
       if(arguments.length === 1) {
         // 向末尾添加
         element = index // 当前元素等于传递的第一项
         index = this.size // 索引指向最后一个元素
       }
      if(index < 0 || index > this.size) {
        throw new Error('添加的索引不正常')
      }
      if(index === 0) {
        // 直接找到头部 把头部改掉 性能更好
        let head = this.head
        this.head = new Node(element,head)
      } else {
        // 获取当前头指针
        let current = this.head
        // 不停遍历 直到找到最后一项 添加的索引是1就找到第0个的next赋值
        for (let i = 0; i < index-1; i++) { // 找到它的前一个
          current = current.next
        }
        // 让创建的元素指向上一个元素的下一个
        // 看图理解next层级 ![](http://img-repo.poetries.top/images/20210522115056.png)
        current.next = new Node(element,current.next) // 让当前元素指向下一个元素的next
      }
    
      this.size++;
     }
     // 删除O(n)
     remove(index) {
      if(index < 0 || index >= this.size) {
        throw new Error('删除的索引不正常')
      }
      this.size--
      if(index === 0) {
        let head = this.head
        this.head = this.head.next // 移动指针位置
    
        return head // 返回删除的元素
      }else {
        let current = this.head
        for (let i = 0; i < index-1; i++) { // index-1找到它的前一个
          current = current.next
        }
        let returnVal = current.next // 返回删除的元素
        // 找到待删除的指针的上一个 current.next.next 
        // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可
        current.next = current.next.next 
    
        return returnVal
      }
     }
     // 查找O(n)
     get(index) {
      if(index < 0 || index >= this.size) {
        throw new Error('查找的索引不正常')
      }
      let current = this.head
      for (let i = 0; i < index; i++) {
        current = current.next
      }
      return current
     }
     reverse() {
      const reverse = head=>{
        if(head == null || head.next == null) {
          return head
        }
        let newHead = reverse(head.next)
        // 从这个链表的最后一个开始反转,让当前下一个元素的next指向自己,自己指向null
        // ![](http://img-repo.poetries.top/images/20210522161710.png)
        // 刚开始反转的是最后两个
        head.next.next = head
        head.next = null
    
        return newHead
      }
      return reverse(this.head)
     }
    }
    
    let ll = new LinkedList()
    
    ll.add(1)
    ll.add(2)
    ll.add(3)
    ll.add(4)
    
    // console.dir(ll,{depth: 1000})
    
    console.log(ll.reverse())
    
    • 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
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    图片懒加载

    // 
    function isVisible(el) {
      const position = el.getBoundingClientRect()
      const windowHeight = document.documentElement.clientHeight
      // 顶部边缘可见
      const topVisible = position.top > 0 && position.top < windowHeight;
      // 底部边缘可见
      const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
      return topVisible || bottomVisible;
    }
    
    function imageLazyLoad() {
      const images = document.querySelectorAll('img')
      for (let img of images) {
        const realSrc = img.dataset.src
        if (!realSrc) continue
        if (isVisible(img)) {
          img.src = realSrc
          img.dataset.src = ''
        }
      }
    }
    
    // 测试
    window.addEventListener('load', imageLazyLoad)
    window.addEventListener('scroll', imageLazyLoad)
    // or
    window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
    
    • 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

    分片思想解决大数据量渲染问题

    题目描述: 渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染

    let ul = document.getElementById("container");
    // 插入十万条数据
    let total = 100000;
    // 一次插入 20 条
    let once = 20;
    //总页数
    let page = total / once;
    //每条记录的索引
    let index = 0;
    //循环加载数据
    function loop(curTotal, curIndex) {
      if (curTotal <= 0) {
        return false;
      }
      //每页多少条
      let pageCount = Math.min(curTotal, once);
      window.requestAnimationFrame(function () {
        for (let i = 0; i < pageCount; i++) {
          let li = document.createElement("li");
          li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
          ul.appendChild(li);
        }
        loop(curTotal - pageCount, curIndex + pageCount);
      });
    }
    loop(total, index);
    
    • 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

    扩展思考 :对于大数据量的简单 dom 结构渲染可以用分片思想解决 如果是复杂的 dom 结构渲染如何处理?

    这时候就需要使用虚拟列表了,虚拟列表和虚拟表格在日常项目使用还是很多的

    请实现一个 add 函数,满足以下功能

    add(1);             // 1
    add(1)(2);      // 3
    add(1)(2)(3)// 6
    add(1)(2, 3); // 6
    add(1, 2)(3); // 6
    add(1, 2, 3); // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    function add(...args) {
      // 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值
      let fn = function(...newArgs) {
       return add.apply(null, args.concat(newArgs))
      }
    
      // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
      fn.toString = function() {
        return args.reduce((total,curr)=> total + curr)
      }
    
      return fn
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    考点:

    • 使用闭包, 同时要对JavaScript 的作用域链(原型链)有深入的理解
    • 重写函数的 toSting()方法
    // 测试,调用toString方法触发求值
    
    add(1).toString();             // 1
    add(1)(2).toString();      // 3
    add(1)(2)(3).toString()// 6
    add(1)(2, 3).toString(); // 6
    add(1, 2)(3).toString(); // 6
    add(1, 2, 3).toString(); // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二叉树搜索

    // 二叉搜索树
    
    class Node {
      constructor(element, parent) {
        this.parent = parent // 父节点 
        this.element = element // 当前存储内容
        this.left = null // 左子树
        this.right = null // 右子树
      }
    }
    
    class BST {
      constructor(compare) {
        this.root = null // 树根
        this.size = 0 // 树中的节点个数
    
        this.compare = compare || this.compare
      }
      compare(a,b) {
        return a - b
      }
      add(element) {
        if(this.root === null) {
          this.root = new Node(element, null)
          this.size++
          return
        }
        // 获取根节点 用当前添加的进行判断 放左边还是放右边
        let currentNode = this.root 
        let compare
        let parent = null 
        while (currentNode) {
          compare = this.compare(element, currentNode.element)
          parent = currentNode // 先将父亲保存起来
          // currentNode要不停的变化
          if(compare > 0) {
            currentNode = currentNode.right
          } else if(compare < 0) {
            currentNode = currentNode.left
          } else {
            currentNode.element = element // 相等时 先覆盖后续处理
          }
        }
    
        let newNode = new Node(element, parent)
        if(compare > 0) {
          parent.right = newNode
        } else if(compare < 0) {
          parent.left = newNode
        }
    
        this.size++
      }
    }
    
    • 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

    // 测试
    var bst = new BST((a,b)=>b.age-a.age) // 模拟sort方法
    
    bst.add({age: 10})
    bst.add({age: 8})
    bst.add({age:19})
    bst.add({age:20})
    bst.add({age: 5})
    
    console.log(bst)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    手写 Promise.then

    then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

    那么,怎么保证后一个 **then** 里的方法在前一个 **then**(可能是异步)结束之后再执行呢? 我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

    • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
    • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
    then(onFulfilled, onReject){
        // 保存前一个promise的this
        const self = this; 
        return new MyPromise((resolve, reject) => {
          // 封装前一个promise成功时执行的函数
          let fulfilled = () => {
            try{
              const result = onFulfilled(self.value); // 承前
              return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
            }catch(err){
              reject(err)
            }
          }
          // 封装前一个promise失败时执行的函数
          let rejected = () => {
            try{
              const result = onReject(self.reason);
              return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
            }catch(err){
              reject(err)
            }
          }
          switch(self.status){
            case PENDING: 
              self.onFulfilledCallbacks.push(fulfilled);
              self.onRejectedCallbacks.push(rejected);
              break;
            case FULFILLED:
              fulfilled();
              break;
            case REJECT:
              rejected();
              break;
          }
        })
       }
    
    
    • 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

    注意:

    • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
    • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

    异步串行 | 异步并行

    // 字节面试题,实现一个异步加法
    function asyncAdd(a, b, callback) {
      setTimeout(function () {
        callback(null, a + b);
      }, 500);
    }
    
    // 解决方案
    // 1. promisify
    const promiseAdd = (a, b) => new Promise((resolve, reject) => {
      asyncAdd(a, b, (err, res) => {
        if (err) {
          reject(err)
        } else {
          resolve(res)
        }
      })
    })
    
    // 2. 串行处理
    async function serialSum(...args) {
      return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
    }
    
    // 3. 并行处理
    async function parallelSum(...args) {
      if (args.length === 1) return args[0]
      const tasks = []
      for (let i = 0; i < args.length; i += 2) {
        tasks.push(promiseAdd(args[i], args[i + 1] || 0))
      }
      const results = await Promise.all(tasks)
      return parallelSum(...results)
    }
    
    // 测试
    (async () => {
      console.log('Running...');
      const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
      console.log(res1)
      const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
      console.log(res2)
      console.log('Done');
    })()
    
    • 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

    二叉树层次遍历

    // 二叉树层次遍历
    
    class Node {
      constructor(element, parent) {
        this.parent = parent // 父节点 
        this.element = element // 当前存储内容
        this.left = null // 左子树
        this.right = null // 右子树
      }
    }
    
    class BST {
      constructor(compare) {
        this.root = null // 树根
        this.size = 0 // 树中的节点个数
    
        this.compare = compare || this.compare
      }
      compare(a,b) {
        return a - b
      }
      add(element) {
        if(this.root === null) {
          this.root = new Node(element, null)
          this.size++
          return
        }
        // 获取根节点 用当前添加的进行判断 放左边还是放右边
        let currentNode = this.root 
        let compare
        let parent = null 
        while (currentNode) {
          compare = this.compare(element, currentNode.element)
          parent = currentNode // 先将父亲保存起来
          // currentNode要不停的变化
          if(compare > 0) {
            currentNode = currentNode.right
          } else if(compare < 0) {
            currentNode = currentNode.left
          } else {
            currentNode.element = element // 相等时 先覆盖后续处理
          }
        }
    
        let newNode = new Node(element, parent)
        if(compare > 0) {
          parent.right = newNode
        } else if(compare < 0) {
          parent.left = newNode
        }
    
        this.size++
      }
      // 层次遍历 队列
      levelOrderTraversal(visitor) {
        if(this.root == null) {
          return
        }
        let stack = [this.root]
        let index = 0 // 指针 指向0
        let currentNode 
        while (currentNode = stack[index++]) {
          // 反转二叉树
          let tmp = currentNode.left
          currentNode.left = currentNode.right
          currentNode.right = tmp
          visitor.visit(currentNode.element)
          if(currentNode.left) {
            stack.push(currentNode.left)
          }
          if(currentNode.right) {
            stack.push(currentNode.right)
          }
        }
      }
    }
    
    • 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
    • 74
    • 75
    • 76
    // 测试
    var bst = new BST((a,b)=>a.age-b.age) // 模拟sort方法
    
    // ![](http://img-repo.poetries.top/images/20210522203619.png)
    // ![](http://img-repo.poetries.top/images/20210522211809.png)
    bst.add({age: 10})
    bst.add({age: 8})
    bst.add({age:19})
    bst.add({age:6})
    bst.add({age: 15})
    bst.add({age: 22})
    bst.add({age: 20})
    
    // 使用访问者模式
    class Visitor {
      constructor() {
        this.visit = function (elem) {
          elem.age = elem.age*2
        }
      }
    }
    
    // ![](http://img-repo.poetries.top/images/20210523095515.png)
    console.log(bst.levelOrderTraversal(new Visitor()))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    实现instanceOf

    // 模拟 instanceof
    function instance_of(L, R) {
      //L 表示左表达式,R 表示右表达式
      var O = R.prototype; // 取 R 的显示原型
      L = L.__proto__; // 取 L 的隐式原型
      while (true) {
        if (L === null) return false;
        if (O === L)
          // 这里重点:当 O 严格等于 L 时,返回 true
          return true;
        L = L.__proto__;
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    怎么在制定数据源里面生成一个长度为 n 的不重复随机数组 能有几种方法 时间复杂度多少(字节)

    第一版 时间复杂度为 O(n^2)

    function getTenNum(testArray, n) {
      let result = [];
      for (let i = 0; i < n; ++i) {
        const random = Math.floor(Math.random() * testArray.length);
        const cur = testArray[random];
        if (result.includes(cur)) {
          i--;
          break;
        }
        result.push(cur);
      }
      return result;
    }
    const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const resArr = getTenNum(testArray, 10);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第二版 标记法 / 自定义属性法 时间复杂度为 O(n)

    function getTenNum(testArray, n) {
      let hash = {};
      let result = [];
      let ranNum = n;
      while (ranNum > 0) {
        const ran = Math.floor(Math.random() * testArray.length);
        if (!hash[ran]) {
          hash[ran] = true;
          result.push(ran);
          ranNum--;
        }
      }
      return result;
    }
    const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const resArr = getTenNum(testArray, 10);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    第三版 交换法 时间复杂度为 O(n)

    function getTenNum(testArray, n) {
      const cloneArr = [...testArray];
      let result = [];
      for (let i = 0; i < n; i++) {
        debugger;
        const ran = Math.floor(Math.random() * (cloneArr.length - i));
        result.push(cloneArr[ran]);
        cloneArr[ran] = cloneArr[cloneArr.length - i - 1];
      }
      return result;
    }
    const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const resArr = getTenNum(testArray, 14);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    值得一提的是操作数组的时候使用交换法 这种思路在算法里面很常见

    最终版 边遍历边删除 时间复杂度为 O(n)

    function getTenNum(testArray, n) {
      const cloneArr = [...testArray];
      let result = [];
      for (let i = 0; i < n; ++i) {
        const random = Math.floor(Math.random() * cloneArr.length);
        const cur = cloneArr[random];
        result.push(cur);
        cloneArr.splice(random, 1);
      }
      return result;
    }
    const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const resArr = getTenNum(testArray, 14);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实现 add(1)(2)(3)

    函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

    1)粗暴版

    function add (a) {
    return function (b) {
         return function (c) {
          return a + b + c;
         }
    }
    }
    console.log(add(1)(2)(3)); // 6
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2)柯里化解决方案

    • 参数长度固定
    var add = function (m) {
      var temp = function (n) {
        return add(m + n);
      }
      temp.toString = function () {
        return m;
      }
      return temp;
    };
    console.log(add(3)(4)(5)); // 12
    console.log(add(3)(6)(9)(25)); // 43
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于add(3)(4)(5),其执行过程如下:

    1. 先执行add(3),此时m=3,并且返回temp函数;

    2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数

    3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数

    4. 由于后面没有传入参数,等于返回的temp函数不被执行而是打印,了解JS的朋友都知道对象的toString是修改对象转换字符串的方法,因此代码中temp函数的toString函数return m值,而m值是最后一步执行函数时的值m=12,所以返回值是12。

    • 参数长度不固定
    function add (...args) {
        //求和
        return args.reduce((a, b) => a + b)
    }
    function currying (fn) {
        let args = []
        return function temp (...newArgs) {
            if (newArgs.length) {
                args = [
                    ...args,
                    ...newArgs
                ]
                return temp
            } else {
                let val = fn.apply(this, args)
                args = [] //保证再次调用时清空
                return val
            }
        }
    }
    let addCurry = currying(add)
    console.log(addCurry(1)(2)(3)(4, 5)())  //15
    console.log(addCurry(1)(2)(3, 4, 5)())  //15
    console.log(addCurry(1)(2, 3, 4, 5)())  //15
    
    
    • 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
  • 相关阅读:
    python --Matplotlib详解
    【设计模式】十、组合模式
    Apache SeaTunnel Web 功能正式发布!
    如何在Ubuntu下安装RabbitMQ服务并异地远程访问?
    敏捷项目管理完整流程及实践管理方法
    Python之多进程
    快2023了你不会还没学uni-app吧?(uniapp开发快速上手,uniapp项目创建,基础目录介绍)
    如何理解高效IO
    【工程应用九】再谈基于离散夹角余弦相似度指标的形状匹配优化(十六角度量化+指令集加速+目标只有部分在图像内的识别+最小外接矩形识别重叠等)
    2023前端大厂高频面试题之JavaScript篇(5)
  • 原文地址:https://blog.csdn.net/helloworld1024fd/article/details/127781825