面试复盘系列第二篇。最近一直感觉良好,迷之自信...
概况
- 公司:坐标上海,游戏、社交类,前端团队三四个人「猜测数据 面试官原话:前端小而美」。
- 面试官:一面 前端开发,二面 技术负责人。
- 面试结果:未通过(不想去)。
- 面试感受:没什么感受 o(╥﹏╥)o ,二面提出的几个问题得到的答复都好绕「😵头晕,我是不是问错了」。
面试题(一面)
- 为什么转行?8
- 重绘和回流?9 ✔
- 3D动画?5
- vue-router 路由守卫?全局、单个组件。8 ✔
- 闭包?7 ✔
- JS线程?宏任务、微任务。8 ✔
- v-for和v-if哪个优先级高?6 ✔
- 兄弟组件间通信,使用$emit最简单的方法?5 ✔
- 有没有自己实现一个简单的 vue? 6 ✔
- 是否有关注 vue 的最新动态?8
问题2 重绘和回流
回流(重排)
layout布局 > layer分层 > Paint(sheet) >> tiles >> raster >> draw quad >> display
更新元素的几何属性,如改变宽高、位置等,浏览器会触发重新计算布局,排列元素。
- 改变元素位置、尺寸、字体大小
- 改变浏览器窗口
- DOM操作,DOM元素的添加、删除,DOM元素属性的获取和修改
大量、频繁的重绘,会对性能造成影响。应对措施如下:
- 多个节点的添加,使用fragment包裹一次性添加
- 多个样式的切换,使用切换class类名的方式
- 避免频繁读取会引起重排的属性和方法,主要是宽高、位置等布局类的,如offsetWidth、offsetHeight、getComputedStyle()等
- 将可能重绘的元素,使用absolute或fixed脱离文档流,较少重排影响的范围
- 优化动画,canvas、CSS3 transform3D、WebGL,启用GPU硬件加速。
关于DOM操作这块,浏览器自身也做了很多优化,比如将多次回流重绘合到一起。
重绘
元素的外观发生变化,但是没有改变布局。如color、border-style、visibility、background等。
直接进入重新绘制阶段,执行效率比重排高。
Paint(sheet) >> tiles >> raster >> draw quad >> display
问题4 路由守卫
全局守卫
- 全局前置守卫。router.beforeEach
- 全局解析守卫。router.beforeResolve。调用时机(不同于beforeEach):导航被确认之前,所有组件内守卫和异步路由组件被解析之后。
- 全局后置守卫。router.afterEach。
路由守卫(独享)
在路由配置文件定义beforeEnter。
组件内守卫
- beforeRouteEnter。渲染该组件的对应路由被confirm前调用,不能获取组件示例this(还没创建)。
- beforeRouteUpdate。调用时机:foo/1与foo/2之间跳转时,复用当前组件实例,
- beforeRouteLeave。
import VueRouter from 'vue-router';
const router = new VueRouter({
route: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {}
}
]
});
router.beforeEach((to, from, next) => {});
router.beforeResolve((to, from, next) => {});
router.afterEach((to, from) => {});
new Vue({ router, render:h=>h(APP) }).$mount('#app');
// 组件内
const Foo = {
template: '...',
beforeRouteEnter(to, from, next) {
next(vm => {}); // 可以传入回调,导航被确认时执行回调,通过vm访问组件示例
},
beforeRouteUpdate(to, from, next) {}, // next不支持传入回调
beforeRouteLeave(to, from, next) {}
}
问题5 闭包
一个带了环境的函数。相比纯函数,多引用一些外部变量。
通常函数的作用域及其所有变量都会在函数执行结束后被销毁;而闭包,可以访问上级作用域,即使外层函数执行完,外层函数的作用域中能被闭包访问的,会一直保存在内存中,直到闭包不存在。
- ES6 之前,利用立即执行函数(创建闭包后立即执行销毁)模仿块级作用域。
- 节流、防抖、函数柯里化等,都用到了闭包。
问题6 JS线程
JS是单线程的,JS引擎内部会维护一个任务队列。
- 接受到任务后,首先判断是同步还是异步任务。
- 如果是同步任务,压入执行栈立即执行,执行完毕后出栈。
- 如果是异步任务,交给定时器触发线程或HTTP请求线程,在特定时机,由事件触发线程将对应的回调函数的任务加入到队列中。
- 当同步任务执行完毕,执行栈清空,会从任务队列头部读取、执行任务。
事件触发线程:用来控制浏览器事件循环,注意这不归 JavaScript 引擎线程管,当事件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。
主线程从任务队列读取事件,是循环不断的。
优先执行主线程上的任务,然后执行微任务,最后执行宏任务。
问题7 v-for、v-if优先级
v-for的优先级更高。也就是v-if将在每个循环项中重复执行。
vue风格指南以及eslint都告诉我们不要把v-if和v-for同时用在同一个元素上。原因:在每个循环项重复执行v-if的判断,对性能有一定的影响。
两种常见的场景,第一是需要使用循环项或其属性判断是否渲染,这个可以将循环列表替换为一个计算属性。第二是需要使用另一个变量作为渲染条件,这个v-if可以移动到容器元素,或者使用template元素。
HTML5的内容模板template标签元素,一个天然display为none的元素(设置display:block就会显示)。加载页面时不会显示,运行时可以进行JS操作。template元素的content属性,是只读的DocumentFragment。
问题8 兄弟组件使用$emit
vm.$root,当前组件的根Vue实例。
vm.$on(event, callback),监听当前实例上的自定义事件。
vm.$emit(event, data),触发当前实例上的自定义事件。
问题9:vue 简单实现
详见 理解&实现(一):Vue2 响应式原理,理解&实现(二):Vue3 响应式原理。
面试题(二面)
- 为什么转行?
- 目前在公司负责什么项目?在团队中扮演什么角色?
- 是否有做过 h5 小游戏?
- 你最看重团队的哪些方面?
- 你觉当刚才面试你人的水平如何?是否能对你的技术成长有帮助?
- 是否能接受加班?「我们不996,但是项目排期紧的时候,需要你每天都到公司。不忙的时候可以申请调休,但不能连续休好几天 /(ㄒoㄒ)/」
- 什么时候能入职?