面试常见手写题
防抖节流深拷贝
防抖
export default function debounce(
fn, // 需要防抖处理的函数
wait = 1000, // 防抖时间
immediate = false, // 是否需要立即执行
) {
let timer = null
let isImmediate = false
return (...args) => {
if (timer) clearTimeout(timer)
if (immediate && isImmediate) {
fn(...args)
isImmediate = true
}
timer = setTimeout(() => {
fn(...args)
isImmediate = false
}, wait)
}
}
节流
export default function throttle(
fn, // 需要节流处理的函数
wait = 1000,
) {
let preTime = 0
return (...args) => {
const currentTime = Date.now()
const diffTime = currentTime - preTime
if (diffTime - wait > 0) {
fn(...args)
preTime = currentTime
}
}
}
深拷贝
function deepClone(obj, map = new WeakMap()) {
if (obj === null) return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj) //解决循环引用
const cloneobj = new obj.constructor() //生成obj的对应类型
map.set(obj, cloneobj)
Reflect.ownKeys(obj).forEach((key) => {
cloneobj[key] = deepClone(obj[key], map)
})
return cloneobj
}
promise
手写 promise
// MyPromise.js
const PROMISE_STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}
function resolvePromise(thenPromise, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (thenPromise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判断x是不是 MyPromise 实例对象
if (x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}
export default class MyPromise {
constructor(fn) {
// fn 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
fn(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PROMISE_STATUS.PENDING
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 存储成功回调函数
onFulfilledCallbacks = []
// 存储失败回调函数
onRejectedCallbacks = []
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status !== PROMISE_STATUS.PENDING) return
// 状态修改为成功
this.status = PROMISE_STATUS.FULFILLED
// 保存成功之后的值
this.value = value
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用
// shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status !== PROMISE_STATUS.PENDING) return
// 状态成功为失败
this.status = PROMISE_STATUS.REJECTED
// 保存失败后的原因
this.reason = reason
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
then(onFulfilled, onRejected) {
// then 后面可能什么都没有传入,所以需要做下判断
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
const realOnRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const thenPromise = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
// https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === PROMISE_STATUS.FULFILLED) {
fulfilledMicrotask()
} else if (this.status === PROMISE_STATUS.REJECTED) {
rejectedMicrotask()
} else if (this.status === PROMISE_STATUS.PENDING) {
// 等待,因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return thenPromise
}
// resolve 静态方法
static resolve(parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter
}
// 转成常规方式
return new MyPromise((resolve) => {
resolve(parameter)
})
}
// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
static all() {}
static race() {}
static allSettled() {}
static any() {}
}
手写 promise.all
Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个 Promise 实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个 Promise 的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。
Promise.MyAll = Function(promises) {
let arr = []
let count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, index) => {
Promise.resolve(item).then(res => {
arr[index] = res
count++
if (count === promise.length) {
resolve(res)
}
}, reject)
})
})
}
手写 promise.race
Promise.race 从字面意思理解就是赛跑,以状态变化最快的那个 Promise 实例为准,最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失败 Promise.race 就失败。
Promise.MyRace = Function(promises) {
return new Promise((resolve, reject) => {
// 这里不需要使用索引,只要能循环出每一项就行
for (const item of promises) {
Promise.resolve(item).then(resolve, reject)
}
})
}
手写 promise.any
Promise.any 与 Promise.all 可以看做是相反的。Promise.any 中只要有一个 Promise 实例成功就成功
Promise.MyAny = Function(promises) {
let arr = []
let count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, index) => {
Promise.resolve(item).then(resolve, err => {
all[index] = {
status: 'reject',
reason: err
}
count++
if (count === promise.length) {
reject(new Error('没有一个成功'))
}
})
})
})
}
手写 promise.allSettled
无论 promise 是成功还是失败,都返回出来
Promise.MyAllSettled = Function(promises) {
let arr = new Array(promises.length)
let count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, index) => {
if (item && typeof item.then === 'function') {
item.then(res => {
arr[index]= { status: 'fulfilled', value: res }
count++
if (count === promises.length) {
resolve(arr)
}
}).catch(err) {
arr[index] = { status: 'reject', reason: err }
count++
if (count === promises.length) {
resolve(arr)
}
}
} else {
arr[index] = { status: 'fulfilled', value: item }
count++
if (count === promises.length) {
resolve(arr)
}
}
})
})
}
实现 promise 并行限制
class Schedule {
constructor(max = 2) {
this.maxNum = max
this.queue = []
this.runCounts = 0
}
add(promise) {
this.queue.push(promise)
}
request() {
if (this.queue.length === 0 || this.runCounts >= this.maxNum) return
this.runCount++
this.queue.shift().then(() => {
this.runCount--
this.request()
})
}
run() {
for (let i = 0; i < this.maxNum; i++) {
this.request()
}
}
}
// ............使用..............
import sleep from 'sleep' // 返回 promise 的延迟函数
const schedule = new Schedule(3)
const promises = Array(10).fill(Promise.resolve(() => sleep(1000)))
promises.forEach(item => {
schedule.add(item)
})
schedule.run()
promise 超时报错
原理; 利用 promise.race, 只要有一个 promise resolve / reject 就结束运行的原理,我们手动创建一个 promise 定时器,在规定时间后报错,如果目标 promise 的执行时间超过预定时间就会执行返回我们定时器里的报错信息
const overTime = (fn, time = 1000) => {
const outTime = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('请求超时'))
}, time)
})
}
return Promise.race([fn, outTime(time)])
}
async-await 实现 promise.all
async function asyncAll(promises) {
try {
const results = promises.map(async (item) => await item)
let arr = []
for (const item of results) {
arr.push(item)
}
return arr
} catch (err) {
throw new Error(err)
}
}
this 相关
手写 call
call(target, a, b, c, ...)
- call
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果
- 删除刚才新增的属性。
- 返回结果。
Function.prototype.myCall = function(ctx) {
if (typeof this !== 'function') {
throw New Error('类型异常')
}
// 获取参数
let args = [...arguments].slice(1)
let result = null
// 判断是否传入 ctx, 没有传的话取 window
ctx = ctx || window
// 将调用函数设为对象的方法
ctx.fn = this
// 执行函数
result = ctx.fn(...args)
// 执行结束移除属性
delete ctx.fn
return result
}
手写 apply
apply(target, [a, b, c, ... ]), 第二个参数只能一次绑定
- apply
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
Function.prototype.myApply = Function(ctx) {
if (typeof this !== 'function') {
throw New Error('类型异常')
}
let result = null
ctx = ctx || window
ctx.fn = this
if (arguments[1]) {
result = ctx.fn(...arguments[1])
} else {
result = ctx.fn()
}
delete ctx.fn
return result
}
手写 bind
bind(target, [a, b, c]) 第二个参数可以多次绑定
- bind
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数
Function.prototype.myBind = Function(ctx) {
if (typeof this !== 'function') {
throw New Error('类型异常')
}
let args = [...arguments].slice(1)
let fn = this
return function fn() {
return fn.apply(
this instanceof Fn ? this : ctx
args.concat(...arguments)
)
}
}
sleep
const sleep = (time = 1000) => {
new Promise((resolve) => {
setTimeout(resolve, time)
})
}
sleep(2000).then(() => {
// do something
})
实现简单的发布订阅
简单实现
class EventBus {
// 事件调度中心
map = {}
// 订阅事件
on = (name, fn) => {
// 创建、获取数组容器
this.map[name] = this.map[name] || []
// 保存事件
this.map[name].push(fn)
}
// 取消订阅
off = (name, fn) => {
const fnList = this.map[name] || []
const index = fnList.indexof(fn)
if (index < 0) return
fnList.splice(index, 1)
}
// 发布订阅
emit = (name, data) => {
const fnList = this.map[name] || []
fnList.forEach((fn) => fn.call(undefined, data))
}
}
使用
// 使用
const e = new MyBus()
e.on('click', (name) => {
console.log('hi ' + name)
})
e.on('click', (name) => {
console.log('hello ' + name)
})
setTimeout(() => {
e.emit('click', 'frank')
}, 3000)
实现简单的 keep-alive 组件
// 接收:字符串,正则,数组
const patternTypes: Array<Function> = [String, RegExp, Array]
// keep-alive 组件
export default {
name: 'keep-alive',
// 抽象组件,是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
abstract: true,
props: {
// 匹配的组件,缓存
include: patternTypes,
// 不去匹配的组件,不缓存
exclude: patternTypes,
// 缓存组件的最大实例数量, 由于缓存的是组件实例(vnode),
// 数量过多的时候,会占用过多的内存,可以用max指定上限
max: [String, Number],
},
created() {
// 用于初始化缓存虚拟DOM数组和vnode的key
this.cache = Object.create(null)
this.keys = []
},
mounted() {
// prune 削减精简[v.]
// 去监控include和exclude的改变,根据最新的include和exclude的内容,
// 来实时削减缓存的组件的内容
this.$watch('include', (val) => {
pruneCache(this, (name) => matches(val, name))
})
this.$watch('exclude', (val) => {
pruneCache(this, (name) => !matches(val, name))
})
},
destroyed() {
// 销毁缓存cache的组件实例
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
}
new
调用 new 的过程
- 首先创建了一个新的空对象
- 设置原型,将对象的原型设置为函数的 prototype 对象。
- 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
- 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function MyNew(fn, ...args) {
if (typeof fn !== 'function') {
throw new Error('类型异常')
}
const obj = Object.create(fn.prototype)
const val = fn.apply(obj, args)
return typeof val === 'object' ? val : obj
}
实现 create
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
函数柯里化
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args)
}
实现 add(1)(2)(3)(4)() = 10
- add(1)(2)(3)(4)() = 10
- add(1, 2, 3)(4)() = 10
function add(...args) {
let allArgs = [...args]
function fn(...newArgs) {
allArgs = [...allArgs, ...newArgs]
return fn
}
fn.toString = function () {
if (!allArgs.length) {
return
}
return allArgs.reduce((sum, cur) => sum + cur)
}
return fn
}
提取 url 参数
const getUrlParams = (url) => {
let reg = /([^?&=]+)=([^?&=]+)/g
let obj = {}
url.replace(reg, () => {
obj[arguments[1]] = arguments[2]
})
return obj
}
斐波拉契数列
// 正常写法
const Fibonacci = (n) => {
if (n <= 1) return 1
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
// 优化 - 尾递归
const Fibonacci = (n, sum1 = 1, sum2 = 1) => {
if (n <= 1) return sum2
return Fibonacci(n - 1, sum2, sum1 + sum2)
}
settimeout 模拟实现 setinterval
function myInterval(fn, wait = 1000) {
let timer = null
const pollFn = () => {
fn()
timer = setTimeout(pollFn, wait)
}
pollFn()
return {
cancel: () => clearTimeout(timer),
}
}
数组扁平化
// 方法1
function myFlat(arr) {
if (!arr.length) return
return arr.reduce((pre, cur) => (Array.isArray(cur) ? [...pre, ...myFlat(cur)] : [...pre, cur]), [])
}
// 方法2
function myFlat(arr) {
if (!arr.length) return
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
手写 instanceof
function MyInstanceOf(Left, Right) {
const L = Left.__proto__
const R = Right.prototype
while (true) {
if (L === null) return false
if (L === R) return true
L = L.__proto__
}
}
手写全等函数
对比两个对象里面的内容是否全等, 使用 JSON.stringify 转化后 undefined 的值会无法判断,例如
let a = { b: 1, c: undefined };
let b = { b: 1 }
console.log(JSON.stringify(a) === JSON.stringify(b))
// 打印结果为 true,但实际上 b 比 a 少了一个属性,应该是 false
const isObjAndNotNull = (x) => typeof x == "object" && x != null
const deepEqual = (x, y) => {
// 指向同一内存时
if (x === y) {
return true;
} else if (isObjAndNotNull(a) && isObjAndNotNull(b)) {
// x,y 都是对象且不是 null
if (Object.keys(x).length !== Object.keys(y).length) {
return false;
}
for (let prop in x) {
if (y.hasOwnProperty(prop)) {
if (!deepEqual(x[prop], y[prop])) return false;
} else {
return false;
}
}
return true;
} else {
return false;
}
}
排序
冒泡排序 O(n^2)
function bubbleSort(arr) {
// 缓存数组长度
const len = arr.length
// 外层循环用于控制从头到尾的比较+交换到底有多少轮
for (let i = 0; i < len; i++) {
// 内层循环用于完成每一轮遍历过程中的重复比较+交换
for (let j = 0; j < len - 1; j++) {
// 若相邻元素前面的数比后面的大
if (arr[j] > arr[j + 1]) {
// 交换两者
;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
// 返回数组
return arr
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));
快速排序 nlogn ~ n^2 之间
function quickSort(arr) {
if (arr.length < 2) {
return arr
}
const cur = arr[arr.length - 1]
const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1)
const right = arr.filter((v) => v > cur)
return [...quickSort(left), cur, ...quickSort(right)]
}
// console.log(quickSort([3, 6, 2, 4, 1]));
2 分查找排序 log2(n)
function search(arr, target, start, end) {
let targetIndex = -1
let mid = Math.floor((start + end) / 2)
if (arr[mid] === target) {
targetIndex = mid
return targetIndex
}
if (start >= end) {
return targetIndex
}
if (arr[mid] < target) {
return search(arr, target, mid + 1, end)
} else {
return search(arr, target, start, mid - 1)
}
}