转载2022凛冬之时三年经验前端面经
Posted 初辰ge
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转载2022凛冬之时三年经验前端面经相关的知识,希望对你有一定的参考价值。
本文系转载
原作者:流年丶风尘
原文链接:https://juejin.cn/post/7173316141161381924
今年的就业形式简直一片黑暗,本着明年会比今年还差的“侥幸心理”,我还是毫不犹豫地选择裸辞了,历经一个半月的努力,收到了还算不错的 offer,薪资和平台都有比较大的提升,但还是和自己的心理预期有着很大的差距。所以得出最大的结论就是:不要裸辞!不要裸辞!不要裸辞!因为面试期间带给人的压力,和现实与理想的落差对心理的摧残是不可估量的,在这样一个环境苟着是不错的选择。
接下来总结一般情况下前端面试中会经历的以下四个阶段和三个决定因素:
作为前端人员,技术的深度广度是排在第一位的,三年是一个分割线,一定要在这个时候找准自己的定位和方向。
其次良好的沟通表达能力、着装和表现等场外因素能提高面试官对你的认可度。
有的人技术很牛逼,但是面试时让面试官觉得不爽,觉得你盛气逼人/形象邋遢/自以为是/表述不清,就直接不要你,那是最得不偿失的。
以下是我整个面试准备以及被问到过的问题的一个凝练,因为在面试过程中,和面试官的交流很大程度并不是简单的背书,最好是要将知识通过自己的总结和凝练表达出来,再根据面试官的提问临场发挥将之补足完善。
一、自我介绍
面试官让你自我介绍,而且不限定自我介绍的范围,肯定是面试官想从你的自我介绍中了解到你,所以介绍一定要保证简短和流畅,面对不同的面试官,自我介绍的内容可以是完全一样的,所以提前准备好说辞很重要,并且一定要注意:不要磕磕巴巴,要自信!流畅的表达和沟通能力,同样是面试官会对候选人考核点之一。我也曾当过面试官,自信大方的候选人,往往更容易受到青睐。
1、个人介绍(基本情况),主要的简历都有了,这方面一定要短
2、个人擅长什么,包括技术上的和非技术上的。技术上可以了解你的转场,非技术可以了解你这个人
3、做过的项目,捡最核心的项目说,不要把所有项目像背书一样介绍
4、自己的一些想法、兴趣或者是观点,甚至包括自己的职业规划。这要是给面试官一个感觉:热衷于"折腾"或者"思考"
示例:
面试官您好,我叫xxx,xx年毕业于xx大学,自毕业以来一直从事着前端开发的相关工作。
我擅长的技术栈是 vue 全家桶,对 vue2 和 vue3 在使用上和源码都有一定程度的钻研;打包工具对 webpack 和 vite
都比较熟悉;有从零到一主导中大型项目落地的经验和能力。在上家公司主要是xx产品线负责人的角色,主要职责是。。。。。。
除了开发相关工作,还有一定的技术管理经验:比如担任需求评审、UI/UE交互评审评委,负责开发排期、成员协作、对成员代码进行review、组织例会等等
平常会在自己搭建的博客上记录一些学习文章或者学习笔记,也会写一些原创的技术文章发表到掘金上,获得过xx奖。
总的来说自我介绍尽量控制在3 - 5分钟之间,简明扼要为第一要义,其次是突出自己的能力和长处。
对于普通的技术面试官来说,自我介绍只是习惯性的面试前的开场白,一般简历上列举的基本信息已经满足他们对你的基本了解了。但是对于主管级别的面试官或者Hr,会看重你的性格、行为习惯、抗压能力等等综合能力。所以要让自己在面试过程尽可能表现的积极向上,爱好广泛、喜欢持续学习,喜欢团队合作,可以无条件加班等等。当然也不是说让你去欺骗,只是在现在这种环境中,这些“侧面能力”也是能在一定程度提升自己竞争力的法宝。
二、项目挖掘
在目前这个行情,当你收到面试通知时,有很大概率是因为你的项目经验和所招聘的岗位比较符合。 所以在项目的准备上要额外上心,比如:
对项目中使用到的技术的深挖
对项目整体设计思路的把控
对项目运作流程的管理
团队协作的能力。
项目的优化点有哪些
这些因人而异就不做赘述了,根据自己的情况好好挖掘即可。
三、个人
先说个人,当你通过了技术面试,到了主管和hr这一步,不管你当前的技术多牛逼,他们会额外考察你个人的潜力、学习能力、性格与团队的磨合等软实力,这里列出一些很容易被问到的:
为什么跳槽?
直接从个人发展入手表现出自己的上进心:
一直想去更大的平台,不但有更好的技术氛围,而且学到的东西也更多
想扩展一下自己的知识面,之前一致是做x端的 xx 产品,技术栈比较单一一点,相对xx进行学习。
之前的工作陷入了舒适圈,做来做去也就那些东西,想要换个平台扩宽自己的技术广度,接触和学习一些新的技术体系,为后续的个人发展更有利
讲讲你和普通前端,你的亮点有哪些?
1、善于规划和总结,我会对自己经手的项目进行一个全面的分析,一个是业务拆解,对个各模块的业务通过脑图进行拆解;另一个就是对代码模块的拆解,按功能去区分各个代码模块。再去进行开发。我觉得这是很多只会进行盲目业务开发的前端做不到的
2、喜欢专研技术,平常对 vue 的源码一直在学习,也有输出自己的技术文章,像之前写过一篇逐行精读 teleport 的源码,花了大约有三十个小时才写出来的,对每一行源码的功能和作用进行了解读(但是为啥阅读和点赞这么低)。
你有什么缺点?
性子比较沉,更偏内向一点,所以我也会尝试让自己变得外向一点。
一个是要开各种评审会,作为前端代表需要我去准备各种材料和进行发言。
所以在团队内做比较多的技术分享,每周主持例会,也让我敢于去表达和探讨。
最近有关注什么新技术吗?
包依赖管理工具 pnpm(不会重复安装依赖,非扁平的node_modules结构,符号链接方式添加依赖包)
打包工具 vite (极速的开发环境)
flutter (Google推出并开源的移动应用程序(App)开发框架,主打跨平台、高保真、高性能)
rust(听说是js未来的基座)
turbopack,webpack的继任者,说是比 vite快10倍,webpack快700倍,然后尤雨溪亲自验证其实并没有比 vite 快10倍
webcomponents
你是偏向于走各个方向探索还是一直向某个方向研究下去?
我对个人的规划是这样的:
3 - 5 年在提高自己的技术深度的同时,扩宽自己的知识面,就是深度和广度上都要有提升,主要是在广度上,充分对大前端有了认知才能更好的做出选择
5 - 7 年就是当有足够的知识积累之后再选择某一个感兴趣方向深研下去,争取成为那个领域的专家
团队规模,团队规范和开发流程
这个因人而异,如实准备即可,因为不同规模团队的研发模式差别是很大的。
代码 review 的目标
1、最注重的是代码的可维护性(变量命名、注释、函数单一性原则等)
2、扩展性:封装能力(组件、代码逻辑是否可复用、可扩展性)
3、ES 新特性(es6+ 、ES2020, ES2021 可选链、at)
4、函数使用规范(比如遇到用 map 拿来当 forEach 用的)
5、性能提升,怎样运用算法,写出更加优雅,性能更好的代码
如何带领团队的
我在上家公司是一个技术管理的角色。
0、落实开发规范,我在公司内部 wiki 上有发过,从命名、最佳实践到各种工具库的使用。新人进来前期我会优先跟进他们的代码质量
1、团队分工:每个人单独负责一个产品的开发,然后公共模块一般我会指定某几个人开发
2、代码质量保证:每周会review他们的代码,也会组织交叉 review 代码,将修改结果输出文章放到 wiki中
3、组织例会:每周组织例会同步各自进度和风险,根据各自的进度调配工作任务
4、技术分享:还会组织不定时的技术分享。一开始就是单纯的我做分享,比如微前端的体系,ice stark 的源码
5、公共需求池:比如webpack5/vite的升级;vue2.7的升级引入setup语法糖;pnpm的使用;拓扑图性能优化
6、优化专项:在第一版产品出来之后,我还发起过性能优化专项,首屏加载性能,打包体积优化;让每个人去负责对应的优化项
对加班怎么看?
我觉得加班一般会有两种情况:
一是项目进度比较紧,那当然以项目进度为先,毕竟大家都靠这个吃饭
二是自身能力问题,对业务不熟啊或者引入一个全新的技术栈,那么我觉得不仅要加班跟上,还要去利用空闲时间抓紧学习,弥补自己的不足
有什么兴趣爱好?
我平常喜欢阅读,就是在微信阅读里读一些心理学、时间管理、还有一些演讲技巧之类的书
然后是写文章,因为我发现单纯的记笔记很容易就忘了,因为只是记载别人的内容,而写自己的原创文章,在这个过程中能将知识非常高的比例转换成自身的东西,所以除了自个发掘金的文章,我也经常会对项目的产出有文章输出到 wiki 上
其他爱好就是和朋友约着打篮球、唱歌
四、技术
技术面试一定要注意:简明扼要,详略得当,不懂的就说不懂。因为在面试过程中是一个和面试官面对面交流的过程,没有面试官会喜欢一个絮絮叨叨半天说不到重点候选人,同时在说话过程中,听者会被动的忽略自己不感兴趣的部分,所以要着重突出某个技术的核心特点,并围绕着核心适当展开。
大厂基本都会通过算法题来筛选候选人,算法没有捷径,只能一步一步地刷题再刷题,这方面薄弱的要提前规划进行个学习了。
技术面过程主要会对前端领域相关的技术进行提问,一般面试官会基于你的建立,而更多的是,面试官基于他之前准备好的面试题,或者所在项目组比较熟悉的技术点进行提问,因为都是未知数,所以方方面面都还是要求比较足的。
如果想进入一个中大型且发展前景不错的公司,并不是照着别人的面经背一背就能糊弄过去的,这里作出的总结虽然每一条都很简短,但都是我对每一个知识点进行全面学习后才提炼出来的部分核心知识点,所以不惧怕面试官的“发散一下思维”。
面试过程一般会涉及到以下八大知识类型的考量:
JS/CSS/TypeScript/框架(Vue、React)/浏览器与网络/性能优化/前端工程化/架构/其他
所以面试前的技术准备绝不是一蹴而就,还需要平时的积累,比如可以利用每天十到二十分钟对其中一个小知识点进行全面的学习,长此以往,无论是几年的面试,都足够侃侃而谈。
JS篇
JS的学习梭哈红包书和冴羽老师的深入JS系列博客就基本ok了
常见的JS面试题一般会有这些
什么是原型/原型链?
原型的本质就是一个对象。
当我们在创建一个构造函数之后,这个函数会默认带上一个prototype属性,而这个属性的值就指向这个函数的原型对象。
这个原型对象是用来为通过该构造函数创建的实例对象提供共享属性,即用来实现基于原型的继承和属性的共享
所以我们通过构造函数创建的实例对象都会从这个函数的原型对象上继承上面具有的属性
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止(最顶层就是Object.prototype的原型,值为null)。
所以通过原型一层层相互关联的链状结构就称为原型链。
什么是闭包?
定义:闭包是指引用了其他函数作用域中变量的函数,通常是在嵌套函数中实现的。
从技术角度上所有 js 函数都是闭包。
从实践角度来看,满足以下俩个条件的函数算闭包
即使创建它的上下文被销毁了,它依然存在。(比如从父函数中返回)
在代码中引用了自由变量(在函数中使用的既不是函数参数也不是函数局部变量的变量称作自由变量)
使用场景:
创建私有变量
vue 中的data,需要是一个闭包,保证每个data中数据唯一,避免多次引用该组件造成的data共享
延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
应用
柯里化函数
例如计数器、延迟调用、回调函数等
this 的指向
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)
1、全局的this非严格模式指向window对象,严格模式指向 undefined
2、对象的属性方法中的this 指向对象本身
3、apply、call、bind 可以变更 this 指向为第一个传参
4、箭头函数中的this指向它的父级作用域,它自身不存在 this
浏览器的事件循环?
js 代码执行过程中,会创建对应的执行上下文并压入执行上下文栈中。
如果遇到异步任务就会将任务挂起,交给其他线程去处理异步任务,当异步任务处理完后,会将回调结果加入事件队列中。
当执行栈中所有任务执行完毕后,就是主线程处于闲置状态时,才会从事件队列中取出排在首位的事件回调结果,并把这个回调加入执行栈中然后执行其中的代码,如此反复,这个过程就被称为事件循环。
事件队列分为了宏任务队列和微任务队列,在当前执行栈为空时,主线程回先查看微任务队列是否有事件存在,存在则依次执行微任务队列中的事件回调,直至微任务队列为空;不存在再去宏任务队列中处理。
常见的宏任务有setTimeout()、setInterval()、setImmediate()、I/O、用户交互操作,UI渲染
常见的微任务有promise.then()、promise.catch()、new MutationObserver、process.nextTick()
宏任务和微任务的本质区别
宏任务有明确的异步任务需要执行和回调,需要其他异步线程支持
微任务没有明确的异步任务需要执行,只有回调,不需要其他异步线程支持。
javascript中数据在栈和堆中的存储方式
1、基本数据类型大小固定且操作简单,所以放入栈中存储
2、引用数据类型大小不确定,所以将它们放入堆内存中,让它们在申请内存的时候自己确定大小
3、这样分开存储可以使内存占用最小。栈的效率高于堆
4、栈内存中变量在执行环境结束后会立即进行垃圾回收,而堆内存中需要变量的所有引用都结束才会被回收
讲讲v8垃圾回收
1、根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同分代采用不同的回收算法
2、新生代采用空间换时间的 scavenge 算法:整个空间分为两块,变量仅存在其中一块,回收的时候将存活变量复制到另一块空间,不存活的回收掉,周而复始轮流操作
3、老生代使用标记清除和标记整理,标记清除:遍历所有对象标记标记可以访问到的对象(活着的),然后将不活的当做垃圾进行回收。回收完后避免内存的断层不连续,需要通过标记整理将活着的对象往内存一端进行移动,移动完成后再清理边界内存
函数调用的方法
1、普通function直接使用()调用并传参,如:function test(x, y) return x + y,test(3, 4)
2、作为对象的一个属性方法调用,如:const obj = test: function (val) return val , obj.test(2)
3、使用call或apply调用,更改函数 this 指向,也就是更改函数的执行上下文
4、new可以间接调用构造函数生成对象实例
defer和async的区别
一般情况下,当执行到 script 标签时会进行下载 + 执行两步操作,这两步会阻塞 html 的解析;
async 和 defer 能将script的下载阶段变成异步执行(和 html解析同步进行);
async下载完成后会立即执行js,此时会阻塞HTML解析;
defer会等全部HTML解析完成且在DOMContentLoaded 事件之前执行。
Vue篇
笔主是主要从事 Vue相关开发的,也做过 react 相关的项目,当然 react 也只是能做项目的水平,所以在简历中属于一笔带过的那种,框架贵不在多而在精,对Vue源码系列的学习让我对Vue还是十分自信的。学习过程也是如此,如果你能够对一门框架达到精通原理的掌握程度,学习其他框架不过只是花时间的事情。
vue和react的区别
1、数据可变性
React 推崇函数式编程,数据不可变以及单向数据流,只能通过setState或者onchange来实现视图更新
Vue 基于数据可变,设计了响应式数据,通过监听数据的变化自动更新视图
2、写法
React 推荐使用 jsx + inline style的形式,就是 all in js
Vue 是单文件组件(SFC)形式,在一个组件内分模块(tmplate/script/style),当然vue也支持jsx形式,可以在开发vue的ui组件库时使用
3、diff 算法
Vue2采用双端比较,Vue3采用快速比较
react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。,需要使用shouldComponentUpdate()来手动优化react的渲染。
vue组件通信方式
props / $emit
ref / $refs
parent/root
attrs / listeners
eventBus / vuex / pinia / localStorage / sessionStorage / Cookie / window
provide / inject
vue 渲染列表为什么要加key?
Vue 在处理更新同类型 vnode 的一组子节点(比如v-for渲染的列表节点)的过程中,为了减少 DOM 频繁创建和销毁的性能开销:
对没有 key 的子节点数组更新是通过就地更新的策略。它会通过对比新旧子节点数组的长度,先以比较短的那部分长度为基准,将新子节点的那一部分直接 patch 上去。然后再判断,如果是新子节点数组的长度更长,就直接将新子节点数组剩余部分挂载;如果是新子节点数组更短,就把旧子节点多出来的那部分给卸载掉)。所以如果子节点是组件或者有状态的 DOM 元素,原有的状态会保留,就会出现渲染不正确的问题。
有 key 的子节点更新是调用的patchKeyedChildren,这个函数就是大家熟悉的实现核心 diff 算法的地方,大概流程就是同步头部节点、同步尾部节点、处理新增和删除的节点,最后用求解最长递增子序列的方法区处理未知子序列。是为了最大程度实现对已有节点的复用,减少 DOM 操作的性能开销,同时避免了就地更新带来的子节点状态错误的问题。
综上,如果是用 v-for 去遍历常量或者子节点是诸如纯文本这类没有”状态“的节点,是可以使用不加 key 的写法的。但是实际开发过程中更推荐统一加上 key,能够实现更广泛场景的同时,避免了可能发生的状态更新错误,我们一般可以使用 ESlint 配置 key 为 v-for 的必需元素。
vue3 相对 vue2的响应式优化
vue2使用的是Object.defineProperty去监听对象属性值的变化,但是它不能监听对象属性的新增和删除,所以需要使用
s
e
t
、
set、
set、delete这种语法糖去实现,这其实是一种设计上的不足。
所以 vue3 采用了proxy去实现响应式监听对象属性的增删查改。
其实从api的原生性能上proxy是比Object.defineProperty要差的。
而 vue 做的响应式性能优化主要是在将嵌套层级比较深的对象变成响应式的这一过程。
vue2的做法是在组件初始化的时候就递归执行Object.defineProperty把子对象变成响应式的;
而vue3是在访问到子对象属性的时候,才会去将它转换为响应式。这种延时定义子对象响应式会对性能有一定的提升
Vue 核心diff流程
前提:当同类型的 vnode 的子节点都是一组节点(数组类型)的时候,
步骤:会走核心 diff 流程
Vue3是快速选择算法
同步头部节点
同步尾部节点
新增新的节点
删除多余节点
处理未知子序列(贪心 + 二分处理最长递增子序列)
Vue2是双端比较算法
在新旧字节点的头尾节点,也就是四个节点之间进行对比,找到可复用的节点,不断向中间靠拢的过程
diff目的:diff 算法的目的就是为了尽可能地复用节点,减少 DOM 频繁创建和删除带来的性能开销
vue双向绑定原理
基于 MVVM 模型,viewModel(业务逻辑层)提供了数据变化后更新视图和视图变化后更新数据这样一个功能,就是传统意义上的双向绑定。
Vue2.x 实现双向绑定核心是通过三个模块:Observer监听器、Watcher订阅者和Compile编译器。
首先监听器会监听所有的响应式对象属性,编译器会将模板进行编译,找到里面动态绑定的响应式数据并初始化视图;watchr 会去收集这些依赖;当响应式数据发生变更时Observer就会通知 Watcher;watcher接收到监听器的信号就会执行更新函数去更新视图;
vue3的变更是数据劫持部分使用了porxy 替代 Object.defineProperty,收集的依赖使用组件的副作用渲染函数替代watcher
v-model 原理
V-model 是用来监听用户事件然后更新数据的语法糖。
其本质还是单向数据流,内部是通过绑定元素的 value 值向下传递数据,然后通过绑定 input 事件,向上接收并处理更新数据。
单向数据流:父组件传递给子组件的值子组件不能修改,只能通过emit事件让父组件自个改。
vue 响应式原理
不管vue2 还是 vue3,响应式的核心就是观察者模式 + 劫持数据的变化,在访问的时候做依赖收集和在修改数据的时候执行收集的依赖并更新数据。具体点就是:
vue2 的话采用的是 Object.definePorperty劫持对象的 get 和 set 方法,每个组件实例都会在渲染时初始化一个 watcher 实例,它会将组件渲染过程中所接触的响应式变量记为依赖,并且保存了组件的更新方法 update。当依赖的 setter 触发时,会通知 watcher 触发组件的 update 方法,从而更新视图。
Vue3 使用的是 ES6 的 proxy,proxy 不仅能够追踪属性的获取和修改,还可以追踪对象的增删,这在 vue2中需要
set/delete 才能实现。然后就是收集的依赖是用组件的副作用渲染函数替代 watcher 实例。
性能方面,从原生 api 角度,proxy 这个方法的性能是不如 Object.property,但是 vue3 强就强在一个是上面提到的可以追踪对象的增删,第二个是对嵌套对象的处理上是访问到具体属性才会把那个对象属性给转换成响应式,而 vue2 是在初始化的时候就递归调用将整个对象和他的属性都变成响应式,这部分就差了。
computed 和 watch
Computed 的大体实现和普通的响应式数据是一致的,不过加了延时计算和缓存的功能:
在访问computed对象的时候,会触发 getter ,初始化的时候将 computed 属性创建的 watcher (vue3是副作用渲染函数)添加到与之相关的响应式数据的依赖收集器中(dep),然后根据里面一个叫 dirty 的属性判断是否要收集依赖,不需要的话直接返回上一次的计算结果,需要的话就执行更新重新渲染视图。
$nextTick 原理?
vue有个机制,更新 DOM 是异步执行的,当数据变化会产生一个异步更行队列,要等异步队列结束后才会统一进行更新视图,所以改了数据之后立即去拿 dom 还没有更新就会拿不到最新数据。所以提供了一个 nextTick 函数,它的回调函数会在DOM 更新后立即执行。
nextTick 本质上是个异步任务,由于事件循环机制,异步任务的回调总是在同步任务执行完成后才得到执行。所以源码实现就是根据环境创建异步函数比如 Promise.then(浏览器不支持promise就会用MutationObserver,浏览器不支持MutationObserver就会用setTimeout),然后调用异步函数执行回调队列。
所以项目中不使用$nextTick的话也可以直接使用Promise.then或者SetTimeout实现相同的效果
Vue 异常处理
1、全局错误处理:Vue.config.errorHandler
Vue.config.errorHandler = function(err, vm, info) ;
如果在组件渲染时出现运行错误,错误将会被传递至全局Vue.config.errorHandler 配置函数 (如果已设置)。
比如前端监控领域的 sentry,就是利用这个钩子函数进行的 vue 相关异常捕捉处理
2、全局警告处理:Vue.config.warnHandler
Vue.config.warnHandler = function(msg, vm, trace) ;
注意:仅在开发环境生效
像在模板中引用一个没有定义的变量,它就会有warning
3、单个vue 实例错误处理:renderError
const app = new Vue(
el: "#app",
renderError(h, err)
return h("pre", style: color: "red" , err.stack);
);
和组件相关,只适用于开发环境,这个用处不是很大,不如直接看控制台
4、子孙组件错误处理:errorCaptured
Vue.component("cat",
template: `<div><slot></slot></div>`,
props: name: type: string ,
errorCaptured(err, vm, info)
console.log(`cat EC: $err.toString()\\ninfo: $info`);
return false;
);
注:只能在组件内部使用,用于捕获子孙组件的错误,一般可以用于组件开发过程中的错误处理
5、终极错误捕捉:window.onerror
window.onerror = function(message, source, line, column, error) ;
它是一个全局的异常处理函数,可以抓取所有的 JavaScript 异常
Vuex 流程 & 原理
Vuex 利用 vue 的mixin 机制,在beforeCreate 钩子前混入了 vuexinit 方法,这个方法实现了将 store 注入 vue 实例当中,并注册了 store 的引用属性 ,所以可以使用‘this.store.xxx`去引入vuex中定义的内容。
然后 state 是利用 vue 的 data,通过new Vue(data: $$state: state 将 state 转换成响应式对象,然后使用 computed 函数实时计算 getter
Vue.use函数里面具体做了哪些事
概念
可以通过全局方法Vue.use()注册插件,并能阻止多次注册相同插件,它需要在new Vue之前使用。
该方法第一个参数必须是Object或Function类型的参数。如果是Object那么该Object需要定义一个install方法;如果是Function那么这个函数就被当做install方法。
Vue.use()执行就是执行install方法,其他传参会作为install方法的参数执行。
所以Vue.use()本质就是执行需要注入插件的install方法。
怎么编写一个vue插件
要暴露一个install方法,第一个参数是Vue构造器,第二个参数是一个可选的配置项对象
Myplugin.install = function(Vue, options = )
// 1、添加全局方法或属性
Vue.myGlobalMethod = function()
// 2、添加全局服务
Vue.directive('my-directive',
bind(el, binding, vnode, pldVnode)
)
// 3、注入组件选项
Vue.mixin(
created: function()
)
// 4、添加实例方法
Vue.prototype.$myMethod = function(methodOptions)
CSS篇
Css直接面试问答的题目相对来说比较少,更多的是需要你能够当场手敲代码实现功能,一般来说备一些常见的布局,熟练掌握flex基本就没有什么问题了。
什么是 BFC
Block Formatting context,块级格式上下文
BFC 是一个独立的渲染区域,相当于一个容器,在这个容器中的样式布局不会受到外界的影响。
比如浮动元素、绝对定位、overflow 除 visble 以外的值、display 为 inline/tabel-cells/flex 都能构建 BFC。
常常用于解决
处于同一个 BFC 的元素外边距会产生重叠(此时需要将它们放在不同 BFC 中);
清除浮动(float),使用 BFC 包裹浮动的元素即可
阻止元素被浮动元素覆盖,应用于两列式布局,左边宽度固定,右边内容自适应宽度(左边float,右边 overflow)
伪类和伪元素及使用场景
伪类
伪类即:当元素处于特定状态时才会运用的特殊类
开头为冒号的选择器,用于选择处于特定状态的元素。比如:first-child选择第一个子元素;:hover悬浮在元素上会显示;:focus用键盘选定元素时激活;:link + :visted点击过的链接的样式;:not用于匹配不符合参数选择器的元素;:fist-child匹配元素的第一个子元素;:disabled 匹配禁用的表单元素
伪元素
伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过::before 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。示例:
::before 在被选元素前插入内容。需要使用 content 属性来指定要插入的内容。被插入的内容实际上不在文档树中
h1:before
content: "Hello ";
::first-line 匹配元素中第一行的文本
src 和 href 区别
href是Hypertext Reference的简写,表示超文本引用,指向网络资源所在位置。href 用于在当前文档和引用资源之间确立联系
src是source的简写,目的是要把文件下载到html页面中去。src 用于替换当前内容
浏览器解析方式
当浏览器遇到href会并行下载资源并且不会停止对当前文档的处理。(同时也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式)
当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载或执行完毕。(这也是script标签为什么放在底部而不是头部的原因)
不定宽高元素的水平垂直居中
flex
<div class="wrapper flex-center">
<p>horizontal and vertical</p>
</div>
.wrapper
width: 900px;
height: 300px;
border: 1px solid #ccc;
.flex-center // 注意是父元素
display: flex;
justify-content: center; // 主轴(竖线)上的对齐方式
align-items: center; // 交叉轴(横轴)上的对齐方式
flex + margin
<div class="wrapper">
<p>horizontal and vertical</p>
</div>
.wrapper
width: 900px;
height: 300px;
border: 1px solid #ccc;
display: flex;
.wrapper > p
margin: auto;
Transform + absolute
<div class="wrapper">
<img src="test.png">
</div>
.wrapper
width: 300px;
height: 300px;
border: 1px solid #ccc;
position: relative;
.wrapper > img
position: absolute;
left: 50%;
top: 50%;
tansform: translate(-50%, -50%)
注:使用该方法只适用于行内元素(a、img、label、br、select等)(宽度随元素的内容变化而变化),用于块级元素(独占一行)会有问题,left/top 的50%是基于图片最左侧的边来移动的,tanslate会将多移动的图片自身的半个长宽移动回去,就实现了水平垂直居中的效果
display: table-cell
<div class="wrapper">
<p>absghjdgalsjdbhaksldjba</p>
</div>
.wrapper
width: 900px;
height: 300px;
border: 1px solid #ccc;
display: table-cell;
vertical-align: middle;
text-align: center;
浏览器和网络篇
浏览器和网络是八股中最典型的案例了,无论你是几年经验,只要是前端,总会有问到你的浏览器和网络协议。
跨页面通信的方法?
这里分了同源页面和不同源页面的通信。
不同源页面可以通过 iframe 作为一个桥梁,因为 iframe 可以指定 origin 来忽略同源限制,所以可以在每个页面都嵌入同一个 iframe 然后监听 iframe 中传递的 message 就可以了。
同源页面的通信大致分为了三类:广播模式、共享存储模式和口口相传模式
第一种广播模式,就是可以通过 BroadCast Channel、Service Worker 或者 localStorage 作为广播,然后去监听广播事件中消息的变化,达到页面通信的效果。
第二种是共享存储模式,我们可以通过Shared Worker 或者 IndexedDB,创建全局共享的数据存储。然后再通过轮询去定时获取这些被存储的数据是否有变更,达到一个的通信效果。像常见cookie 也可以作为实现共享存储达到页面通信的一种方式
最后一种是口口相传模式,这个主要是在使用 window.open 的时候,会返回被打开页面的 window 的引用,而在被打开的页面可以通过 window.opener 获取打开它的页面的 window 点引用,这样,多个页面之间的 window 是能够相互获取到的,传递消息的话通过 postMessage 去传递再做一个事件监听就可以了
详细说说 HTTP 缓存
在浏览器第一次发起请求服务的过程中,会根据响应报文中的缓存标识决定是否缓存结果,是否将缓存标识和请求结果存入到浏览器缓存中。
HTTP 缓存分为强制缓存和协商缓存两类。
强制缓存就是请求的时候浏览器向缓存查找这次请求的结果,这里分了三种情况,没查找到直接发起请求(和第一次请求一致);查找到了并且缓存结果还没有失效就直接使用缓存结果;查找到但是缓存结果失效了就会使用协商缓存。
强制缓存有 Expires 和 Cache-control 两个缓存标识,Expires 是http/1.0 的字段,是用来指定过期的具体的一个时间(如 Fri, 02 Sep 2022 08:03:35 GMT),当服务器时间和浏览器时间不一致的话,就会出现问题。所以在 http1.1 添加了 cache-control 这个字段,它的值规定了缓存的范围(public/private/no-cache/no-store),也可以规定缓存在xxx时间内失效(max-age=xxx)是个相对值,就能避免了 expires带来的问题。
协商缓存就是强制缓存的缓存结果失效了,浏览器携带缓存标识向服务器发起请求,有服务器通过缓存标识决定是否使用缓存的过程。
控制协商缓存的字段有 last-modified / if-modified-since 和 Etag / if-none-match,后者优先级更高。
大致过程就是通过请求报文传递 last-modified 或 Etag 的值给服务器与服务器中对应值作对比,若和响应报文中的 if-modified-since 或 if-none-match 结果一致,则协商缓存有效,使用缓存结果,返回304;否则失效,重新请求结果,返回200
输入 URL 到页面展现的全过程
用户输入一段内容后,浏览器会先去判断这段内容是搜索内容还是 URL ,是搜索内容的话就会接合默认的搜索引擎生成 URL,比如 google 浏览器是goole.com/search?xxxx,如果是 URL 会拼接协议,比如 http/https。当页面没有监听 beforeupload 时间或者同意了继续执行流程,浏览器图标栏会进入加载中的状态。
接下来浏览器进程会通过 IPC 进程间通信将 URL 请求发送给网络进程,网络进程会先去缓存中查找该资源,如果有则拦截请求并直接200返回,没有的话会进入网络请求流程。
网络请求流程是网络进程请求 DNS 服务器返回域名对应的IP和端口号(如果这些之前有缓存也是直接返回缓存结果),如果没有端口号,http默认为80,https默认为443,如果是https还需要建立 TLS 安全连接创建加密的数据通道。
接着就是 TCP 三次握手建立浏览器和服务器连接,然后进行数据传输,数据传输完成四次挥手断开连接,如果设置了connection: keep-alive就可以一直保持连接。
网络进程将通过TCP获取的数据包进行解析,首先是根据响应头的content-type来判断数据类型,如果是字节流或者文件类型的话,会交给下载管理器进行下载,这时候导航流程就结束了。如果是 text/html 类型,就会通知到浏览器进程获取文档进行渲染。
浏览器进程获取到渲染的通知,会根据当前页面和新输入的页面判断是否是同一个站点,是的话就复用之前网页创建的渲染进程,否则的话会新创建一个单独的渲染进程。
浏览器进程将“提交文档”的消息给渲染进程,渲染进程接收到消息就会和网络进程建立传输数据的通道,数据传输完成后就返回“确认提交”的信息给浏览器进程。
浏览器接收到渲染进程的“确认提交“的消息后,就会更新浏览器的页面状态:安全状态、地址栏 URL、前进后退的历史消息,并更新 web页面,此时页面是空白页面(白屏)。
页面渲染过程(重点记忆)
最后是渲染进程对文档进行页面解析和子资源加载,渲染进程会将 HTML 转换成 DOM 树结构,将 css 转换成 styleSeets ( CSSOM)。然后复制 DOM 树过滤掉不显示的元素创建基本的渲染树,接着计算每个 DOM 节点的样式和计算每个节点的位置布局信息构建成布局树。
具有层叠上下文或者需要要裁剪的地方会独立创建图层,这就是分层,最终会形成一个分层树,渲染进程会给每个图层生成绘制列表并提交给合成线程,合成线程将图层分成图块(避免一次性绘制图层所有内容,可以根据图块优先需渲染视口部分),并在光栅化线程池中将图块转换成位图。
转换完毕后合成线程发送绘制图块命令 DrawQuard 给浏览器进程,浏览器根据 DrawQuard 消息生成页面,并显示到浏览器上。
跨域常用方案
什么是跨域?
协议 + 域名 + 端口号均相同时则为同域,任意一个不同则为跨域
解决方案
1、 传统的jsonp:利用
2、 一般使用 cors(跨域资源共享)来解决跨域问题,浏览器在请求头中发送origin字段指明请求发起的地址,服务端返回Access-control-allow-orign,如果一致的话就可以进行跨域访问
3、 Iframe 解决主域名相同,子域名不同的跨域请求
4、 浏览器关闭跨域限制的功能
5、 http-proxy-middleware 代理
XSS 和 CSRF
xss基本概念
Xss (Cross site scripting)跨站脚本攻击,为了和 css 区别开来所以叫 xss
Xss 指黑客向 html 或 dom 中注入恶意脚本,从而在用户浏览页面的时候利用注入脚本对用户实施攻击的手段
恶意脚本可以做到:窃取 cookie 信息、监听用户行为(比如表单的输入)、修改DOM(比如伪造登录界面骗用户输入账号密码)、在页面生成浮窗广告等
websocket
websocket是一种支持双向通信的协议,就是服务器可以主动向客户端发消息,客户端也可以主动向服务器发消息。
它是基于 HTTP 协议来建立连接的的,与http协议的兼容性很好,所以能通过 HTTP 代理服务器;没有同源限制。
WebSocket 是一种事件驱动的协议,这意味着可以将其用于真正的实时通信。与 HTTP 不同(必须不断地请求更新),而使用 websockets,更新在可用时就会立即发送
当连接终止时,WebSockets 不会自动恢复,这是应用开发中需要自己实现的机制,也是存在许多客户端开源库的原因之一。
像webpack和vite的devServer就使用了websocket实现热更新
Post 和 Get 区别
应用场景: (GET 请求是一个幂等的请求)一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。(而 Post 不是一个幂等的请求)一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。(幂等是指一个请求方法执行多次和仅执行一次的效果完全相同)
是否缓存: 因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。
传参方式不同: Get 通过查询字符串传参,Post 通过请求体传参。
安全性: Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。
请求长度: 浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。
参数类型: get参数只允许ASCII字符,post 的参数传递支持更多的数据类型(如文件、图片)。
性能优化篇
性能优化是中大型公司非常注重的点,因为和前端人的KPI息息相关,所以自然会作为面试题的常客。
性能优化有哪些手段
从缓存的角度
将一些不常变的大数据通过localstorage/sessionStorage/indexdDB 进行读取
活用 http 缓存(强缓存和协商缓存),将内容存储在内存或者硬盘中,减少对服务器端的请求
网络方面比较常用的是静态资源使用 CDN
打包方面
路由的按需加载
优化打包后资源的大小
开启 gzip 压缩资源
按需加载三方库
代码层面
减少不必要的请求,删除不必要的代码
避免耗时过长的js处理阻塞主线程(耗时且无关 DOM 可以丢到web worker去处理或者拆分成小的任务)
图片可以使用懒加载的方式,长列表使用虚拟滚动
首屏速度提升
代码压缩,减少打包的静态资源体积(Terser plugin/MiniCssExtratplugin)
路由懒加载,首屏就只会请求第一个路由的相关资源
使用 cdn加速第三方库,我们是toB的产品,会需要部署在内网,所以一般不用,toC用的多
ssr 服务端渲染,由服务器直接返回拼接好的html页面
vue 常见的性能优化方式
图片懒加载: vue-lazyLoad
虚拟滚动
函数式组件
v-show/ keep-alive 复用 dom
deffer延时渲染组件(requestIdleCallback)
时间切片 time slicing
前端监控 SDK 技术要点
可以通过window.performance获取各项性能指标数据
大图片优化的方案
优化请求数
雪碧图,将所有图标合并成一个独立的图片文件,再通过 background-url和backgroun-position来显示图标
懒加载,尽量只加载用户正则浏览器或者即将浏览的图片。最简单使用监听页面滚动判断图片是否进入视野;使用 intersection Observer API;使用已知工具库;使用css的background-url来懒加载
base64,小图标或骨架图可以使用内联 base64因为 base64相比普通图片体积大。注意首屏不需要懒加载,设置合理的占位图避免抖动。
减小图片大小
使用合适的格式比如WebP、svg、video替代 GIF 、渐进式 JPEG
削减图片质量
使用合适的大小和分辨率
删除冗余的图片信息
Svg 压缩
缓存
代码优化
非响应式变量可以定义在created钩子中使用 this.xxx 赋值
访问局部变量比全局变量块,因为不需要切换作用域
尽可能使用 const声明变量,注意数组和对象
使用 v8 引擎时,运行期间,V8 会将创建的对象与隐藏类关联起来,以追踪它们的属性特征。能够共享相同隐藏类的对象性能会更好,v8 会针对这种情况去优化。所以为了贴合”共享隐藏类“,我们要避免”先创建再补充“式的动态属性复制以及动态删除属性(使用delete关键字)。即尽量在构造函数/对象中一次性声明所有属性。属性删除时可以设置为 null,这样可以保持隐藏类不变和继续共享。
避免内存泄露的方式
尽可能少创建全局变量
手动清除定时器
少用闭包
清除 DOM 引用
弱引用
避免强制同步,在修改 DOM 之前查询相关值
避免布局抖动(一次 JS 执行过程中多次执行强制布局和抖动操作),尽量不要在修改 DOM 结构时再去查询一些相关值
合理利用 css 合成动画,如果能用 css 处理就交给 css。因为合成动画会由合成线程执行,不会占用主线程
避免频繁的垃圾回收,优化存储结构,避免小颗粒对象的产生
感兴趣的可以看看我之前的一篇性能优化技巧整理的文章极意 · 代码性能优化之道
前端工程化篇
前端工程化是前端er成长路上最必不可少的技能点,一切能够提高前端开发效率的功能都可以当做前端工程化的一部分。刚入门的可以从零搭建脚手架开始万字长文详解从零搭建企业级 vue3 + vite2+ ts4 框架全过程
对于面试而言,考察的最多的还是对打包工具的掌握程度。
webpack的执行流程和生命周期
webpack 是为现代 JS 应用提供静态资源打包功能的 bundle。
核心流程有三个阶段: 初始化阶段、构建阶段和生成阶段
1、初始化阶段,会从配置文件、配置对象和Shell参数中读取初始化的参数并与默认配置结合成最终的参数,之以及创建 compiler 编译器对象和初始化它的运行环境
2、构建阶段,编译器会执行它的 run()方法开始编译的过程,其中会先确认 entry 入口文件,从入口文件开始搜索和入口文件有直接或者简介关联的所有文件创建依赖对象,之后再根据依赖对象创建 module 对象,这时候会使用 loader 将模块转换标准的 js 内容,再调用 js 的解释器将内容转换成 AST 对象,再从 AST 中找到该模块依赖的模块,递归本步骤知道所有入口依赖文件都经过了本步骤的处理。最后完成模块编译,得到了每个模块被翻译的内容和他们之间的关系依赖图(dependency graph),这个依赖图就是项目所有用到的模块的映射关系。
3、生成阶段,将编译后的 module 组合成 chunk ,再把每个 chunk 转换成一个单独的文件输出到文件列表,确定好输出内容后,根据配置确定输出路径和文件名,就把文件内容写入文件系统
webpack的plugin 和loader
loader
webpack只能理解 JS 和 JSON 文件,loader 本质上就是个转换器,能将其他类型的文件转换成 webpack 识别的东西
loader 会在 webpack 的构建阶段将依赖对象创建的 module 转换成标准的 js 内容的东西。比如 vue-loader 将vue文件转换成 js 模块,图片字体通过 url-loader 转换成 data URL,这些 webpack 能够识别的东西。
可以在 module.rules 中配置不同的 loader 解析不同的文件
plugin
插件本质是一个带有 apply 函数的类class myPlugin apply(compiler) ,这个apply 函数有个参数 compiler 是webpack 初始化阶段生成的编译器对象,可以调用编译器对象中的 hooks 注册各种钩子的回调这些 hooks 是贯穿整个编译的生命周期。所以开发者可以通过钩子回调在里面插入特定的代码,实现特定的功能。
比如stylelint plugin可以指定 stylelint 的需要检查文件类型和文件范围;HtmlWebpackPlugin 用来生成打包后的模板文件;MiniCssExtactPlugin会将所有的css提取成独立的chunks,stylelintplugin可以在开发阶段提供样式的检查功能。
webpack的hash策略
MiniCssExtractPlugin 对于浏览器来说,一方面期望每次请求页面资源时,获得的都是最新的资源;一方面期望在资源没有发生变化时,能够复用缓存对象。这个时候,使用文件名+文件哈希值的方式,就可以实现只要通过文件名,就可以区分资源是否有更新。而webpack就内置了hash计算方法,对生成文件的可以在输出文件中添加hash字段。
vite原理
Vite 主要由两个部分组成
开发环境
Vite 利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用(就相当于把我们在开发的文件转换成 ESM 格式直接发送给浏览器)
当浏览器解析 import HelloWorld from ‘./components/HelloWorld.vue’ 时,会向当前域名发送一个请求获取对应的资源(ESM支持解析相对路径),浏览器直接下载对应的文件然后解析成模块记录(打开 network 面板可以看到响应数据都是 ESM 类型的 js)。然后实例化为模块分配内存,按照导入导出语句建立模块和内存的映射关系。最后运行代码。
vite 会启动一个 koa 服务器拦截浏览器对 ESM 的请求,通过请求路径找到目录下对应的文件并处理成 ESM 格式返回给客户端。
vite的热加载是在客户端和服务端之间建立了 websocket 连接,代码修改后服务端发送消息通知客户端去请求修改模块的代码,完成热更新,就是改了哪个 view 文件就重新请求那个文件,这样保证了热更新速度不受项目大小影响。
开发环境会使用 esbuild 对依赖进行个预构建缓存,第一次启动会慢一点,后面的启动会直接读取缓存
生产环境
使用 rollup 来构建代码,提供指令可以用来优化构建过程。缺点就是开发环境和生产环境可能不一致;
webpack 和 vite 对比
Webpack 的热更新原理简单来说就是,一旦发生某个依赖(比如 a.js )改变,就将这个依赖所处的 module 的更新,并将新的 module 发送给浏览器重新执行。每次热更新都会重新生成 bundle。试想如果依赖越来越多,就算只修改一个文件,理论上热更新的速度也会越来越慢
Vite 利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用,热更新是在客户端和服务端之间建立了 websocket 连接,代码修改后服务端发送消息通知客户端去请求修改模块的代码,完成热更新,就是改了哪个文件就重新请求那个文件,这样保证了热更新速度不受项目大小影响。
所以vite目前的最大亮点在于开发体验上,服务启动快、热更新快,明显地优化了开发者体验,生产环境因为底层是 rollup ,rollup更适合小的代码库,从扩展和功能上都是不如 webpack 的,可以使用vite作为一个开发服务器dev server使用,生产打包用webpack这样的模式。
做过哪些 wewbpack 的优化
0、升级 webpack 版本,3升4,实测是提升了几十秒的打包速度
1、splitChunksPlugin 抽离共用模块输出单独的chunks ,像一些第三方的依赖库,可以单独拆分出来,避免单个chunks过大。
2、DllPlugin 作用同上,这个依赖库相当于从业务代码中剥离出来,只有依赖库自身版本变化才会重新打包,提升打包速度
3、loaders的运行是同步的,同各模块会执行全部的loaders
可以使用oneOf,只要匹配上对应的loader就不会继续执行loader
使用 happyPack 将loader的同步执行转换成并行比如(style-loader,css-loader,less-loader合并起来执行)
4、exclude/include 指定匹配范围;alias指定路径别名;
5、cache-loader持久化存储;
6、ESM项目开启 useExport标记,使用 tree-shaking
7、exclude/include 指定匹配范围;alias指定路径别名;cache-loader持久化存储;
8、terserPlugin 可以提供代码压缩,去除注释、去除空格的功能;MiniCssExtractPlugin 压缩 css
npm run 执行过程
0、在 package.json 文件中可以定义 script 配置项,里面可以定义运行脚本的键和值
1、在 npm install 的时候,npm 会读取配置将执行脚本软链接到node_modules/.bin目录下,同时将./bin加入当环境变量$PATH中,所以如果在全局直接运行该命令会去全局目录里找,可能会找不到该命令就会报错。比如 npm run start,他是执行的webpack-dev-server带上参数
2、还有一种情况,就是单纯的执行脚本命令,比如 npm run build,实际运行的是 node build.js,即使用 node 执行 build.js 这个文件
好的,以上就是我对三年前端经验通过面试题做的一个总结了,祝大家早日找到心仪的工作~
以上是关于转载2022凛冬之时三年经验前端面经的主要内容,如果未能解决你的问题,请参考以下文章