前端面试必备:JavaScript手写代码与核心算法解析
1. 前端面试的核心考察维度前端面试通常分为三个主要部分基础知识考察八股文、手写代码能力测试和项目经验评估。这三个环节共同构成了前端工程师面试的完整评估体系。基础知识部分主要考察候选人对前端核心概念的理解程度包括但不限于JavaScript语言特性原型链、闭包、作用域等CSS布局与渲染机制浏览器工作原理前端框架设计思想网络协议基础手写代码环节则更注重实际编码能力通常会要求候选人现场实现一些常见功能或算法。这部分最能直观反映一个开发者的编码习惯和问题解决能力。2. JavaScript高频手写题解析2.1 原型相关方法实现手写instanceoffunction myInstanceOf(obj, constructor) { // 边界检查 if (obj null || typeof obj ! object) return false let proto Object.getPrototypeOf(obj) while (proto) { if (proto constructor.prototype) return true proto Object.getPrototypeOf(proto) } return false }关键点使用Object.getPrototypeOf替代__proto__获取原型更规范通过循环不断向上查找原型链边界情况处理null和基本类型手写new操作符function myNew(constructor, ...args) { // 1. 创建新对象并链接原型 const obj Object.create(constructor.prototype) // 2. 执行构造函数绑定this const result constructor.apply(obj, args) // 3. 处理构造函数返回值 return result instanceof Object ? result : obj }实现要点Object.create建立原型链接比直接修改__proto__更安全构造函数可能有返回值需要特殊处理参数处理使用剩余参数语法更简洁2.2 函数方法实现手写call/apply/bindFunction.prototype.myCall function(context, ...args) { context context || window const fn Symbol(fn) context[fn] this const result context[fn](...args) delete context[fn] return result } Function.prototype.myApply function(context, argsArray []) { return this.myCall(context, ...argsArray) } Function.prototype.myBind function(context, ...args) { const self this return function(...innerArgs) { return self.myCall(context, ...args, ...innerArgs) } }技术细节使用Symbol避免属性冲突参数默认值处理闭包保存原始函数引用参数合并技巧3. 数据处理与算法实现3.1 深拷贝进阶实现基础版本function deepClone(obj) { if (obj null || typeof obj ! object) return obj const clone Array.isArray(obj) ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] deepClone(obj[key]) } } return clone }支持循环引用版本function deepClone(obj, map new WeakMap()) { if (obj null || typeof obj ! object) return obj if (map.has(obj)) return map.get(obj) const clone Array.isArray(obj) ? [] : {} map.set(obj, clone) for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] deepClone(obj[key], map) } } return clone }关键改进WeakMap解决循环引用问题提前缓存对象引用保持数据结构一致性3.2 数组操作大全数组去重多种实现// 方法1Set 展开运算符 const unique1 arr [...new Set(arr)] // 方法2filter indexOf const unique2 arr arr.filter((item, index) arr.indexOf(item) index) // 方法3reduce实现 const unique3 arr arr.reduce((acc, cur) acc.includes(cur) ? acc : [...acc, cur], [])数组扁平化// 递归版 function flatten(arr) { return arr.reduce((acc, cur) Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur], []) } // 迭代版 function flattenIterative(arr) { while (arr.some(item Array.isArray(item))) { arr [].concat(...arr) } return arr }4. 异步编程与设计模式4.1 Promise实现Promise.all实现Promise.myAll function(promises) { return new Promise((resolve, reject) { const results [] let count 0 promises.forEach((promise, index) { Promise.resolve(promise).then(res { results[index] res if (count promises.length) resolve(results) }).catch(reject) }) }) }Promise.race实现Promise.myRace function(promises) { return new Promise((resolve, reject) { promises.forEach(promise { Promise.resolve(promise).then(resolve).catch(reject) }) }) }4.2 发布订阅模式class EventEmitter { constructor() { this.events {} } on(event, listener) { (this.events[event] || (this.events[event] [])).push(listener) return this } emit(event, ...args) { const listeners this.events[event] if (listeners) { listeners.forEach(listener listener.apply(this, args)) } return this } off(event, listener) { const listeners this.events[event] if (listeners) { const index listeners.indexOf(listener) if (index ! -1) listeners.splice(index, 1) } return this } }5. 前端工程化相关5.1 模块加载器实现class Module { constructor() { this.cache {} } require(name) { if (this.cache[name]) return this.cache[name].exports const module { exports: {} } this.cache[name] module // 实际项目中这里会读取文件内容 const code this.getModuleCode(name) const wrapper Function(module, exports, require, code) wrapper(module, module.exports, this.require.bind(this)) return module.exports } getModuleCode(name) { // 返回模块的源代码字符串 // 实际项目中需要从文件系统读取 return } }5.2 虚拟DOM diff算法function diff(oldNode, newNode) { if (!oldNode) return { type: CREATE, node: newNode } if (!newNode) return { type: REMOVE } if (changed(oldNode, newNode)) return { type: REPLACE, node: newNode } const patches { type: UPDATE, children: [] } const len Math.max(oldNode.children.length, newNode.children.length) for (let i 0; i len; i) { patches.children.push(diff(oldNode.children[i], newNode.children[i])) } return patches } function changed(node1, node2) { return typeof node1 ! typeof node2 || typeof node1 string node1 ! node2 || node1.type ! node2.type }6. 性能优化相关实现6.1 防抖与节流// 防抖连续触发时只执行最后一次 function debounce(fn, delay) { let timer null return function(...args) { clearTimeout(timer) timer setTimeout(() { fn.apply(this, args) }, delay) } } // 节流固定时间间隔执行 function throttle(fn, interval) { let lastTime 0 return function(...args) { const now Date.now() if (now - lastTime interval) { fn.apply(this, args) lastTime now } } }6.2 图片懒加载class LazyLoader { constructor(selector img[data-src]) { this.images document.querySelectorAll(selector) this.init() } init() { this.observe new IntersectionObserver(entries { entries.forEach(entry { if (entry.isIntersecting) { this.loadImage(entry.target) this.observe.unobserve(entry.target) } }) }) this.images.forEach(img this.observe.observe(img)) } loadImage(img) { const src img.getAttribute(data-src) if (!src) return img.src src img.removeAttribute(data-src) } }7. 算法题精选7.1 快速排序function quickSort(arr, left 0, right arr.length - 1) { if (left right) return const pivot partition(arr, left, right) quickSort(arr, left, pivot - 1) quickSort(arr, pivot 1, right) return arr } function partition(arr, left, right) { const pivot arr[right] let i left for (let j left; j right; j) { if (arr[j] pivot) { [arr[i], arr[j]] [arr[j], arr[i]] i } } [arr[i], arr[right]] [arr[right], arr[i]] return i }7.2 二分查找function binarySearch(arr, target) { let left 0 let right arr.length - 1 while (left right) { const mid Math.floor((left right) / 2) if (arr[mid] target) return mid if (arr[mid] target) left mid 1 else right mid - 1 } return -1 }8. CSS相关手写题8.1 水平垂直居中方案/* 方案1flex布局 */ .container { display: flex; justify-content: center; align-items: center; } /* 方案2绝对定位 transform */ .container { position: relative; } .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* 方案3grid布局 */ .container { display: grid; place-items: center; }8.2 三栏布局实现/* 圣杯布局 */ .container { padding: 0 200px; } .left, .right { width: 200px; position: relative; } .left { margin-left: -100%; left: -200px; } .right { margin-right: -200px; } /* 双飞翼布局 */ .main-wrap { width: 100%; float: left; } .main { margin: 0 200px; } .left { width: 200px; margin-left: -100%; } .right { width: 200px; margin-left: -200px; }9. 前端安全相关实现9.1 XSS防御function escapeHtml(unsafe) { return unsafe .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) .replace(//g, quot;) .replace(//g, #039;) } function safeRender(input) { const div document.createElement(div) div.textContent input return div.innerHTML }9.2 CSRF防御// 服务端生成并返回token function generateCSRFToken() { const token crypto.randomBytes(32).toString(hex) document.cookie csrf_token${token}; SameSiteStrict; Path/ return token } // 客户端发送请求时带上token function safeRequest(url, data) { const token getCookie(csrf_token) return fetch(url, { method: POST, headers: { Content-Type: application/json, X-CSRF-Token: token }, body: JSON.stringify(data) }) }10. 现代前端框架原理10.1 简易响应式系统class Dep { constructor() { this.subscribers [] } depend() { if (target !this.subscribers.includes(target)) { this.subscribers.push(target) } } notify() { this.subscribers.forEach(sub sub()) } } let target null function observe(data) { Object.keys(data).forEach(key { let value data[key] const dep new Dep() Object.defineProperty(data, key, { get() { dep.depend() return value }, set(newVal) { if (newVal ! value) { value newVal dep.notify() } } }) }) }10.2 虚拟DOM实现function createElement(type, props, ...children) { return { type, props: { ...props, children: children.map(child typeof child object ? child : createTextElement(child) ) } } } function createTextElement(text) { return { type: TEXT_ELEMENT, props: { nodeValue: text, children: [] } } } function render(element, container) { const dom element.type TEXT_ELEMENT ? document.createTextNode() : document.createElement(element.type) Object.keys(element.props) .filter(key key ! children) .forEach(name { dom[name] element.props[name] }) element.props.children.forEach(child render(child, dom)) container.appendChild(dom) }11. 前端工程化工具11.1 简易打包工具const fs require(fs) const path require(path) const parser require(babel/parser) const traverse require(babel/traverse).default const babel require(babel/core) function createAsset(filename) { const content fs.readFileSync(filename, utf-8) const ast parser.parse(content, { sourceType: module }) const dependencies [] traverse(ast, { ImportDeclaration: ({ node }) { dependencies.push(node.source.value) } }) const { code } babel.transformFromAstSync(ast, null, { presets: [babel/preset-env] }) return { filename, dependencies, code } } function createGraph(entry) { const mainAsset createAsset(entry) const queue [mainAsset] for (const asset of queue) { asset.mapping {} const dirname path.dirname(asset.filename) asset.dependencies.forEach(relativePath { const absolutePath path.join(dirname, relativePath) const child createAsset(absolutePath) asset.mapping[relativePath] child.filename queue.push(child) }) } return queue } function bundle(graph) { let modules graph.forEach(mod { modules ${mod.filename}: [ function(require, module, exports) { ${mod.code} }, ${JSON.stringify(mod.mapping)} ], }) const result (function(modules) { function require(filename) { const [fn, mapping] modules[filename] function localRequire(relativePath) { return require(mapping[relativePath]) } const module { exports: {} } fn(localRequire, module, module.exports) return module.exports } require(${graph[0].filename}) })({${modules}}) return result }12. 性能监控与错误追踪12.1 性能指标采集class PerformanceMonitor { constructor() { this.metrics {} this.init() } init() { window.addEventListener(load, () { setTimeout(() { this.collectTiming() this.collectResources() this.sendToServer() }, 0) }) } collectTiming() { const timing performance.timing this.metrics { dns: timing.domainLookupEnd - timing.domainLookupStart, tcp: timing.connectEnd - timing.connectStart, ttfb: timing.responseStart - timing.requestStart, domReady: timing.domContentLoadedEventEnd - timing.navigationStart, load: timing.loadEventEnd - timing.navigationStart } } collectResources() { const resources performance.getEntriesByType(resource) this.metrics.resources resources.map(r ({ name: r.name, duration: r.duration, type: r.initiatorType })) } sendToServer() { navigator.sendBeacon(/analytics, JSON.stringify(this.metrics)) } }12.2 错误监控class ErrorTracker { constructor() { this.init() } init() { window.addEventListener(error, this.handleError.bind(this)) window.addEventListener(unhandledrejection, this.handleRejection.bind(this)) } handleError(event) { const { message, filename, lineno, colno, error } event this.report({ type: JS_ERROR, message, stack: error?.stack, location: ${filename}:${lineno}:${colno} }) } handleRejection(event) { this.report({ type: PROMISE_REJECTION, reason: event.reason?.message || String(event.reason) }) } report(data) { const body JSON.stringify({ timestamp: new Date().toISOString(), url: window.location.href, ...data }) navigator.sendBeacon(/error-log, body) } }13. 前端面试实战技巧在实际面试中除了能够正确实现代码外还需要注意以下要点代码风格保持一致的缩进和命名规范适当添加注释边界处理考虑输入为null/undefined/空数组等特殊情况性能分析能够分析算法的时间/空间复杂度测试用例给出典型测试用例验证代码正确性渐进优化先给出基础实现再逐步优化沟通交流边写边解释思路展示思考过程例如在实现深拷贝时可以按照以下步骤进行先给出基础递归版本指出循环引用问题引入WeakMap解决循环引用讨论其他边界情况Date/RegExp等特殊对象分析时间复杂度和空间复杂度给出测试用例验证这种渐进式的解题方式能够全面展示你的技术能力和思维过程给面试官留下更好的印象。