前端面试总结(css,js,vue)

247 阅读14分钟

html,css,js渲染过程

  • 解析html文件,生成dom树
  • 解析css,生成CSSOM,css对象模型
  • Dom和CSSOM结合,生成rander树
  • 生成布局flow,将渲染树的所有节点进行平面合成
  • 将布局绘制paint在屏幕上 若有引入外部css文件,会异步加载css文件,但是若有js文件,则会等待js文件引入完成,会阻塞渲染。所以会将js文件放在最后引入。

css3新特性

  • 过渡:transition
  • 动画:animation
  • 形状转换:transform:
  • 阴影:box-shadow
  • 边框:border-image
  • 渐变
  • 弹性布局:flex
  • 盒模型定义
  • 媒体查询

css盒模型

所有html元素都可以看作盒子,css盒模型本质上是一个盒子,封装周围的html元素,它包括:边距,边框,填充,和实际内容。 默认content-box,盒子宽高不包含padding和border。 border-box,怪异盒模型,盒子宽高包含padding和border。

div上下左右居中布局

  • 弹性布局
  • display:table布局
  • 定位+上下左右为0,margin:auto
  • 定位+transform:translate(-50%, -50%),left、top为50%。

左侧固定,右侧自适应布局

  • float加margin-left
  • calc() 动态计算
  • table-cell表格布局
  • flex布局  flex-grow:1
  • grad布局  left:grad-column:1,right:grad-column:2

屏幕适配

  • Meta viewport标签
  • 动态rem,如flexable.js
  • vw,vh布局
  • flex布局
  • 跨平台使用媒介查询
  • 根据窗口宽高比 动态transfrom scale(大屏适配)

前端性能优化

  • 浏览器缓存
  • 资源懒加载、按需加载,路由懒加载,预加载
  • 减少网络请求
  • 优化代码
  • 防抖、节流
  • 压缩代码,压缩静态资源

浅拷贝和深拷贝及实现深拷贝的方法

拷贝针对的是引用类型的数据,当将变量a赋值给变量b,当改变其中一个变量的值,会影响另一个变量的值,这就是浅拷贝。因为赋值只是将a变量的存储的数据空间地址赋值给了b,a和b共享一个空间,所以会相互影响。

实现深拷贝的方法:

  1. JSON
JSON.parse(JSON.stringify(obj))

缺点:无法拷贝对象里有函数、无法拷贝对象原型链上的属性和方法、当数据的层次很深,会栈溢出。 2. 递归遍历:

function deepCopy(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj === 'object') {
        for (let key in obj) {
            if (obj[key] && typeof obj[key] === 'object') {
                objClone[key] = deepCopy(obj[key]);
            } else {
                objClone[key] = obj[key];
            }
        }
    }
    return objClone;
}

Let、const和var的不同,const只能声明常量吗?

  • Let跟const声明变量会形成块级作用域,不会存在变量提升,存在暂时性死区,var会存在变量提升,会挂在到window上。
  • Let和const同一作用域下不能重复声明,而var可重复声明。
  • const声明必须赋值,且声明后不能修改,但是如果值是引用类型,可以修改其属性。原因是引用类型的值保存的只是一个指向实际数据的指针,它不能控制其数据结构。

js原型、原型链

  • 每个对象都有__proto__属性和constructor属性,函数除了这两个属性,还会多出来一个prototype属性
  • 其中__proto__的作用是,在查找对象的属性时,会在对象内部查找,如果没有找到,就会去__proto__指向的对象里查找,找不到就一直找下去,直到找到最顶层null,返回undefined,这一查找的过程,就是原型链。
  • Prototype的作用是为了给构造函数的实例对象们添加一些公用的属性和方法。
  • Constructor主要用来指向实例对象的本身。

js实现继承有哪些方式

  1. 原型链继承 让新实例的原型等于父类的实例
  2. 构造函数继承 用.call()和.apply()将父类构造函数引入子类函数
  3. 组合继承(组合原型链继承和借用构造函数继承)
  4. ES6中Class继承  通过extends关键字继承了父类所有的属性和方法
  5. 原型式继承  利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
  6. 寄生式继承 在原型式继承的基础上,增强对象,返回构造函数
  7. 寄生组合式继承  结合借用构造函数传递参数和寄生模式实现继承

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

  • 闭包优点:

    1. 能够访问函数定义时所在的词法作用域(阻止其被回收)
    2. 私有化变量
    3. 模拟块级作用域
    4. 创建模块
  • 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会在IE导致内存泄漏

事件委托

利用事件冒泡的机制,把对子节点的监听事件绑定到父节点上,由父节点统一处理多个子节点的事件,叫做事件委托。

  • 阻止冒泡:event.stopPropagation()
  • 阻止默认事件  event.preventDefault() 或 return false

防抖和节流

  • 防抖:用于表单验证、窗口大小resize等。一段时间内只触发一次,如果在这段时间内又触发此事件,则清除延时器,重新计时,即只触发最后一次。如:公交车司机只能等所有的乘客上完车后,才能触发关门这个动作。
function debounce (func, wait){
    let timer = null;
    return () => {
        clearTimeout(timer);
        timer = setTimeout(func, wait);
    }
}
  • 节流:用于多次频繁点击,发起请求。通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而实现函数一段时间内只执行一次。
const throttle = (func, wait) => {
    let timer = null;
    return () => {
        if (timer) {
            return;
        }
        timer = setTimeout(() => {
            func();
            timer = null;
        }, wait);
    }
}

ES6新特性

  • const 与 let 变量,因为是块级作用域,不会变量提升。
  • 模板字符串
  • 数组和对象的结构赋值
  • 对象字面量简写法
  • 扩展运算符
  • Map和Set(数组去重)
  • 剩余参数(参数不确定时,用…表示)
  • 箭头函数
  • 函数参数默认值
  • 导入import导出export
  • Promise 处理异步请求,避免回调地狱
  • Async、await 更好的解决异步请求,是Generator的语法糖形式,替代*和yield
  • Symbol 新的数据类型,独一无二的值
  • Proxy 代理对象
  • Class 类

箭头函数与普通函数,以及使用场景

不同之处:

  • this指向不同
  • 不能作为构造函数,不能使用new(因为箭头函数this指向无法改变)
  • 箭头函数不能当做Generator函数,不能使用yield关键字
  • 箭头函数没有原型属性
  • 箭头函数不绑定arguments,取而代之用rest参数...解决 使用场景:
  • 匿名函数
  • 回调函数

new关键字作用

  1. 创建一个新对象
  2. 修改对象的原型指向函数的prototype对象
  3. 修改this指向,调用构造函数代码
  4. 返回这个新对象。

js事件循环

JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。

  • 微任务队列 Promise.then
  • 宏任务队列 setTimeout、setInterval

实现拖拽功能

  1. 利用onmousedown,onmousemove和onmouseup事件
    • 设置绝对定位
    • 按下获取鼠标的坐标,获取盒子离页面横纵方向的距离,计算出鼠标相对盒子的距离
    • 当鼠标移动的时候,获取鼠标移动的距离,在用鼠标此刻的位置减去鼠标相对盒子的距离,获得的是盒子此刻的坐标位置
    • 将这个位置赋值给盒子,鼠标抬起,清除鼠标移动事件
  2. 利用draggable属性
    • 给元素添加draggable属性,为true元素就能拖拽,会触发dragstart,drag和dragend事件。

数据过多,渲染卡顿怎么优化

  • 分页(分页请求或者分页循环渲染)
  • 局部渲染:只渲染可视化区域,只把展示给用户的那部分渲染出来,比如滚到上面的 dom 就回收掉。插件:vue-virtual-scroller
  • 请求时间过长的话 启用缓存

requestAnimationFrame()

requestAnimationFrame()类似计时器,用于渲染动画,一般使用计时器来做动画,因为大多数屏幕渲染的时间间隔是每秒60帧。

优点:

  • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

取消动画:cancelAnimationFrame()

Fetch和ajax的区别

  • ajax是利用XMLHttpRequest对象来请求数据的,而fetch是window的一个方法。
  • ajax基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案。fetch结合promise,比ajax写法更方便。
  • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理。
  • fetch没有办法原生监测请求的进度,而XHR可以。
  • Fetch不支持ie,兼容性不好。
  • Fetch传cookie,需要开启第二个参数  credentials: 'include'

cookie和localStorage以及sessionStorage的区别

cookie主要用来保存登录信息,辨别用户身份。localStorage和sessionStorage类似,用于将数据存入浏览器本地。

区别:

大小不同:Cookie不超过4K,其他两个5M甚至更多

有效期不同:Cookie由服务器生成,可以设置过期时间,若没有设置时间,关闭浏览器cookie失效。sessionStorage关闭页面或浏览器会被清除,localStorage只能手动清除。

作用域不同: cookie在所有同源窗口中都是共享的,sessionStorage在同一个浏览器窗口是共享的(不同浏览器,即使是统一页面也不共享),localStorage在所有同源窗口中共享。

网络请求:cookie会跟随网络请求发送给服务器,二另外两个不会主动发送数据

浏览器从输入url到渲染页面的过程

网络篇:

  1. 构建请求
  2. 查找强缓存
  3. DNS解析
  4. 建立TCP连接(三次握手)
  5. 发送HTTP请求(网络请求后网络响应)

浏览器解析篇:

  1. 解析html构建DOM树
  2. 解析css构建CSS树、样式计算
  3. 生成布局树(Layout Tree) 浏览器渲染篇:
  4. 建立图层树(Layer Tree)
  5. 生成绘制列表
  6. 生成图块并栅格化
  7. 显示器显示内容
  8. 最后断开连接:TCP 四次挥手

http实现缓存

  • 强缓存==>Expires(过期时间)/Cache-Control(no-cache)(优先级高) 协商缓存 ==>Last-Modified/Etag(优先级高)Etag适用于经常改变的小文件 Last-Modefied适用于不怎么经常改变的大文件

  • 强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

前端工程化的理解

目的:为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间(前后端分离,各司其责)

做法:

  • 模块化,js模块化、css模块化
  • 组件化,封装公用组件
  • 规范化:代码格式规范、文件结构规范,接口规范。
  • 自动化:自动构建、自动测试、自动部署等

VUE

New vue之后发生了什么

创建一个vue的实例对象并挂载到指定dom上。

  1. initProxy:作用域代理,拦截组件内访问其它组件的数据。
  2. initLifecycle:建立父子组件关系,在当前组件实例上添加一些属性和生命周期标识。如parent,parent,refs,$children,_isMounted等。
  3. initEvents:对父组件传入的事件添加监听,事件是谁创建谁监听,子组件创建事件子组件监听
  4. initRender:声明slotsslotscreateElement()等。
  5. initInjections:注入数据,初始化inject,一般用于组件更深层次之间的通信。
  6. initState:数据响应式:初始化状态。很多选项初始化的汇总:data,methods,props,computed和watch
  7. initProvide:提供数据注入。

Vue双向数据绑定原理

Vue2使用Object.defineProperty对data中的属性进行劫持,使用getter和setter方法进行监听,当访问属性的值时,会触发getter方法,当修改数据时,会触发setter方法。

缺点:

  • 无法检测到对象属性的动态添加
  • 不能监听数组下标的变化,如通过下标设置值,不能实时响应

Vue3使用es6的proxy对整个对象做代理,可以不操作对象,通过对代理对象进行操作,达到监听的目的。

缺点:

  • ie11不支持

keep-alive作用及原理

作用: 缓存组件的状态,防止重复渲染dom

生命周期:

  • activated :当页面存在缓存的时候执行该函数。
  • deactivated :在页面结束时触发该方法,可清除掉滚动方法等缓存。 参数:
  • include: 字符串或正则表达式。只有匹配的组件会被缓存。
  • exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。

原理: Vue 的缓存机制并不是直接存储 DOM 结构,而是将 DOM 节点抽象成了一个个 VNode节点。因此,Vue 的 keep-alive 缓存也是基于 VNode节点 而不是直接存储 DOM 节点。

每个keep-alive都会有一个cache,缓存在this.cache里

Vue单向数据流的理解

父组件通过props传值给子组件,父级prop的改变会影响到子组件,但是子组件中props的值如果是引用类型的话,不能修改,因为vue是单向数据流,但是引用类型的可以修改,修改后会影响父级的数据。不建议修改。

v-model的实现原理

主要用于表单,实现双向数据绑定,监听输入事件更新数据,视图更改影响data,data更改影响视图。

原理: v-bind:绑定数据的value,触发输入事件v-on:input($event.target.value)

Vue中$nextTick原理及作用

作用: 数据发生变化后,为了确保dom更新循环结束后的回调,在$nextTick里可获取更新后的dom。进而操作dom。

原理:

vue是异步执行dom更新的,一旦观察到数据变化,vue就会开启一个队列,然后把在同一事件循环当中观察到数据变化的watcher推送进这个队列,如果这个watcher被触发多次,只会被推送到队列一次,这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和dom操作,这样可以提高渲染效率。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;异步回调采用的是promise。

vue-router实现原理、路由懒加载

原理:

两种模式,hash和history两种,更新视图但是不会引起页面刷新,

Hash:url中带有#号,利用锚点,默认模式。

History:利用浏览器HTML5 History API。

懒加载:

  • 异步组件:component:resolve=>{reuqire(地址),resolve)
  • 动态引入:ES6的import()
  • 按需加载:配置webpack, require.ensure()

Vue插槽及实现原理

Vue中写在子组件标签里的内容不会显示,如果要显示,就要使用slot,若单个插槽,子组件中slot写在哪里,内容就显示在哪里,多个插槽的话需要给插槽起上名字,也就是具名插槽,名字对应上,就会显示在对应的位置。

原理:

  • 插槽的作用是实现内容分发,Slot相当于一个占位符,父组件中插槽内容,就是要分发的内容。
  • 父组件模板解析在子组件之前(beforeMounte),所以父组件首先会获取到插槽模板内容,子组件模板解析在后,所以在子组件调用render方法生成VNode时,可以借助部分手段,拿到插槽的VNode节点。

Vue父组件如何监听子组件的生命周期

  • $emit 每次都要手动触发
  • @hook

Vue怎么捕获组件的错误信息

  • errorCaptured 是组件内部钩子,当捕获一个来自子孙组件的错误时被调用,接收 error、 vm、info 三个参数,return false 后可以阻止错误继续向上抛出;
  • errorHandler 为全局钩子,使用 Vue.config.errorHandler 配置,接收参数与 errorCaptured 一 致,可捕捉 v-on 与 promise 链的错误,可用于统一错误处理与错误兜底。

Vue组件渲染及更新的过程

渲染:

  1. 解析模板为 render 函数,
  2. 触发响应式,监听 data 属性的 getter 和 setter
  3. 执行 render 函数, 生成 vnode,patch(elem,vnode)

更新:

  1. 修改 data, 触发 setter (此前在getter中已被监听)
  2. 重新执行 render 函数,生成 newVnode,patch(vnode, newVnode)

Vue中的diff算法

因为操作真实dom会引起大量的回流(修改位置和大小)和重绘(修改颜色),会引起很大的开销,所以采用根据真实dom生成虚拟dom做中间层,当虚拟dom某个节点的数据改变之后,会生成一个新的vnode,新的vnode和旧的作比较,有不一样的地方就直接修改真实dom。

比较过程:

  • diff的过程就是调用patch函数,比较新旧节点,只会在同层级进行比较,首先进行树级别的比较,new Vnode不存在就删除 old Vnode,old Vnode 不存在就增加新的Vnode,都存在就执行diff更新
  • 新老节点均有子节点,则对子节点进行diff操作,调用updatechidren
  • 如果老节点没有子节点而新节点有子节点,先清空老节点的文本内容,然后为其新增子节点,
  • 如果新节点没有子节点,而老节点有子节点的时候,则移除该节点的所有子节点,
  • 新老节点都没有子节点的时候,进行文本的替换。

其他

vue3新特性

插件源码修改

webpack配置

项目中的难点

项目中的重大贡献

说明

此文章只是本人当做个人笔记使用,若有错误,欢迎指出。

参考

2021年我的前端面试准备 - 掘金 (juejin.cn)