过完年马上又要到金三银四面试季了,想必很多同学已经跃跃欲试,提前开始准备面试了,本文就列举了面试过程中一些常见的手写代码实现供参考。或许很多人会问,这些手写代码实现意义何在,社区已经有很多poly-fill
或者函数库供选择,何必要自己费力去折腾呢?我的理解是,在真实业务开发场景中,我们真的用不上这些自己写的方法,一个lodash
库完全可以满足我们的需求,但此时你仅仅只是一个API Caller ,你经常使用到它,但对它实现原理却一无所知,哪怕它实现起来其实是非常简单。所以亲自动手写出它的实现过程,对理解其中原理是很有帮助的。另外,不要觉得用ES6
语法,或者最新的语法去实现ES5
甚至是ES3
的方法是件可笑的事情,相反,它更能体现出你对ES6
语法的掌握程度以及对JS发展的关注度,在面试中说不定会成为你的一个亮点。
<<<前端是我一辈子的信仰,写代码直到50岁>>>
关注微信公众号:web前端学习圈
回复关键词【CSDN】领取2020年最新web前端系统学习教程(视频+笔记+素材+源码+项目实战)全套整理约50G
另外公众号每天都会分享学习方法,知识干货,实战案例,面试技巧,经验分享等相关文章,关注web前端学习圈=关注5000+前端大牛
模拟call
null
或者undefined
时,this
指向全局对象window
,值为原始值的指向该原始值的自动包装对象,如 String
、Number
、Boolean
context
)的属性发生冲突,使用Symbol
类型作为唯一值context
)属性执行Function.prototype.myCall = function(context, ...args) {
context = (context ?? window) || new Object(context)
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
复制代码
注: 代码实现使用了ES2020
新特性Null
判断符 ??
, 详细参考阮一峰老师的ECMAScript 6 入门
模拟apply
call
一样Function.prototype.myApply = function(context) {
context = (context ?? window) || new Object(context)
const key = Symbol()
const args = arguments[1]
context[key] = this
let result
if(args) {
result = context[key](...args)
} else {
result = context[key]
}
delete context[key]
return result
}
复制代码
注:代码实现存在缺陷,当第二个参数为类数组时,未作判断(有兴趣可查阅一下如何判断类数组)
模拟bind
call / apply
指定 this
new
调用,绑定的上下文指向实例对象prototype
为原函数的prototype
Function.prototype.myBind = function(context, ...args) {
const fn = this
const bindFn = function (...newFnArgs) {
fn.call(
this instanceof bindFn ? this : context,
...args, ...newFnArgs
)
}
bindFn.prototype = Object.create(fn.prototype)
return bindFn
}
复制代码
模拟new
this
绑定到空对象__proto__
指向构造函数的原型(prototype
)const createNew = (Con, ...args) => {
const obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
复制代码
模拟instanceOf
false
const myInstanceOf = (left, right) => {
let leftValue = left.__proto__
let rightValue = right.prototype
while(true) {
if(leftValue === null) return false
if(leftValue === rightValue) return true
leftValue = leftValue.__proto__
}
}
复制代码
深拷贝(简单版)
const deepClone = (target, cache = new WeakMap()) => {
if(target === null || typeof target !== 'object') {
return target
}
if(cache.get(target)) {
return target
}
const copy = Array.isArray(target) ? [] : {}
cache.set(target, copy)
Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
return copy
}
复制代码
深拷贝(尤雨溪版)
vuex源码
function find(list, f) {
return list.filter(f)[0]
}
function deepCopy(obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))
return copy
}
复制代码
函数防抖
this
继承自父级上下文,指向触发事件的目标元素event
对象leading
参数,判断是否可以立即执行回调函数,不必要等到事件停止触发后才开始执行 const debounce = (fn, wait = 300, leading = true) => {
let timerId, result
return function(...args) {
timerId && clearTimeout(timerId)
if (leading) {
if (!timerId) result = fn.apply(this, args)
timerId = setTimeout(() => timerId = null, wait)
} else {
timerId = setTimeout(() => result = fn.apply(this, args), wait)
}
return result
}
}
复制代码
函数节流(定时器)
const throttle = (fn, wait = 300) => {
let timerId
return function(...args) {
if(!timerId) {
timerId = setTimeout(() => {
timerId = null
return result = fn.apply(this, ...args)
}, wait)
}
}
}
复制代码
函数节流(时间戳)
const throttle = (fn, wait = 300) => {
let prev = 0
let result
return function(...args) {
let now = +new Date()
if(now - prev > wait) {
prev = now
return result = fn.apply(this, ...args)
}
}
}
复制代码
函数节流实现方法区别
方法 | 使用时间戳 | 使用定时器 |
---|---|---|
开始触发时 | 立刻执行 | n秒后执行 |
停止触发后 | 不再执行事件 | 继续执行一次事件 |
数组去重
const uniqBy = (arr, key) => {
return [...new Map(arr.map(item) => [item[key], item])).values()]
}
const singers = [
{ id: 1, name: 'Leslie Cheung' },
{ id: 1, name: 'Leslie Cheung' },
{ id: 2, name: 'Eason Chan' },
]
console.log(uniqBy(singers, 'id'))
// [
// { id: 1, name: 'Leslie Cheung' },
// { id: 2, name: 'Eason Chan' },
// ]
复制代码
原理是利用Map
的键不可重复
数组扁平化(技巧版)
const flatten = (arr) => arr.toString().split(',').map(item => +item)
复制代码
数组扁平化
const flatten = (arr, deep = 1) => {
return arr.reduce((cur, next) => {
return Array.isArray(next) && deep > 1 ?
[...cur, ...flatten(next, deep - 1)] :
[...cur, next]
},[])
}
复制代码
函数柯里化
const currying = (fn) {
_curry = (...args) =>
args.length >= fn.length
? fn(...args)
: (...newArgs) => _curry(...args, ...newArgs)
}
复制代码
原理是利用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数
发布订阅EventEmitter
class EventEmitter {
#subs = {}
emit(event, ...args) {
if (this.#subs[event] && this.#subs[event].length) {
this.#subs[event].forEach(cb => cb(...args))
}
}
on(event, cb) {
(this.#subs[event] || (this.#subs[event] = [])).push(cb)
}
off(event, offCb) {
if (offCb) {
if (this.#subs[event] && this.#subs[event].length)
this.#subs[event] = this.#subs[event].filter(cb => cb !== offCb)
} else {
this.#subs[event] = []
}
}
}
复制代码
subs
是EventEmitter
私有属性(最新特性参考阮一峰老师的ECMAScript 6 入门),通过on
注册事件,off
注销事件,emit
触发事件
寄生组合继承
function Super(foo) {
this.foo = foo
}
Super.prototype.printFoo = function() {
console.log(this.foo)
}
function Sub(bar) {
this.bar = bar
Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
复制代码
ES6版继承
class Super {
constructor(foo) {
this.foo = foo
}
printFoo() {
console.log(this.foo)
}
}
class Sub extends Super {
constructor(foo, bar) {
super(foo)
this.bar = bar
}
}
复制代码
ES5
的继承,实质是先创造子类的实例对象,然后将再将父类的方法添加到this
上。 ES6
的继承,先创造父类的实例对象(所以必须先调用super
方法,然后再用子类的构造函数修改this