前端面试常考的手写代码不是背出来的!
- 防抖
function debounce(func, ms = 1000) {let timer;return function (...args) {if (timer) {clearTimeout(timer)}timer = setTimeout(() => {func.apply(this, args)}, ms)}
}// 测试
const task = () => { console.log('run task') }
const debounceTask = debounce(task, 1000)
window.addEventListener('scroll', debounceTask)
- 节流
function throttle(func, ms = 1000) {let canRun = truereturn function (...args) {if (!canRun) returncanRun = falsesetTimeout(() => {func.apply(this, args)canRun = true}, ms)}
}// 测试
const task = () => { console.log('run task') }
const throttleTask = throttle(task, 1000)
window.addEventListener('scroll', throttleTask)
- 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
}// 测试
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)
- 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) {// 通过 new 调用,绑定 this 为实例对象fn.apply(this, newArgs)} else {// 通过普通函数形式调用,绑定 contextfn.apply(context, newArgs)}}// 支持 new 调用方式newFunc.prototype = Object.create(fn.prototype)return newFunc
}// 测试
const me = { name: 'Jack' }
const other = { name: 'Jackson' }
function say() {console.log(`My name is ${this.name || 'default'}`);
}
const meSay = say.myBind(me)
meSay()
const otherSay = say.myBind(other)
otherSay()
- call
Function.prototype.myCall = function (context = globalThis) {// 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖const key = Symbol('key')context[key] = this// es5 可通过 for 遍历 arguments 得到参数数组const args = [...arguments].slice(1)const res = context[key](...args)delete context[key]return res
};// 测试
const me = { name: 'Jack' }
function say() {console.log(`My name is ${this.name || 'default'}`);
}
say.myCall(me)
- apply
Function.prototype.myApply = function (context = globalThis) {// 关键步骤,在 context 上调用方法,触发 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
}// 测试
const me = { name: 'Jack' }
function say() {console.log(`My name is ${this.name || 'default'}`);
}
say.myApply(me)
- deepCopy
function deepCopy(obj, cache = new WeakMap()) {if (!obj instanceof Object) return obj// 防止循环引用if (cache.get(obj)) return cache.get(obj)// 支持函数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)// 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了// 数组是 key 为数字素银的特殊对象const res = Array.isArray(obj) ? [] : {}// 缓存 copy 的对象,用于处理循环引用的情况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
}// 测试
const source = {name: 'Jack',meta: {age: 12,birth: new Date('1997-10-10'),ary: [1, 2, { a: 1 }],say() {console.log('Hello');}}
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
console.log(newObj.meta.birth === source.meta.birth); // false
- 事件总线 | 发布订阅模式
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]) {// 创建副本,如果回调函数内继续注册相同事件,会造成死循环const tasks = this.cache[name].slice()for (let fn of tasks) {fn();}}}emit(name, once = false) {if (this.cache[name]) {// 创建副本,如果回调函数内继续注册相同事件,会造成死循环const tasks = this.cache[name].slice()for (let fn of tasks) {fn();}if (once) {delete this.cache[name]}}}
}// 测试
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)
- 柯里化:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
function curry(func) {return function curried(...args) {// 关键知识点:function.length 用来获取函数的形参个数// 补充:arguments.length 获取的是实参个数if (args.length >= func.length) {return func.apply(this, args)}return function (...args2) {return curried.apply(this, args.concat(args2))}}
}// 测试
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))
- es5 实现继承
function create(proto) {function F() {}F.prototype = proto;return new F();
}// Parent
function Parent(name) {this.name = name
}Parent.prototype.sayName = function () {console.log(this.name)
};// Child
function 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)
}// 测试
const child = new Child(18, 'Jack')
child.sayName()
child.sayAge()
- 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__}
}// 测试
class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array));
- 异步并发数限制
/*** 关键点* 1. new promise 一经创建,立即执行* 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法* 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里* 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行* 5. 任务完成后,需要从 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))
}// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {console.log(res)
})
- 异步串行 | 异步并行
// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);}, 500);
}// 解决方案
// 1. promisify
const 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)
}// 测试
(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');
})()
- vue reactive
// Dep module
class 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()}
}// reactive
function 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)}
}// watcher
class Watcher {constructor(effect) {this.effect = effectthis.update()}update() {Dep.pushTarget(this)this.value = this.effect()Dep.popTarget()return this.value}
}// 测试代码
const data = reactive({msg: 'aaa'
})new Watcher(() => {console.log('===> effect', data.msg);
})setTimeout(() => {data.msg = 'hello'
}, 1000)
- promise
// 建议阅读 [Promises/A+ 标准](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);}
}// 测试
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);})
- 数组扁平化
// 方案 1
function recursionFlat(ary = []) {const res = []ary.forEach(item => {if (Array.isArray(item)) {res.push(...recursionFlat(item))} else {res.push(item)}})return res
}
// 方案 2
function reduceFlat(ary = []) {return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}// 测试
const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(recursionFlat(source))
console.log(reduceFlat(source))
- 对象扁平化
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
}// 测试
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));
- 图片懒加载
// <img src="default.png" data-src="https://xxxx/real.png">
function isVisible(el) {const position = el.getBoundingClientRect()const windowHeight = document.documentElement.clientHeight// 顶部边缘可见const topVisible = position.top > 0 && position.top < windowHeight;// 底部边缘可见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 = ''}}
}// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or
window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
前端面试常考的手写代码不是背出来的!相关推荐
- web前端面试常考问题——持续更新中(5.20)
1.介绍一下你的技术栈 HTML5 + CSS3 + ES6 + Jquery + React + Webpack + git + npm .... 2.简单自我介绍 3.电商 ...
- JavaScript前端面试常考算法模板
一.二分查找 使用条件 排序数组 (30-40%是二分) 当面试官要求你找一个比 O(n) 更小的时间复杂度算法的时候(99%) 找到数组中的一个分割位置,使得左半部分满足某个条件,右半部分不满足(1 ...
- html盒子模型子元素怎么水平占满父元素_前端面试常考问题之css盒模型
一.题目:谈一谈你对CSS盒模型的认识 专业的面试,一定会问 CSS 盒模型.对于这个题目,我们要回答一下几个方面: (1)基本概念:content.padding.margin. (2)标准盒模型. ...
- 前端面试常考知识点---js
1.同步和异步 详细- 文章总结: setTimeout(fn,ms)这个函数,是经过指定时间后,把要执行的任务加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时 ...
- 2020年前端面试之JS手写代码题合集
2020年前端面试之JS手写代码题合集 预计会有上千道题,后续慢慢补! 1. 写一个把字符串大小写切换的方法 function caseConvert(str){return str.replace ...
- 前端面试高频手写代码题
前端面试高频手写代码题 一.实现一个解析URL参数的方法 方法一:String和Array的相关API 方法二: Web API 提供的 URL 方法三:正则表达式+string.replace方法 ...
- 前端面试:手写代码JS实现字符串反转
前端萌新面试:手写代码JS实现字符串反转 前言 因为做前年小红书的前端校招面试题,发现出现好几道关于字符串对象和数组对象的题目,说难不难,但突然要写的话一时想不起来,这不想着做个小总结. 首先明白字符 ...
- 2021-最新Web前端经典面试试题及答案-史上最全前端面试题(含答案)---手写代码篇
★★★ 手写代码:实现forEach map filter reduce ★★★ 手写实现一个简易的 Vue Reactive ★★★ 手写代码,监测数组变化,并返回数组长度 ★★★ 手写原生继承,并 ...
- 前端面试之手写代码篇
手写代码 1.手写instanceof方法 2.手写new操作符 3.手写Promise.all() 4.手写防抖函数 5.手写节流函数 6.手写call.apply函数 7.手写bind函数 8.封 ...
最新文章
- 【Codeforces】716B Complete the Word (26个字母)
- LaTeX 修改参考文献的方法
- NHibernate Step by Step:序篇 (转)
- 深入分析 Redis Lua 脚本运行原理
- javascript--拖动图片时取消浏览器默认提示
- [转载]项目风险管理七种武器-霸王枪
- HTML如何添加锚点,我先收藏为敬
- Windows系统使用minGW+msys 编译ffmpeg 0.5的全过程详述
- Per-class allocator 2
- 查看User Profile的名称和显示名称
- 基于51单片机编写的六位电子密码锁由LCD1602显示
- 预测控制matlab程序,预测控制matlab程序
- JavaScript 小白手册
- 以后在校期间的规划计算机专业的,计算机专业职业生涯规划方案书样本.doc
- 常见的数据结构与算法
- Excel函数返回定期付息证券的应计利息
- buu crypto 变异凯撒
- 冬季送暖!实用围巾打法!(图)
- 三季度高歌猛进,广告主为何向微博平台迁移?
- muParser公式库使用简介( 转)
热门文章
- Java基础学习总结(119)——Java8 JVM与Java7 JVM比较
- Android学习总结(3)——Handler深入详解
- Maven学习总结(20)——Maven pom.xml配置再体会
- mysql 列很多_MySQL:多个表还是一个表有很多列?
- 企业分布式微服务云SpringCloud SpringBoot mybatis (二)服务消费者(rest+ribbon)
- 打车应用上马快递业务靠谱吗?
- MyEclipse 2015 CI
- 解读系统资质审批的相关政策
- jdk1.5、1.6、1.7新特性详细介绍(整理)
- 谈现代企业中(一)菜鸟和大牛