前端面試常見(jiàn)的手寫(xiě)功能(常用代碼)
(給前端大學(xué)加星標(biāo),提升前端技能.)
作者:iboying
https://juejin.im/post/6873513007037546510
1. 防抖
function debounce(func, ms = 1000) {let timer;return function (...args) {if (timer) {clearTimeout(timer)}timer = setTimeout(() => {func.apply(this, args)}, ms)}}// 測(cè)試const task = () => { console.log('run task') }const debounceTask = debounce(task, 1000)window.addEventListener('scroll', debounceTask)
2. 節(jié)流
function throttle(func, ms = 1000) {let canRun = truereturn function (...args) {if (!canRun) returncanRun = falsesetTimeout(() => {func.apply(this, args)canRun = true}, ms)}}// 測(cè)試const task = () => { console.log('run task') }const throttleTask = throttle(task, 1000)window.addEventListener('scroll', throttleTask)
3. new
function myNew(Func, ...args) {const instance = {};if (Func.prototype) {Object.setPrototypeOf(instance, Func.prototype)}const res = Func.apply(instance, args)if (typeof res === "function" || (typeof res === "object" && res !== null)) {return res}return instance}// 測(cè)試function Person(name) {this.name = name}Person.prototype.sayName = function() {console.log(`My name is ${this.name}`)}const me = myNew(Person, 'Jack')me.sayName()console.log(me)
4. bind
Function.prototype.myBind = function (context = globalThis) {const fn = thisconst args = Array.from(arguments).slice(1)const newFunc = function () {const newArgs = args.concat(...arguments)if (this instanceof newFunc) {// 通過(guò) new 調(diào)用,綁定 this 為實(shí)例對(duì)象fn.apply(this, newArgs)} else {// 通過(guò)普通函數(shù)形式調(diào)用,綁定 contextfn.apply(context, newArgs)}}// 支持 new 調(diào)用方式newFunc.prototype = Object.create(fn.prototype)return newFunc}// 測(cè)試const me = { name: 'Jack' }const other = { name: 'Jackson' }function say() {console.log(`My name is ${this.name || 'default'}`);}const meSay = say.bind(me)meSay()const otherSay = say.bind(other)otherSay()
5. call
Function.prototype.myCall = function (context = globalThis) {// 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context,使用 Symbol 防止原有屬性的覆蓋const key = Symbol('key')context[key] = thislet args = [].slice.call(arguments, 1)let res = context[key](...args)delete context[key]return res};// 測(cè)試const me = { name: 'Jack' }function say() {console.log(`My name is ${this.name || 'default'}`);}say.myCall(me)
6. apply
Function.prototype.myApply = function (context = globalThis) {// 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context,使用 Symbol 防止原有屬性的覆蓋const key = Symbol('key')context[key] = thislet resif (arguments[1]) {res = context[key](...arguments[1])} else {res = context[key]()}delete context[key]return res}// 測(cè)試const me = { name: 'Jack' }function say() {console.log(`My name is ${this.name || 'default'}`);}say.myApply(me)
7. deepCopy
function deepCopy(obj, cache = new WeakMap()) {if (!obj instanceof Object) return obj// 防止循環(huán)引用if (cache.get(obj)) return cache.get(obj)// 支持函數(shù)if (obj instanceof Function) {return function () {obj.apply(this, arguments)}}// 支持日期if (obj instanceof Date) return new Date(obj)// 支持正則對(duì)象if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)// 還可以增加其他對(duì)象,比如:Map, Set等,根據(jù)情況判斷增加即可,面試點(diǎn)到為止就可以了// 數(shù)組是 key 為數(shù)字素銀的特殊對(duì)象const res = Array.isArray(obj) ? [] : {}// 緩存 copy 的對(duì)象,用于處理循環(huán)引用的情況cache.set(obj, res)Object.keys(obj).forEach((key) => {if (obj[key] instanceof Object) {res[key] = deepCopy(obj[key], cache)} else {res[key] = obj[key]}});return res}// 測(cè)試const source = {name: 'Jack',meta: {age: 12,birth: new Date('1997-10-10'),ary: [1, 2, { a: 1 }],say() {console.log('Hello');}}}source.source = sourceconst newObj = deepCopy(source)console.log(newObj.meta.ary[2] === source.meta.ary[2]);
8. 事件總線 | 發(fā)布訂閱模式
class EventEmitter {constructor() {this.cache = {}}on(name, fn) {if (this.cache[name]) {this.cache[name].push(fn)} else {this.cache[name] = [fn]}}off(name, fn) {const tasks = this.cache[name]if (tasks) {const index = tasks.findIndex((f) => f === fn || f.callback === fn)if (index >= 0) {tasks.splice(index, 1)}}}emit(name) {if (this.cache[name]) {// 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊(cè)相同事件,會(huì)造成死循環(huán)const tasks = this.cache[name].slice()for (let fn of tasks) {fn();}}}emit(name, once = false) {if (this.cache[name]) {// 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊(cè)相同事件,會(huì)造成死循環(huán)const tasks = this.cache[name].slice()for (let fn of tasks) {fn();}if (once) {delete this.cache[name]}}}}// 測(cè)試const eventBus = new EventEmitter()const task1 = () => { console.log('task1'); }const task2 = () => { console.log('task2'); }eventBus.on('task', task1)eventBus.on('task', task2)setTimeout(() => {eventBus.emit('task')}, 1000)
9. 柯里化:只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)
function curry(func) {return function curried(...args) {// 關(guān)鍵知識(shí)點(diǎn):function.length 用來(lái)獲取函數(shù)的形參個(gè)數(shù)// 補(bǔ)充:arguments.length 獲取的是實(shí)參個(gè)數(shù)if (args.length >= func.length) {return func.apply(this, args)}return function (...args2) {return curried.apply(this, args.concat(args2))}}}// 測(cè)試function sum (a, b, c) {return a + b + c}const curriedSum = curry(sum)console.log(curriedSum(1, 2, 3))console.log(curriedSum(1)(2,3))console.log(curriedSum(1)(2)(3))
10. es5 實(shí)現(xiàn)繼承
function create(proto) {function F() {}F.prototype = proto;return new F();}// Parentfunction Parent(name) {this.name = name}Parent.prototype.sayName = function () {console.log(this.name)};// Childfunction Child(age, name) {Parent.call(this, name)this.age = age}Child.prototype = create(Parent.prototype)Child.prototype.constructor = ChildChild.prototype.sayAge = function () {console.log(this.age)}// 測(cè)試const child = new Child(18, 'Jack')child.sayName()child.sayAge()
11. instanceof
function isInstanceOf(instance, klass) {let proto = instance.__proto__let prototype = klass.prototypewhile (true) {if (proto === null) return falseif (proto === prototype) return trueproto = proto.__proto__}}// 測(cè)試class Parent {}class Child extends Parent {}const child = new Child()console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array))
12. 異步并發(fā)數(shù)限制
/*** 關(guān)鍵點(diǎn)* 1. new promise 一經(jīng)創(chuàng)建,立即執(zhí)行* 2. 使用 Promise.resolve().then 可以把任務(wù)加到微任務(wù)隊(duì)列,防止立即執(zhí)行迭代方法* 3. 微任務(wù)處理過(guò)程中,產(chǎn)生的新的微任務(wù),會(huì)在同一事件循環(huán)內(nèi),追加到微任務(wù)隊(duì)列里* 4. 使用 race 在某個(gè)任務(wù)完成時(shí),繼續(xù)添加任務(wù),保持任務(wù)按照最大并發(fā)數(shù)進(jìn)行執(zhí)行* 5. 任務(wù)完成后,需要從 doingTasks 中移出*/function limit(count, array, iterateFunc) {const tasks = []const doingTasks = []let i = 0const enqueue = () => {if (i === array.length) {return Promise.resolve()}const task = Promise.resolve().then(() => iterateFunc(array[i++]))tasks.push(task)const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))doingTasks.push(doing)const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()return res.then(enqueue)};return enqueue().then(() => Promise.all(tasks))}// testconst timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {console.log(res)})
13. 異步串行 | 異步并行
// 字節(jié)面試題,實(shí)現(xiàn)一個(gè)異步加法function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);}, 500);}// 解決方案// 1. promisifyconst 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)}// 測(cè)試(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');})()
14. vue reactive
// Dep moduleclass Dep {static stack = []static target = nulldeps = nullconstructor() {this.deps = new Set()}depend() {if (Dep.target) {this.deps.add(Dep.target)}}notify() {this.deps.forEach(w => w.update())}static pushTarget(t) {if (this.target) {this.stack.push(this.target)}this.target = t}static popTarget() {this.target = this.stack.pop()}}// reactivefunction reactive(o) {if (o && typeof o === 'object') {Object.keys(o).forEach(k => {defineReactive(o, k, o[k])})}return o}function defineReactive(obj, k, val) {let dep = new Dep()Object.defineProperty(obj, k, {get() {dep.depend()return val},set(newVal) {val = newValdep.notify()}})if (val && typeof val === 'object') {reactive(val)}}// watcherclass Watcher {constructor(effect) {this.effect = effectthis.update()}update() {Dep.pushTarget(this)this.value = this.effect()Dep.popTarget()return this.value}}// 測(cè)試代碼const data = reactive({msg: 'aaa'})new Watcher(() => {console.log('===> effect', data.msg);})setTimeout(() => {data.msg = 'hello'}, 1000)
15. promise
// 建議閱讀 [Promises/A+ 標(biāo)準(zhǔn)](https://promisesaplus.com/)class MyPromise {constructor(func) {this.status = 'pending'this.value = nullthis.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) {setTimeout(() => {this.status = 'fulfilled'this.value = valuethis.resolvedTasks.forEach(t => t(value))})}_reject(reason) {setTimeout(() => {this.status = 'reject'this.value = reasonthis.rejectedTasks.forEach(t => t(reason))})}then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {this.resolvedTasks.push((value) => {try {const res = onFulfilled(value)if (res instanceof MyPromise) {res.then(resolve, reject)} else {resolve(res)}} catch (error) {reject(error)}})this.rejectedTasks.push((value) => {try {const res = onRejected(value)if (res instanceof MyPromise) {res.then(resolve, reject)} else {reject(res)}} catch (error) {reject(error)}})})}catch(onRejected) {return this.then(null, onRejected);}}// 測(cè)試new MyPromise((resolve) => {setTimeout(() => {resolve(1);}, 500);}).then((res) => {console.log(res);return new MyPromise((resolve) => {setTimeout(() => {resolve(2);}, 500);});}).then((res) => {console.log(res);throw new Error('a error')}).catch((err) => {console.log('==>', err);})
16. 數(shù)組扁平化
// 方案 1function recursionFlat(ary = []) {const res = []ary.forEach(item => {if (Array.isArray(item)) {res.push(...recursionFlat(item))} else {res.push(item)}})return res}// 方案 2function reduceFlat(ary = []) {return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])}// 測(cè)試const source = [1, 2, [3, 4, [5, 6]], '7']console.log(recursionFlat(source))console.log(reduceFlat(source))
17. 對(duì)象扁平化
function objectFlat(obj = {}) {const res = {}function flat(item, preKey = '') {Object.entries(item).forEach(([key, val]) => {const newKey = preKey ? `${preKey}.${key}` : keyif (val && typeof val === 'object') {flat(val, newKey)} else {res[newKey] = val}})}flat(obj)return res}// 測(cè)試const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }console.log(objectFlat(source));
18. 圖片懶加載
//function isVisible(el) {const position = el.getBoundingClientRect()const windowHeight = document.documentElement.clientHeight// 頂部邊緣可見(jiàn)const topVisible = position.top > 0 && position.top < windowHeight;// 底部邊緣可見(jiàn)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.srcif (!realSrc) continueif (isVisible(img)) {img.src = realSrcimg.dataset.src = ''}}}// 測(cè)試window.addEventListener('load', imageLazyLoad)window.addEventListener('scroll', imageLazyLoad)// orwindow.addEventListener('scroll', throttle(imageLazyLoad, 1000))
??愛(ài)心三連擊
點(diǎn)分享 點(diǎn)點(diǎn)贊 點(diǎn)在看
評(píng)論
圖片
表情



