尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaScript面试题全面解析:从闭包到事件循环的核心考点

JavaScript面试题全面解析:从闭包到事件循环的核心考点 做前端这些年面试过别人也被别人面试过回头看看JavaScript这块要考察的点其实特别集中。我把自己整理过的题目重新梳理了一遍按知识点分类写出来每个题目都附上了考察意图和参考思路。这篇文章适合准备面试的初级中级前端也适合带新人的同学拿来当考核题库。内容偏基础但基础的东西往往最见功夫。1. 基础语法与数据类型核心题1.1 变量提升与暂时性死区先说一个面试必问、工作中也容易踩坑的点变量提升。很多人背过概念但真正遇到题目还是会懵。console.log(a); // undefined var a 1; console.log(b); // ReferenceError: Cannot access b before initialization let b 1;用var声明变量会被提升到作用域顶部但赋值不会提升所以第一行输出undefined。而let和const引入了暂时性死区在声明之前访问变量会直接报错。考察这个题目不只是看你对关键字的记忆而是看你对JavaScript执行上下文的理解深度。我一般会追问那函数声明呢函数声明会整个提升包括函数体foo(); // 可以正常执行 function foo() { console.log(hello); }这里有个实用的建议写代码时不要在同一个作用域内混用var和let声明同名变量会出现难以排查的bug。团队规范里建议直接用const声明默认变量需要重新赋值时才用let尽量不用var。1.2 类型转换与NaN的坑JavaScript的类型转换是出了名的灵活灵活到经常出bug。下面这几个题目几乎每次整理都绕不开。console.log(1 1); // 11 console.log(1 - 1); // 0 console.log(true 1); // 2 console.log([] []); // console.log([] {}); // [object Object]加号运算符在处理字符串时会优先做字符串拼接但减号会强制转数字计算。空数组转字符串是空串空对象转字符串是[object Object]所以[] {}的结果是字符串[object Object]。NaN是另一个高频考点。注意NaN NaN返回false因为NaN不等于任何值包括它自己。判断NaN用Number.isNaN()不要用全局的isNaN()后者会把非数字类型先转成数字再判断Number.isNaN(abc); // false isNaN(abc); // true因为abc转成数字是NaN实际开发中后端返回的数据经常需要做容错处理我习惯用Number.isNaN(Number(value))这样的方式先做一次安全转换再判断是否继续往下走这种习惯能在数据清洗阶段拦截大量运行时错误。1.3 和 的差异双等号会做隐式类型转换三等号不会。这个知识点简单但面试官经常出变种题目。我见过一道经典题console.log(0 ); // true console.log(0 0); // true console.log(0 false); // true console.log(null undefined); // true console.log(null undefined); // false这类题目的核心在于理解双等号的转换规则先转成数字再比较但null和undefined比较例外它们俩用双等号比较时直接返回true。说实话工作中我几乎不用双等号全站代码规范都要求用严格相等。但理解双等号规则仍然有意义因为你可能要维护老代码或者排查别人留下的bug。2. 函数、作用域与闭包题2.1 闭包的作用与典型应用闭包是JavaScript面试的重灾区也是容易让初学者觉得抽象的概念。我习惯用一句话概括函数在执行时记住了定义它的作用域这个函数就是一个闭包。典型的闭包面试题function createCounter() { let count 0; return function() { count; return count; }; } const counter createCounter(); console.log(counter()); // 1 console.log(counter()); // 2count变量本应在createCounter执行结束后被销毁但因为内部函数仍然持有对它的引用垃圾回收器不会回收它所以count得以存活。这题真正想考察的是是否理解函数作用域链的机制是否明白垃圾回收与引用持有的关系能否说出闭包的实际应用场景实际开发中闭包常用于封装私有变量、实现模块化、防抖节流函数。我面试时会追问如果这里不用闭包你怎么实现一个计数器用全局变量是一种办法但会污染全局作用域用对象属性可以但属性是公开的。闭包的核心价值就在于提供了一种简洁的私有状态管理方式。这里有个值得注意的问题闭包容易导致内存泄漏。如果闭包引用了大型对象而这个闭包又被长期持有比如挂在全局事件上那这部分内存在页面关闭前都不会被释放。经验做法是用完的DOM监听器要记得移除大对象不用了主动置为null。2.2 this指向问题this的指向问题基本是必考题。我总结了一套简单的判断方法看函数被调用时的调用方式而不是看函数定义在哪里。const obj { name: obj, getName: function() { return this.name; } }; const fn obj.getName; console.log(obj.getName()); // obj console.log(fn()); // undefined严格模式下报错obj.getName()是作为对象方法调用的所以this指向obj。把函数赋给变量再单独调用此时调用者是全局对象this指向全局拿不到name。箭头函数是个特例它没有自己的this会捕获定义时外层作用域的this。面试官很喜欢出对比题const obj { name: obj, getName: () { return this.name; } }; console.log(obj.getName()); // undefined箭头函数的this来自定义时的外层作用域在这里是全局所以拿不到obj.name。日常写代码时在对象方法里嵌套回调函数如果用普通函数需要提前缓存this或者用箭头函数用箭头函数则完全没有这个问题。我个人的经验是回调场景优先用箭头函数需要动态this时再用普通函数配合call、apply、bind来做显式绑定。2.3 防抖与节流的实现防抖节流是高频面试题也是性能优化的重要工具。防抖的核心思路事件触发后等待一段时间再执行如果在这段时间内再次触发则重新计时。function debounce(fn, wait) { let timer null; return function(...args) { clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, wait); }; }节流的核心思路限制函数在一段时间内最多执行一次。function throttle(fn, interval) { let lastTime 0; return function(...args) { const now Date.now(); if (now - lastTime interval) { lastTime now; fn.apply(this, args); } }; }面试时除了让写代码我还会问搜索框的实时联想用防抖还是节流滚动加载更多数据用防抖还是节流答案是搜索联想用防抖因为用户停下输入时才需要请求滚动监听用节流因为需要持续响应但要在一定频率内。这个知识点从原理到应用都要能说清楚。3. 异步编程与事件循环题3.1 Promise的基本用法与链式调用Promise的出现解决了回调地狱问题。面试常考的是链式调用和错误处理。function fetchData(url) { return new Promise((resolve, reject) { setTimeout(() { if (url) { resolve(data from ${url}); } else { reject(url is required); } }, 1000); }); } fetchData(url1) .then(data { console.log(data); return fetchData(url2); }) .then(data { console.log(data); }) .catch(err { console.error(err); });看到这个题目我会追问几个问题then里return一个Promise会怎样会等待这个Promise完成再进入下一个then。catch放置在链尾能捕获前面所有then里的抛错吗能除非前面的错误已经被处理过。如果then里再次抛出错误后面的catch能捕获吗能Promise链会传递错误直到被处理。实际项目中很多人会忽略Promise构造器里的异常捕获比如用JSON.parse解析数据时如果格式不对整个Promise可能会卡在pending状态。稳妥的做法是把可能出错的同步代码放进try...catch中再手动reject。3.2 async/await与错误捕获async/await是Promise的语法糖。面试时考察的重点是如何正确做错误处理。async function loadData() { try { const data await fetchData(url); console.log(data); return data; } catch (error) { console.error(加载失败:, error); return null; } }这里有一个容易忽略的坑await只能捕获Promise被reject的情况捕获不了同步抛出的错误。所以如果有同步代码可能抛异常比如解析JSON最好一并放进try块里。还有一个高频追问await在循环里怎么用如果循环中每个迭代有先后依赖必须串行执行那就用for...of加await如果迭代之间没有依赖应该用Promise.all并发执行来提升性能。// 串行执行 for (const id of ids) { await fetchData(id); } // 并行执行 await Promise.all(ids.map(id fetchData(id)));用Promise.all时要注意只要有一个失败整个Promise.all就reject哪怕其他请求都成功了。如果你希望隔离错误用Promise.allSettled更合适它会等待所有Promise结束再逐个判断成功或失败。这个区分面试很爱考工作中也非常实用。3.3 事件循环宏任务与微任务一道经典的输出顺序题console.log(1); setTimeout(() { console.log(2); }, 0); Promise.resolve().then(() { console.log(3); }); console.log(4); // 输出顺序1, 4, 3, 2为什么是1, 4, 3, 2执行同步代码输出1和4然后微任务队列中的Promise.then先执行输出3最后宏任务setTimeout执行输出2。JavaScript事件循环的规则是每执行完一个宏任务先把微任务队列清空再取下一个宏任务。这个知识点背后是浏览器渲染机制。如果大量使用微任务比如连续await很多Promise渲染会被延后页面可能出现卡顿。反过来requestAnimationFrame和setTimeout这类宏任务会让浏览器有机会在任务间隙做渲染更新。排查页面卡顿问题时我会优先检查是否有循环里疯狂触发微任务的代码这种问题很难用肉眼发现需要借助Performance面板观察任务队列。4. ES6新特性与常用数组方法题4.1 解构赋值与合并对象解构赋值在开发中太常用了几乎每个文件都能看到。面试时会考嵌套解构和默认值const obj { name: test, info: { age: 20, address: { city: 上海 } } }; const { name, info: { age } {}, info: { address: { city } {} } {} } obj;注意解构一个不存在的嵌套属性时会报错因为你在访问undefined的属性。所以嵌套解构时要给每一层都设置默认值或者用可选链操作符。我在代码评审的时候经常看到有人直接解构后端返回的数据其实很危险一旦某个字段缺失整行代码直接崩溃。我会建议配合默认值一起用。合并两个对象是很常见的需求ES6提供了简洁的写法const obj1 { a: 1, b: 2 }; const obj2 { b: 3, c: 4 }; const merged { ...obj1, ...obj2 }; // 结果{ a: 1, b: 3, c: 4 }展开运算符是浅拷贝嵌套对象里的引用仍然共享。如果业务场景不允许共享引用需要做深拷贝常见方案是用structuredClone现代浏览器已支持或者手动递归。我面试时看到候选人直接用JSON.parse(JSON.stringify(obj))做深拷贝通常会追问一句万一对象里有undefined、函数、循环引用呢这三个情况这个方案都处理不了。4.2 filter、map、reduce的对比数组的filter、map、reduce三个方法考察频率极高。面试官经常让用它们替代传统for循环实现数据转换。filter用于过滤const numbers [1, 2, 3, 4, 5]; const evenNumbers numbers.filter(num num % 2 0); // [2, 4]map用于映射转换const doubledNumbers numbers.map(num num * 2); // [2, 4, 6, 8, 10]reduce用于汇总归约const sum numbers.reduce((total, num) total num, 0); // 15这里有一个考察理解深度的变体用reduce实现map和filter的效果。const needFilter numbers.filter(num num 2); const needMap numbers.map(num num * 10); // 等价于 const result numbers.reduce((acc, num) { if (num 2) { acc.push(num * 10); } return acc; }, []);这种写法一次遍历就完成了过滤加转换性能上比先filter再map做两次遍历要好。数据量大的时候能明显感受到差距这个点答出来会比较加分。4.3 可选链与空值合并?.和??这两个运算符在代码评审时看到得越来越多。它们解决的是同一个痛点属性或值可能为空时代码要写大量的防护判断。// 传统的写法 const city user user.info user.info.address ? user.info.address.city : 未知; // 可选链加空值合并 const city user?.info?.address?.city ?? 未知;注意区别??只处理null和undefined不处理空字符串、0、false。如果业务逻辑里空字符串是有效值用||来兜底就会导致逻辑错误应该改用??。我问过不少候选人很多人能说出?.的用法但说不出原理。其实可选链的编译产物就是一组三元表达式加访问判断。了解原理有一个实际好处当你使用Babel或SWC编译代码时可以预期它生成的目标代码体积如果兼容低版本浏览器需要加上对应的polyfill否则老环境运行时会报错。5. DOM操作与浏览器API题5.1 DOM选择与遍历DOM操作是前端的基本功现在框架用得多很多人直接操作DOM的机会变少了但面试仍然会考。// 获取元素 const app document.getElementById(app); const items document.querySelectorAll(.item); // 遍历NodeList items.forEach(item { console.log(item.textContent); }); // 获取父节点和子节点 const parent app.parentNode; const firstChild app.firstElementChild; const children Array.from(app.children);面试常问的一个点querySelectorAll返回的结果是NodeList不是数组。虽然NodeList支持forEach但不支持map、filter、reduce等方法。需要转换时用Array.from()或展开运算符。这个细节很多人踩过坑我排查代码时也遇到过直接调用querySelectorAll(...).map(...)然后报错的情况。5.2 事件监听与事件委托事件相关的题目面试必考。基础写法document.getElementById(btn).addEventListener(click, function(e) { console.log(button clicked, e); });高频考点是事件委托。给父元素监听事件通过事件冒泡机制在e.target上判断具体触发的子元素这样即使子元素是动态添加的也能正常响应事件。document.getElementById(list).addEventListener(click, function(e) { if (e.target.tagName LI) { console.log(点击的项是:, e.target.textContent); } });这里有个实际收益动态渲染的列表不需要每次DOM更新后重新绑定事件减少重复监听带来的内存开销。如果不用事件委托每次新增列表项都要重新绑事件页面复杂以后性能下降非常明显。还要提一下获取坐标的常见需求比如点击位置定位、拖拽动画经常用e.clientX和e.clientY配合getBoundingClientRect()计算元素相对视口的位置。坐标计算是前端开发绕不开的基础特别在做自定义组件的时候很容易出错。5.3 视频元素操作与样式旋转这个题目来自真实需求把页面里的视频旋转90度适合竖屏拍摄的视频在横屏播放的场景。对应的热词里有这样一段缩写代码实际展开后的完整思路是这样的。const video document.querySelector(video); if (video) { video.style.rotate -90deg; // 或者用 transform video.style.transform rotate(-90deg); }两段代码的效果相同但注意style.rotate是较新的独立CSS属性兼容性不如transform老浏览器可能不支持。如果视频需要同时调整尺寸和位置用transform配合translate和scale会更灵活。实际操作中还需要注意旋转视频元素后页面布局可能需要配合调整特别是视频在容器中居中时旋转后往往超出边界。遇到这种情况我一般会动态计算旋转后的宽高再手动设置容器尺寸。这个方法适合临时处理但如果要做成稳定的功能建议把视频放进canvas在canvas层做旋转绘制这样对布局的影响更可控。顺便提一句需要找视频元素时最好加一个id或class定位避免document.querySelector(video)选中了DOM树里隐藏的播放器组件。5.4 表单提交与H5校验的区别表单相关的题目也常出现。经典场景!-- 传统form表单提交 -- form action/submit methodPOST input typetext nameusername required button typesubmit提交/button /form!-- H5校验在input上直接加校验属性 -- form input typeemail nameemail required input typepassword namepassword minlength6 required button typesubmit提交/button /formH5表单校验的好处是不用写JavaScript就能完成基础校验比如required、minlength、maxlength、pattern这些属性。但它有局限性校验规则比较简单无法实现跨字段逻辑校验比如两次密码是否一致。另外不同浏览器对校验失败提示的样式和文案不一致定制能力弱。实际项目中我的做法是两层配合H5校验用于快速过滤明显无效的输入JavaScript校验用于处理复杂业务逻辑最后后端仍然要重新校验一遍。这里要强调一个安全意识前端的任何校验都只是用户体验不能替代后端校验因为请求是可以通过开发者工具直接修改的。5.5 添加账号链接的触发机制热词里有一个“javascript:;”的写法这个需要单独提一下。早期前端经常看到在a标签的href里写javascript:;用来阻止链接的默认跳转行为a hrefjavascript:; onclickhandleClick()添加账号/a在现代开发中不建议继续用它原因是javascript:协议会在当前页面执行一段JavaScript代码很多团队的安全规范会明确禁用。更推荐的替代方案是a href# idaddAccount onclickreturn false;添加账号/a或者彻底不用a标签改用button元素button typebutton idaddAccount添加账号/button这两种方式一致性好也不产生多余的浏览记录。如果项目里有代码审计或扫描工具javascript:协议链接往往是会被单独标记出来的点不要在这个细节上给自己挖坑。6. 常见报错与安全检测题6.1 JavaScript运行时错误的排查思路JavaScript运行时报错有很多种面试一般会考察对常见错误类型的敏感度。ReferenceError引用了一个不存在的变量比如打错变量名或者变量在作用域外访问。TypeError值不是期望的类型比如调用undefined的方法。SyntaxError语法错误代码无法解析这个在构建阶段就会被编译器捕获。RangeError数值超出有效范围比如递归太深导致调用栈溢出。实际排查时我的习惯是分三个步骤第一先看控制台报错信息定位报错文件和行号不急着改代码。第二根据错误类型缩小范围TypeError说明在错误类型上调用了方法ReferenceError说明变量作用域或命名有问题。第三在代码中加日志验证关键变量的值或者直接在浏览器Sources面板打断点。不要靠猜要有依据地修改。6.2 ES模块加载报错的常见场景热词里有一条“failed to load module script: expected a javascript module script but the server responded with a mime type of...”的报错这是开发中很典型的问题。出现这个报错通常是script typemodule加载的JS文件服务器返回的Content-Type不是application/javascript。常见原因有几个文件后缀写错了比如用.js的文件实际内容是纯HTML或纯JSON。开发服务器的静态文件配置有问题没有正确识别JS文件的类型。引用了不存在的路径服务器返回了一个HTML 404页面浏览器识别到类型不是JS直接报错。排查方法是打开浏览器网络面板检查这个脚本的响应类型和状态码。如果返回的是404页面的HTML就去检查路径如果返回的是text/html就去检查静态资源配置。如果是自己搭建的开发服务器需要确保以正确的MIME类型服务JS文件。这类问题在老项目的调试中容易碰到因为有些人习惯新建一个index.html直接用浏览器打开本地file协议加载ES模块本身就会受到CORS限制也推荐大家统一通过本地开发服务器访问项目。6.3 JavaScript框架库漏洞检测“检测到目标站点存在JavaScript框架库漏洞”这类提示来自安全扫描工具比如Wappalyzer的检测结果说明页面加载的某个前端框架或库版本过旧已公开的CVE漏洞没有修复。解决办法很直接升级到安全版本。但在实际操作中很多人会遇到一个尴尬项目里通过CDN引用了老版本的jQuery或Vue升级相比npm包形式更麻烦。个人建议分几步处理第一运行安全检测工具确认具体是哪个库的哪个版本存在漏洞。第二查看官方公告和升级文档确认当前代码有没有使用废弃API。第三在测试环境升级后跑一遍全量回归重点检查这个库相关的功能模块。这里需要提醒不要图省事在页面里同时引入两套相同功能的库可能导致全局变量冲突和一些奇怪的问题。老项目如果短时间内无法升级至少要在入口层做好访问控制并尽快排期处理。前端安全不是网关能完全兜住的依赖库一旦被利用影响面往往超出预期。6.4 检测到旧版本依赖的处理思路换个角度聊旧版本依赖的问题。很多时候不是不想升级而是不敢升牵一发动全身。我的实操建议是用工具先摸清现状比如在package.json里确认版本再用npm outdated查看哪些包有新版本。升级优先级可以从这几个维度来判断已披露高危漏洞的包优先处理长期不维护或已废弃的包重点评估替代方案与安全问题有关的传递依赖需要查npm审计报告升级策略也有讲究先小版本后大版本先在分支上升级跑完整套测试再合并。前端生态变化快依赖升级本身就是一项持续的日常工作别把事情拖成技术债。7. 实用小技巧与避坑指南7.1 监视页面加载状态排查一些问题的时候要看页面在什么时候开始执行脚本或者在什么条件下执行功能代码。常用的是if (document.readyState loading) { document.addEventListener(DOMContentLoaded, init); } else { init(); }这个写法解决了脚本置前置后的问题保证DOM构建完成后再初始化业务逻辑。我周围不少同事习惯直接用window.onload但要注意load会等到页面所有资源图片、样式、子框架等都加载完才触发时间太晚。如果业务不依赖图片DOMContentLoaded通常更合适。7.2 调试时善用console归类这个属于调试技巧。项目大了以后console输出很乱我习惯把日志按类型归类配合代码阅读更清楚。console.error(请求失败:, error); console.warn(参数缺失:, paramName); console.log(用户点击:, event.target); console.table(listData); // 数组或对象列表用表格展示这里有一个团队协作的小细节上线的生产代码里尽量清理无用的console输出特别是不要把敏感信息打在控制台上。用ESLint配合no-console规则来约束开发环境允许打印生产构建自动去除。排查线上问题时console.error比console.log更醒目错误信息加个固定前缀也能方便日志聚合工具检索。7.3 操作数组时避免直接修改原数组写业务代码时经常遇到把一个数组用作baseData后面又对数组做排序、过滤操作结果把baseData也改了导致其他模块复用数据时出现诡异的bug。原因是sort、splice、push这些方法会直接修改原数组而filter、map、concat不会。如果你需要先排序再展示但又要保留原始数据使用slice先拷贝一份再排序const sortedList [...originalList].sort((a, b) a.value - b.value);这也算一个面试的加分点能体现出对数据不可变性的理解。Redux和Vuex这些状态管理库中都强调不可变更新原因就是共享对象被意外修改后很难追踪是谁改的。养成这个编码习惯会少踩很多莫名其妙的坑。7.4 使用JSON.parse的安全姿势接口返回的数据需要解析JSON直接使用JSON.parse(data)存在风险如果数据格式不对会直接抛异常导致整个脚本中断。安全写法是这样的function safeJsonParse(text) { try { return { data: JSON.parse(text), error: null }; } catch (e) { return { data: null, error: e }; } } const result safeJsonParse(responseText); if (result.error) { console.error(JSON解析失败:, result.error); } else { console.log(解析结果:, result.data); }返回一个包含data和error的对象调用方行为更可控。这个写法在Node.js后端处理请求体时同样适用。我见过不少线上故障起因就是上游返回了一个错误页面的HTML字符串前端直接解析失败页面白屏。有了这一层保护至少能降级到错误提示而不是完全不可用。8. 学习路径与刷题建议8.1 我最建议的JavaScript学习顺序根据这些题目我建议按下面的顺序学习和传统的从语法开始不同更侧重重视语言特性和工程实践的结合。第一先过一遍语言基础变量、类型、运算符、流程控制做到心中有框架。第二深入函数式编程基础函数、作用域、闭包、this指向这是JavaScript最重要的部分花多长时间都不亏。第三理解异步模型Promise、async/await、事件循环这一步跨过去了开发体验会顺很多。第四学习和掌握ES6常用特性解构、扩展运算符、可选链、数组新方法日常开发效率能提升一截。第五结合浏览器环境学习DOM操作、事件机制、网络请求、存储等API这是前端区别于后端JavaScript的地方。第六开始做完整的小项目把前面学的语法和API串起来比如做一个待办清单、简易播放器不需要用框架目的是理解原生能力。8.2 刷题过程的心态调整我见过很多人刷题刷到自我怀疑其实没必要。JavaScript的题目尤其是基础语法和异步相关的题目第一次做错太正常了这些题目本身就是在考察编译器执行机制的细节靠直觉往往猜不中。我的建议是每道题不要只看到正确答案要手动在浏览器控制台里跑一遍观察输出再断点进入Source面板一行一行看执行顺序。看一次十次都不如自己手动调试一次来得记忆深刻。没有环境的话直接用浏览器F12控制台就能运行不需要额外安装任何工具。还有一个小技巧把做错的题目用自己的话整理成错题笔记过两周再回来看。我自己的经验是很多题当时只是记住了答案并没有真正理解过两周再做题能暴露底层理解的空缺。8.3 面试之外的实战建议题库只是敲门砖真正的能力体现在解决实际问题上。面试官也会追问实际项目经验所以笔试之后要能聊出真实场景。比如你说会做防抖那你能不能说出自己负责的哪个模块里用到了防抖为什么用防抖而不是节流你说会事件委托那你能不能说出动态列表的事件绑定有什么坑你说会用Promise那遇到并发请求你如何处理错误这些问题都没法临时背靠的是平时工作中多加思考。再提一点保持对JavaScript新特性的敏感度。比如Array.prototype.at、Object.hasOwn、structuredClone这些新API不需要刻意去背但可以定期看浏览器的更新日志。很多新API能直接解决旧的痛点并且在代码评审时用出来会让人眼前一亮。8.4 监控与扩展的建议题目整理出来不是终点它应该随你的工作经历持续更新。我自己的做法是每遇到一个排查了很久的bug就归纳成一道题并记录解法更新到文档里。这里面最主要是因为这种题是“从问题里提炼”的不是“从书上抄出来”的含金量高得多。如果带着团队带新人也可以让新人先刷一遍这组题目再在实际业务中做一个小功能开发基本能判断出他的基础和潜力。前端的技术栈还在不断变化但JavaScript的核心语言机制没有变愿意动手实践的人技术成长通常不会差。最后说一点自己的感受做面试题也好带团队也好做前端这么多年最深的体会是基础越扎实越自由。当年刚入行的时候也觉得什么框架新就追什么后来发现框架是流水的JavaScript本身才是铁打的。把基础打好转什么框架都不慌。这份题目整理出来也是这个初衷希望能对入行不久的朋友提供一点帮助。
返回列表