前端手写算法题

1、深拷贝deepCopy

function deepCopy (obj) {
  // 如果obj为空,直接return
  if (!obj) return obj
  // 如果obj不是对象类型,直接return
  if (!obj instanceof Object) return obj
  // 如果obj是Function类型,
  if (obj instanceof Function) {
    return function () {
      return obj.apply(this, arguments)
    }
  }
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
  const res = Array.isArray(obj) ? [] : {}
  Object.keys(obj).forEach(key => {
    if (obj[key] instanceof Object) {
      res[key] = deepCopy(obj[key])
    } else {
      res[key] = obj[key]
    }
  })
  return res
}

2、对象扁平化

function objectFlat (obj) {
  let res = {}
  function flat (key, value) {
    if (Object(value) !== value) {
      // 判断是基本数据类型还是引用数据类型
      if (key) {
        res[key] = value
      }
    } else if (Array.isArray(value)) {
      for (let i = 0; i < value.length; i ++) {
        flat(`${key}[${i}]`, value[i])
      }
      if (value.length === 0) {
        res[key] = []
      }
    } else {
      let objArr = Object.keys(value)
      objArr.forEach(item => {
        flat(key ? `${key}.${item}` : `${item}`, value[item])
      })
      if (objArr.length === 0 && key) {
        res[key] = {}
      }
    }
  }
  flat('', obj)
  return res
}

3、数组扁平化

// arr = [1,[2,3], [3, [5,6]]]
方法一:
function arrFlat (arr) {
  let res = []
  arr.forEach(item => {
    if (Array.isArray(item)) {
      res.push(...arrFlat(item))
    } else {
      res.push(item)
    }
  })
return res
}
方法二:
function arrFlat (arr) {
  return arr.reduce((res, item) => {
    res.concat(Array.isArray(item) ? arrFlat(item) : item, [])
  })
}

4、手写Promise

// pending fulfilled reject
// constructor resolve reject then catch
// Promise是一个类
class MyPromise {
  // 定义一个构造函数
  constructor (func) {
    this.status = 'pending'
    this.value = null
    this.reason = null
    // 成功回调的空数组
    this.resolvedTasks = []
    // 失败回调的空数组
    this.rejectedTasks = []
    this._resolve = this._resolve.bind(this)
    this._reject = this._reject.bind(this)
    try {
      func(this._resolve, this._reject)
    } catch (error) {
      this._reject(error)
    }
  }
  _resolve (value) {
    this.status = 'fulfilled'
    this.value = value
    // 循环回调数组,把数组前面的方法弹出来且直接调用
    this.resolvedTasks.forEach(t => t(value))
  }
  _reject (reason) {
    this.status = 'reject'
    this.reason = reason
    // 循环回调数组,把数组前面的方法弹出来且直接调用
    this.rejectedTasks.forEach(t => t(reason))
  }
  // then方法接收两个参数:一个是成功时执行的回调函数,一个是失败时执行的回调函数
  then (resolvedTasks, rejectedTasks) {
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === 'fulfilled') {
        resolvedTasks(this.value)
      } else if (this.status === 'reject') {
        rejectedTasks(this.reason)
      } else {
        this.resolvedTasks.push(resolvedTasks)
        this.rejectedTasks.push(rejectedTasks)
      }
    })
    return promise2
  }
  catch (rejectedTasks) {
    return this.then(null, rejectedTasks)
  }
}
module.exports = MyPromise

// 测试
new MyPromise((resolve) => {
  setTimeout(() => {
    resolve(1)
  }, 500)
}).then((res) => {
  return new MyPromise((resolve) => {
    setTimeout(() => {
      resolve(2)
    }, 500)
  })
}).then((res) => {
  throw new Error('error')
}).catch((err) => {
  console.log('err')
})

5、promise.all方法

// all方法接收一个数组,Promise返回值也是一个Promise对象,如果数组中所有值是成功的,那么then里面就是成功回调,如果有一个值是失败的,那么then里面就是失败的
Promise.all = function (promise) {
  return new Promise((resolve, reject) => {
    let index = 0; // 声明一个计数,每完成一个Promise就加1
    let result = []
    if (promise.length === 0) {
      resolve(result)
    } else {
      function processValue (i, data) {
        result[i] = data
        // 如果计数器和数组长度相同,那说明所有元素都执行完了,可以输出了
        if (++index === promise.length) {
          resolve(result)
        }
      }
      // 对传递的数组进行遍历
      for (let i = 0; i < promise.length; i++) {
        // Promise对象就执行then方法,如果是resolve就把值添加到数组中,如果是错误就执行reject返回
        Promise.resolve(promise[i]).then((data) => {
          processValue(i, data)
        }, (err) => {
          reject(err)
          return
        })
      }
    }
  })
}

6、防抖
// 防抖就是延迟执行,就是等待n秒后再去执行。也就是持续触发时不执行,等不触发了一段时间之后再去执行。

function debounce (fn, delay) {
  let timer = null
  return function (...args) {
     if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, args) // 使用apply立即执行
    }, delay)
  }
}

7、节流
// 节流就是间隔执行,就是n秒内只执行一次。持续触发时,将开关关闭,等时间到了,再将开关打开。

function throttle (fn, delay) {
  let canRun = true
  return function (...args) {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      fn.apply(this, args)
      canRun = true
    }, delay)
  }
}

8、call

// call是立即执行
Function.prototype.call = function (context) {
  const cxt = context || window;
  // 将当前被调用的方法定义在cxt.func上,为了能以对象调用的形式绑定this
  cxt.func = this;
  // 获取实参,入参是(对象,参数列表),slice(1)是为了获取参数列表
  const args = Array.from(arguments).slice(1);
  // 以对象调用的形式调用func,此时this指向cxt,也就是传入的需要绑定的this指向
  const res = arguments.length > 1 ? cxt.func(...args) : cxt.func();
  // 删除该方法,不然会对传入对象造成污染(添加该方法)
  delete cxt.func;
  return res;
};

9、apply

// apply是立即执行
Function.prototype.apply = function (context) {
  const cxt = context || window;
  // 将当前被调用的方法定义在cxt.func上,为了能以对象调用的形式绑定this
  cxt.func = this;
  // 获取实参
  const res = arguments[1] ? cxt.func(...arguments[1]) : cxt.func();
  // 删除该方法,不然会对传入对象造成污染(添加该方法)
  delete cxt.func;
  return res;
};

10、bind

// bind是绑定一个对象指向当前函数,以便稍后调用。方法.bind(对象)
Function.prototype.bind = function (context) {
  // 对context进行深拷贝,防止污染
  const cxt = JSON.parse(JSON.stringify(context)) || window;
   // 将当前被调用的方法定义在cxt.func上,为了能以对象调用的形式绑定this
  cxt.func = this;
  // 获取实参
  const args = Array.from(arguments).slice(1);
  // bind返回一个绑定函数,等待调用
  const newFunc = function () {
    // 这里需要注意一点的是需要对bind函数的实参和返回的绑定函数的实参进行参数合并,调用时传入!
    const allArgs = args.concat(Array.from(arguments));
    // 以对象调用的形式调用func,此时this指向cxt,也就是传入的需要绑定的this指向
    // if (this instanceof newFunc) {
      // cxt.func.apply(this, newFunc)
    // } else {
      // cxt.func.apply(cxt, allArgs)
    // }
    return allArgs.length > 0 ? cxt.func(...allArgs) : cxt.func();
  };
  return newFunc
};

11、new操作
(1) 创建一个新对象;
(2) 将构造函数中的this指向该对象
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。

function new (obj, ...args) {
  // 基于obj的原型创建一个新对象
  const newObj = Object.create(obj.prototype)
  // 添加新的属性到newObj上,并获取obj函数的结果
  const res = obj.apply(newObj, args)
  // 如果执行结果有返回值并且是一个对象,则返回执行的结果,否则,返回新创建的对象
  return typeof res === 'object' ? res : newObj
}

12、二叉树层序遍历(广度遍历)

/**
* params {TreeNode} = root
* 先遍历根节点的相邻节点,再一次遍历相邻节点的子节点,用队列来存储当前
* 层的节点数,遍历当前层的节点,将当前层节点依次推入subRes[],
*再将其子节点依次推入,然后进入下一层遍历,直到遍历完整棵树。
* function TreeNode(val) {
*     this.val = val;
*     this.left = this.right = null;
* }
*/
// 非递归遍历
function levelOrder (root) {
    if (!root) return []
    let res = []
    let queue = [root]
    // 当前层的节点数目>0
    while (queue.length > 0) {
        let subRes = []
        for (let i = 0; i < queue.length; i++){
            let node = queue.shift() // 节点出列
            subRes.push(node.value) // 将当前层的节点值加入subRes数组中
            // 将下一层节点计入队列中
            if (node.left) {
                subRes.push(node.left)
            }
            if (node.right) {
                subRes.push(node.right)
            }
        }
        res.push(subRes)
    }
    return res
}
// 递归遍历
function levelOrder (root) {
  if (!root) return []
  let res = []
  let stack = [tree] // 先将要遍历的树压入栈
  let count = 0 // 用来记录执行到第一层
  function bfs () {
    let node = stack[count]
    if (node) {
      res.push(node.value)
      if (node.left) res.push(node.left)
      if (node.right) res.push(node.right)
      count++
      bfs()
    }
  }
  return res
}

13、二叉树深度遍历

// 二叉树的结构
var root = {
  value: '-',
  left: {
    value: '+',
    left: {
      value: 'a'
    },
    right: {
      value: 'b'
    }
  },
  right: {
    value: '*',
    left: {
      value: 'd'
    },
    right: {
      value: 'e'
    }
  }
}
// 前序遍历:根 - 左 - 右
// 非递归方法
// 利用栈:将遍历到的结点都依次存入栈中,拿结果时从栈中访问
function preDeepOrder (root) {
  if (!root) return []
  let res = []
  let stack = [] // 初始化一个栈,将根节点压入栈中;
  while (root || stack.length > 0) {
    while (root) {
      res.push(root.val)
      stack.push(root)
      root = root.left
    }
    if (stack.length > 0) {
      root = stack.pop()
      root = root.right
    }
  }
  return res
}
// 递归的方法
// 先遍历根结点,将值存入数组,然后递归遍历:先左结点,将值存入数组,继续向下遍历;直到(二叉树为空)子树为空,则遍历结束;
然后再回溯遍历右结点,将值存入数组,这样递归循环,直到(二叉树为空)子树为空,则遍历结束。
function preDeepOrder (root) {
  if (!root) return []
  let res = []
  function preOrder (root) {
    if (root) {
      res.push(root.val)
      preOrder(root.left)
      preOrder(root.right)
    }
  }
  preOrder(root)
  return res
}
// 中序遍历:左 - 根 - 右
// 非递归方法
function middleDeepOrder (root) {
  if (!root) return []
  let res = []
  let stack = []
  while (root || stack.length > 0) {
    while (root) {
      stack.push(root)
      root = root.left
    }
    if (stack.length > 0) {
      root = stack.pop()
      res.push(root.val)
      root = root.right
    }
  }
}
// 递归方法
function middleDeepOrder (root) {
  if (!root) return []
  let res = []
  function middleOrder (root) {
    if (root) {
      middleOrder(root.left)
      res.push(root.val)
      middleOrder(root.right)
    }
  }
  middleOrder(root)
  return res
}
// 后序遍历:左 - 右 - 根
// 非递归方法
// 先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。
function postDeepOrder (root) {
  if (!root) return []
  let node = root
  let lastVisit = root
  let res = []
  let stack = []
  while (node || stack.length > 0) {
    while (node) {
      stack.push(node)
      node = node.left
    }
    node = stack[stack.length - 1]
    if (!node.right || node.right === lastVisit) {
      res.push(node.val)
      stack.pop()
      lastVisit = node
      node = null
    } else {
      node = node.right
    }
  }
  return res
}
// 递归方法
function postDeepOrder (root) {
  if (!root) return []
  let res = []
  function postOrder (root) {
    if (root) {
      postOrder(root.left)
      postOrder(root.right)
      res.push(root.val)
    }
  }
  postOrder(root)
  return res
}

14、instanceof

function isInstanceOf (instance, obj) {
  let proto = instance.__proto__
  let prototype = obj.prototype
  while (true) {
    if (proto === null) return false
    if (proto === prototype) return true
    proto = proto.__proto__
  }
}

15、ES5实现继承


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容