我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
我们经常会在面试中遇到需要手写题目,今天刚好来总结下,查漏补缺,方便后面查阅。
1. 防抖函数
防抖函数是指在规定时间内,如果监听的事件多次触发,则不会执行对应的函数,直到最后一次间隔的时间超过规定时间才会执行。
const debounce = (fn, delay = 200) => {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn(...args)
}, delay)
}
}
2. 节流函数
节流函数是指在规定时间内,如果监听的事件多次触发,只会在规定时间内执行一次,以此类推,下一个规定时间也只会执行一次。
const throttle = (fn, delay = 200) => {
let timer = null
return function (...args) {
if (timer) return
timer = setTimeout(() => {
fn(...args)
clearTimeout(timer)
timer = null
}, delay)
}
}
防抖函数和节流函数的区别是什么?
- 二者都会返回一个新函数
- 防抖函数判断有timer时会clear后继续执行
- 节流函数判断如果有timer则return,如果没有timer则继续执行,执行后然后把timer清除。
3. 实现apply
apply,call方法是用来改变普通函数的this指向。但是apply只支持数组。
如果没有传context,默认是window。把函数绑定到context的属性(this),执行的时候this就指向了context。
Function.prototype._apply = function (context = window, args) {
args = args ? args : []
context.fn = this
const res = context.fn(...args)
delete context.fn
return res
}
4. 实现 call
call跟apply不同的是,它的参数可以是任意类型,没有限制。所以要入参的时候通过扩展符把它变成数组,调用的时候再展开。
Function.prototype._call = function (context = window, ...args) {
args = args ? args : []
context.fn = this
const res = context.fn(...args)
delete context.fn
return res
}
5. 实现bind
bind的使用情况跟apply,call不一样,它是返回一个新函数,而不是直接调用。
因为返回新函数,所以新函数就有可能会被当作构造函数,这里就要做判断。
判断如果是新函数的实例,则要调用new, new的对象是绑定的函数(const Fn = this,存起来).
否则就是普通的函数,调用call即可,也可以使用上面的_call方法。
参数也要注意合并。
Function.prototype._bind = function (context = window, ...args) {
const Fn = this
return function newFn (...newArgs) {
let res = null
// 构造函数
if (this instanceof newFn) {
res = new Fn(...args, ...newArgs)
} else {
res = Fn.call(context, ...args, ...newArgs)
}
return res
}
}
6. 实现new
我们要了解new的过程中做了什么,才能实现new方法。
- new过程中会新建一个对象,然后构造函数的this会指向这个对象。
- 这个对象会继承构造函数prototype上的方法
- new过程执行构造函数返回的值,如果是对象,则返回该对象,否则返回新建的对象。
这里我们使用Object.create
来模拟新建的对象,并继承构造函数的prototype。
然后调用apply,让构造函数的this指向新建的对象。
最后判断如果返回值是否为对象。
function NewFn (fn, ...args) {
const obj = Object.create(fn.prototype)
const res = fn.apply(obj, args)
return res instanceof Object ? res : obj
}
7. 发布订阅模式
发布订阅模式,我们这里使用class实现, 使用对象存储,当然也可以使用 Map 存储,这样就不单单存字符串了。
我们实现了on方法,once方法,emit方法,off方法。
once方法和on方法的区别是,once只会执行一次。
所以实现它内部是调用on方法,执行后就调用off取消它,这样就能保证调用一次。
class MyEvent {
constructor () {
this.stack = {}
}
on (name, fn) {
if (!this.stack[name]) this.stack[name] = []
this.stack[name].push(fn)
}
once (name, fn) {
const onceFn = () => {
fn()
this.off(name, onceFn)
}
this.on(name, onceFn)
}
emit (name) {
const fns = this.stack[name] || []
for (let i = 0; i < fns.length; i++) {
fns[i]()
}
}
off (name, fn) {
let fns = this.stack[name] || []
if (fn) {
this.stack[name] = fns.filter(item => item !== fn)
} else delete this.stack[name]
}
}
8. 深拷贝
这个深拷贝,是很简单的深拷贝,通过遍历加递归实现,可以满足一些基本需求。
这里没有考虑循环利用(子属性是自己)的情况
function deepCopy (obj) {
if (typeof obj !== 'object' || obj === null) return obj
const res = Array.isArray(obj) ? [] : {}
const type = Object.prototype.toString.call(obj)
if(type === '[object Date]' || type === '[object RegExp]') return new obj.constructor(obj)
for (const key in obj) {
if (typeof obj === 'object') res[key] = deepCopy(obj[key])
else res[key] = obj[key]
}
return res
}
总结
以上就是常见的手写题目,感谢你们的阅读。