2022前端面试题汇总

您所在的位置:网站首页 vue项目优化面试题 2022前端面试题汇总

2022前端面试题汇总

2023-08-11 02:01| 来源: 网络整理| 查看: 265

最近面试一个月,记录一下遇到的面试题 后续还会做一些补充更改

语义化 页面整体结构 代码结构清晰,易于阅读 利于开发和维护 有利于搜索引擎优化(SEO) 复制代码 无障碍开发(盲人) 语义化的 HTML label for属性 通过键盘完成所有的功能 复制代码 SEO Title:掘金 - 代码不止,掘金不停 Keywords:掘金,稀土,Vue.js,前端面试题 description: 描述内容 复制代码 url输入之后 1、查找缓存 2、DNS解析 3、建立TCP连接 4、HTTP请求 5、服务器响应请求并返回结果 6、关闭TCP连接 7、浏览器渲染 8、构建DOM树 9、构建CSS规则树 10、合并生成render树 11、布局-绘制 强制缓存失效-携带缓存标识向服务器发起请求-返回304,协商缓存生效-协商缓存失效,返回200和请求结果 复制代码 回流和重绘 重绘:元素外观改变,改变布局 重排/回流:重新计算元素,重新生成布局 复制代码 避免回流或重构 定位\集中改变样式,不要一条一条地修改 DOM 的样式 复制代码 css Flex 布局 容器的属性: ​ flex-direction:决定主轴的方向 flex-direction: row|row-reverse|column|column-reverse; flex-wrap:决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse; flex-flow: .box { flex-flow: || ; } justify-content:对其方式,水平主轴对齐方式 align-items:对齐方式,竖直轴线方向 align-content ​ 项目的属性(元素的属性): ​ order 属性:定义项目的排列顺序,顺序越小,排列越靠前,默认为 0 flex-grow 属性:定义项目的放大比例,即使存在空间,也不会放大 flex-shrink 属性:定义了项目的缩小比例,当空间不足的情况下会等比例的缩小 flex-basis 属性:定义了在分配多余的空间,项目占据的空间。 flex:是 flex-grow 和 flex-shrink、flex-basis 的简写,默认值为 0 1 auto。 align-self:允许单个项目与其他项目不一样的对齐方式,可以覆盖 align-items,默认属 性为 auto,表示继承父元素的 align-items 复制代码 元素水平垂直居中 .parent {   position: relative; } .child {   position: absolute;   top: 0;   right: 0;   bottom: 0;   left: 0;   margin: auto; } 复制代码 .parent {   display: flex;   /* 定义项目在主轴上如何对齐 */   justify-content: center;   /* 定义项目在交叉轴上如何对齐 */   align-items: center; } 复制代码 .parent {   position: relative; } ​ .child {   position: absolute;   top: 50%;   left: 50%;   transform: translate(-50%, -50%); } 复制代码 文本溢出 .ellipsis{ overflow: hidden;   text-overflow:ellipsis;     white-space: nowrap; display: inline-block; } .ellipsis{ overflow: hidden;   display: -webkit-box; text-overflow:ellipsis;   -webkit-line-clamp:2; -webkit-box-orient: vertical } ​ 设置相对定位的容器高度,用包含省略号(…)的 元素/伪元素 模拟实现 一些开源的js库 复制代码 js es6新增语法 let 和 const、解构赋值、展开运算符、模板字⾯量、箭头函数、Promises、Generators、Iterator、 for ... in、for ... of、Map 和 Set、Proxy、Class、es module 复制代码 JS中的数据类型 基本类型(值类型):Number、String、Boolean、Symbol,null,undefined   栈内存储 引用类型(复杂数据类型):Object、Function、Array         堆内存储 复制代码 JS中的数据类型检测方案 typeof instanceof Object.prototype.toString.call() 复制代码 作用域和作用域链 简单来说作用域就是变量与函数的可访问范围 ​ 1.全局作用域:代码在程序的任何地方都能被访问,window 2.函数作用域:在固定的代码片段才能被访问,function内 3.新增块级作用域,大括号内{} ​ 一般情况下查找变量会在当前作用域先找。但是如果在当前作用域中没有查到,就会向上级作用域去查 直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。 复制代码 闭包 闭包是指能访问另一个函数作用域中的变量的函数, 正常就是函数嵌套函数延长外部函数的作用域 优点:模仿块级作用域、封装私有化变量、创建模块(IIFE) 缺点:一直保存在内存中,过多的闭包可能会导致内存泄漏 复制代码 new运算符的实现机制 1.创建了一个对象 2.this指向这个对象 3.执行构造函数的代码 4.返回这个对象 注意的是要 判断函数定义的返回值类型(函数默认返回undefined),如果是基本数据类型,返回创建的对象。 如果是引用类型,就返回这个引用类型的对象。 复制代码 原型 && 原型链 构造函数   通过new得到实例对象,内部prototype指向原型对象 实例对象   __proto__指向原型对象 原型对象   constructor指向构造函数 ​ 在对象上查找一个属性或者方法时,如果找不到就会去原型对象找,再找不到再去原型对象的原型对象, 最后找到object的原型对象为null时就表示没有 ​ JavaScript对象是通过引用来传递的。当我们修改原型时,与之相关的对象也会改变 复制代码 EventLoop js: 先会执行栈中的内容 - 栈中的内容执行后执行微任务 - 微任务清空后再取出一个宏任务压入执行栈执行 - 再去执行微任务 - 然后在取宏任务清微任务这样不停的循环。 复制代码 宏任务:ajax、定时器、一些浏览器api、script... 微任务:promise.then、mutationObersve 复制代码 防抖节流 防抖:高频触发事件n 秒内函数只会执行一次, 思路:每次触发前都取消之前的延时调用方法 节流:高频事件触发, n 秒内只会执行一次, 思路:每次触发事件时都判断当前是否有等待执行的延时函数, 有等待执行的延时函数就直接return 复制代码 vue 组件中的data为什么是一个函数 在Vue中组件是可以复用的,如果data是对象,当组件被多次引入后,由于对象属于引用类型, 一个组件内部的data中的属性发生改变就会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突, data必须是一个函数 复制代码 key 的作用

key的作用是为了在diff运算时更快的找到相同的节点让其复用,避免大量不必要的dom操作,影响性能。

// 这里是Vue中对比两个节点是否是相同节点的代码 function sameVnode (a, b) {  return (    a.key === b.key && (     (        a.tag === b.tag &&        a.isComment === b.isComment &&        isDef(a.data) === isDef(b.data) &&        sameInputType(a, b)     ) || (        isTrue(a.isAsyncPlaceholder) &&        a.asyncFactory === b.asyncFactory &&        isUndef(b.asyncFactory.error)     )   ) ) } 复制代码

举个🌰,在一个列表根据不同部分条件排序时,页面结构如下,当我下次的排序条件使当前列表发生翻转,而反转后内容不会发生变化,在上述情况下我们分析当写key和不写key两种情况的dom对比。首先如果不写key,Vue内部会默认根据索引来生成一个key。

标题:{{item.title}} 创建时间:{{item.createTime}} 创建人:{{item.createUser}} 内容:{{item.content}} export default { data() { return { arr: [ { id: 111, title:'111111-title', createTime:'111111-createTime', createUser:'111111-createUser', content:'111111-content', }, { id: 222, title:'222222-title', createTime:'222222-createTime', createUser:'222222-createUser', content:'222222-content', }, { id: 333, title:'333333-title', createTime:'333333-createTime', createUser:'333333-createUser', content:'333333-content', }, ] }; }, } 复制代码 不写key:当不写key时,Vue内部会默认根据索引来生成一个key。内部的结果就是 //旧dom数据 //新dom数据 { { key:1, key:1, id: 111, id: 333, ... ... }, }, { { key:2, key:2, id: 222, id: 222, ... ... }, }, { { key:3, key:3, id: 333, id: 111, ... ... }, }, 在上述情况下,当数据发生翻转后,Vnode的key值还是不发生变化的,因为都是根据索引生成的, 所以在做虚拟dom对比的时候会判断id为111的旧dom与id为333的dom为同一个节点, 而进入内部字节点的时候发现是不同的dom,就会做dom的删除、创建、新增 复制代码 写key时 //旧dom数据 //新dom数据 { { key:111, key:333, id: 111, id: 333, ... ... }, }, { { key:222, key:222, id: 222, id: 222, ... ... }, }, { { key:333, key:111, id: 333, id: 111, ... ... }, }, 在写key情况下,当数据发生翻转后,Vnode的key值是不变的,所以在做虚拟dom对比的时候会判断 id为111的旧dom与id为333的dom 不是 同一个节点,而同一个节点只是位置发生了改变,只需要做dom的移动, 而不是删除、创建、添加,这样就提高了渲染的性能 复制代码 vue组件的通信方式 1、props 2、$on、$emit 3、$parent、$children、$refs 4、Event Bus ==> Vue.prototype.$bus = new Vue() 5、$attrs、$listeners 6、Provide、inject 7、vuex 复制代码 双向数据绑定 Vue 使用 Object.defineProperty 遍历和递归对data中的所有属性做数据劫持,把这些 属性 全部转为 getter/setter,当获取的时候会触发getter,设置的时候会触发setter 复制代码 发布者-订阅者模式 监听(Observer): 遍历和递归对data中的所有属性做数据劫持,当获取的时候会触发get,设置的时候会触发set 模版编译(Compile): 解析模板指令,将模板中变量替换成数据,然后渲染初始化的页面视图。并且给所有的 变量 添加监听数据的 订阅者,一旦数据有变动,收到通知,更新试图 依赖收集(Dep): 收集者,每个变量会有一个收集者,收集变量所对应的所有的订阅者, 当数据发生变化的时候通知订阅者更新 订阅者(Watcher):数据监听 和 模版编译之间通信的桥梁,当收到通知更新时更新页面 复制代码 vue1.0的订阅发布模式 1.遍历和递归对data中的所有属性做数据劫持,添加get和set,并且同时会给每个属性创建一个对应的dep 2.模版编译,将模版中所有的变量替换成数据,同时会给每个变量创建一个订阅者(Watcher),⚠️并且会将当前的 Watcher添加到当前变量所对应的dep中。添加到dep的操作就是 订阅。 具体实现方式是触发一次当前属性的get,把当前的Watcher传递过去。(这里dep有可能会对应多个Watcher) 3.往后当数据发生改变的时候就会触发每个dep的notice(),然后就是遍历dep中保存的Watcher,去执行更新, 这里就是发布 复制代码 Vue2.0的订阅发布模式 在vue1.0中会给每个动态的属性添加一个Watcher,当项目体量较大时这无疑是一笔很大的开销,因为会占据很多 内存,同时,在频繁的操作dom也是对性能有了很大的消耗。所以在vue2.0引入了虚拟dom ​ 1.遍历和递归对data中的所有属性做数据劫持,添加get和set,并且同时会给每个属性创建一个对应的dep 2.vue2.0不会给每个属性添加一个Watcher,而是给一个组件添加一个Watcher 3.当数据改变,触发dep的notice(),遍历dep中保存的Watcher,去执行更新时,不会做大量的dom操作, 而是执行虚拟dom对比,完成更新。 复制代码 Vue.set

由于 Vue 会在初始化实例时对 属性 执行 getter/setter 转化,所以 属性 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

弊端就是监听不到新增的属性,以及无法检测数组通过索引下标和length的修改,Vue.set就是让这些新的属性也变成响应式的数据

查看源码会发现Vue.set函数内部执行了defineReactive方法,而defineReactive方法就是Vue使用 Object.defineProperty对属性执行 getter/setter 转化的方法。

/*Vue 源码中set方法的实现(精简版) */ export function set (target, key, val) {  const ob = (target: any).__ob__  defineReactive(ob.value, key, val)  ob.dep.notify()  return val } 复制代码 Vue.nextTick

在下次 DOM 更新循环结束之后执行延迟回调。

因为Vue 在更新 DOM 时是异步执行的,当我们设置一个属性变更时,组件不会立即渲染,所以我们在同步代码中获取不到变更后的渲染结果。

以下是属性变更,通知Watcher的DOM更新函数,可以看出,内部也是用了nextTick方法

/*Watcher内部的更新函数 update() */ update () {  if (this.lazy) {    this.dirty = true } else if (this.sync) {    this.run() } else {    // 这里是监听数据变化通知视图更新的方法,会用异步的方式去做页面更新    queueWatcher(this) } } ​ export function queueWatcher (watcher: Watcher) {   nextTick(flushSchedulerQueue) } 复制代码

而nextTick函数内部是在用微任务去执行所有的更新函数,而我们的Vue.nextTick传入的回掉函数也会被加入到以下的callbacks数组中,也就变成了异步执行函数

// 保存当前的所有回掉函数 const callbacks = [] ​ export function nextTick (cb, ctx) {  callbacks.push(() => {    // 将当前的回掉函数保存    cb.call(ctx) })  // 执行函数  timerFunc() } ​ let timerFunc = () => {  // 用微任务去执行保存的所有回掉函数  Promise.resolve().then(flushCallbacks) } ​ function flushCallbacks () {  // 遍历执行所有的回掉函数  const copies = callbacks.slice(0)  callbacks.length = 0  for (let i = 0; i < copies.length; i++) {    copies[i]() } } 复制代码

注意⚠️:Vue内部也做了兼容降级处理,依次是

Promise - MutationObserver - setImmediate - setTimeout0秒

computed与watch

计算属性依赖于多个属性,有缓存。只有当依赖的属性发生变化时才会更新。监听器用于观察单个属性的变化,会立即执行。

计算属性:当模版内的表达式过于复杂时,会让模版过重且难以维护 例如: {{ message.split('').reverse().join('') }}所以,对于复杂逻辑,应该使用计算属性

computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } } 复制代码

我们提供的函数将作为声明的计算属性的getter 函数,由于Vue知道计算属性是依赖于那些data里的属性的,因此Vue内部做了缓存,只有计算属性依赖的属性发生改变时,计算属性的getter 函数才会更新

注意⚠️:计算属性使用箭头函数时,箭头函数绑定了父级作用域的上下文, this 不会指向Vue 实例,但是会默认传入到第一个参数vm => vm.a * 2,或者用function声明函数

监听器:观察一个属性/表达式/回掉结果,

注意⚠️:不应该使用箭头函数来定义 watcher 函数 (例如 search: newVal => this.update(newVal))。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会指向 Vue 实例

/*Watcher内部的更新函数 update() */ update () {  if (this.lazy) {    //这里是页面中的computed选项,只有所依赖的属性发生变化时才会惰性的做更新    this.dirty = true } else if (this.sync) {    //这里是页面中的watch选项,表示当监听到数据变化的时候会同步执行传入的回掉函数    this.run() } else {    // 这里是监听数据变化通知视图更新的方法,会用异步的方式去做页面更新    queueWatcher(this) } } 复制代码

computed与watch以及dom更新在Vue中都是new 了一个Watcher,只是参数有所不同而已,在Watcher中的update更新函数中做了区分

虚拟dom

虚拟dom本质上就是一个普通的JS对象,来表示dom节点的内容

在vue中,渲染视图的时候会调用render函数,这个渲染 在组件创建时,和视图依赖的数据更新时。如果在渲染的时候,直接使用真实DOM操作的创建、更新、插入等操作会带来大量的性能损耗,这样就会极大的降低渲染效率。

因此,vue在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题。

vue中的虚拟dom是基于Snabbdom的虚拟DOM修补算法

vue3和vue2的区别 生命周期、Composition API、Teleport、响应式原理、虚拟DOM静态节点、更好的Tree-shaking 复制代码 webpack vue有哪些性能优化 对于第三方js库CDN 图片资源的压缩,icon资源使用雪碧图 开启gizp压缩 生产环境关闭SourceMap mini-css-extract-plugin-提取CSS到单独的文件,压缩CSS vue-router使用懒加载 合理使用watch和computed v-for必须添加key 销毁定时器 keep-alive Object.freeze ​ 压缩代码 CDN加速 Tree Shaking 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,可以⻓期缓存这些⽆需频繁变动的公共代码 复制代码 webpack优化 include、exclude配置项来缩⼩loader的处理范围 多配置别名 happypack=>thread-loader(多进程loader) cache-loader\babel-loader开启缓存 hard-source-webpack-plugin缓存中间步骤 ​ webpack5 复制代码 treeShaking机制的原理 利用es6模块的规范 ES6 Module引入进行静态分析,编译的时候判断到底加载了那些模块 判断那些模块和变量未被使用或者引用,然后删除对应代码 复制代码 Loader和Plugin webpack自身只支持js和json这两种格式的文件,对于其他文件需要通过loader将其转换。 loader,它是一个转换器,将A文件进行编译成B文件,单纯的文件转换 plugin是工具不操作文件,在webpack打包过程中,执行一些任务(打包优化、文件管理、环境注入) 复制代码 常见的plugin ProvidePlugin:自动加载模块,代替require和import html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件 clean-wenpack-plugin 清理每次打包下没有使用的文件 DefinePlugin 编译时配置全局变量 HotModuleReplacementPlugin 热更新 optimize-css-assets-webpack-plugin 不同组件中重复的css可以快速去重 compression-webpack-plugin 生产环境可采用gzip压缩JS和CSS webpack-bundle-analyzer:可视化Webpack输出文件的体积 复制代码 常⻅的Loader file-loader:把⽂件输出到⼀个⽂件夹中 url-loader:和 file-loader 类似,base64 的⽅式 source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试 babel-loader:把 ES6 转换成 ES5 css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性 style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。 eslint-loader:通过 ESLint 检查 JavaScript 代码 thread-loader(多进程loader) 复制代码 webpack 构建流程 1、初始化参数,合并传入的和webpack.config.js文件中的配置参数 2、注册/监听 插件,监听构建的生命周期 3、拿到配置从入口处开始构建这个AST语法树,它会根据依赖一直递归下去 4、webpack默认只能编译js和json,遇到其他文件类型会根据配置的loader进行转换 5、得到文件的结果和依赖关系后生成代码块chunk 6、输出到dist目录 复制代码 webpack 热跟新 1、首先启动dev-server,webpack开始构建时会向 入口 文件注入热更新代码 2、浏览器打开的时候,浏览器与本地服务会基于Socket建立通讯 3、本地服务会监听文件的改动,然后会再次编译 4、编译完成后通过socket 发送消息告诉浏览器,(通过维护hash值和state状态); 5、浏览器再去请求新的模块替换掉之前旧的模块 复制代码 webpack 的hash 1、hash: 每次webpack编译中生成的hash值 2、chunkhash: chunkhash基于入口文件及其关联的chunk形成,文件的改动只会影响与它有关联的chunk的 hash值,不会影响其他文件 3、contenthash: contenthash根据文件内容创建。当文件内容发生变化时,contenthash发生变化 复制代码 网络 请求跨域问题 跨域是由浏览器的同源策略造成,(协议、域名、端口)有一个不同就视为跨域 ​ JSONP CORS 服务器设置Access-Control-Allow-Origin 响应头,允许跨域请求 proxy 代理 目前常用方式,通过服务器设置代理 复制代码 http2 http1: 每次请求都会建立一次HTTP连接-3次握手4次挥手(每个TCP连接只能发送一个请求) http1.1: TCP 连接默认不关闭,可以被多个请求复用,但是同一个TCP连接里面,所有的数据通信是按次序进行。 服务器只有处理完一个回应,才会进行下一个回应,前面慢的时候,后面的就需要长时间等待 http2:http2的传输是基于二进制帧的。每一个TCP连接中承载了多个双向流通的流。同一域名只需占用一个 TCP 连接,就是可以并行的发出多个请求,不会堵塞 复制代码 http状态码3开头 301 永久性重定向, 可促进搜索引擎优化效果。 302 临时性重定向, 影响搜索引擎优化效果。 304 协商缓存生效。 复制代码 浏览器的缓存机制 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识,查找到就是强缓存生效 ​ 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,当协商缓存生效,返回304, 当协商缓存失效,返回200和请求结果结果 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3