前端知识点
- vue知识点
- Vue的两个核心点
- 组件渲染更新的过程
- Vue的MVVM实现流程
- 虚拟 DOM、patch() 方法、diff 算法
- 什么是patch(), **patch的流程**
- 说一下指令v-el的作用是什么?
- nextTick 使用场景和原理
- Vue.set 方法原理
- 为什么有些数组的变更不能被VUE检测到?哪些会?哪些不会?
- vue 是怎么检测数组的变化的
- Vue初始化过程中(new Vue(options))都做了什么?
- 为什么vue采用异步渲染
- vue中data的属性可以和methods中方法同名吗,为什么?
- data 里面数据量比较大如何优化
- Vue中watch用法详解
- $route 和 $router的区别
- vue-router 路由模式有几种
- vue-router 路由钩子函数是什么?
- 路由守卫
- 虚拟DOM实现原理
- v-if和v-for为什么不建议一起使用?
- Vue父子组件的生命周期顺序
- 组件中写 name 选项有哪些好处
- vue 如何快速定位那个组件出现性能问题的
- 性能优化
- 网络请求
- JavaScript
- 浏览器
vue知识点
Vue的两个核心点
数据驱动:ViewModel,保证数据和视图的一致性
组件系统:应用类UI可以看作全部是由组件树构成的
组件渲染更新的过程
初次渲染:
- initState ->进行双向绑定
- $mount->将template编译成render函数
- 执行渲染 触发属性getter函数,将渲染watcher 收集到dep(依赖收集器)中
- 调用render 函数 生成 虚拟节点对象 vnode
- 打补丁patch(elm,vnode)
更新
- 修改数据 触发属性set
- 然后dep.notify() ->watch.update派发更新
- 触发render watcher 的render回调
- 生成新的vnode
- patch(oldVnode,newVnode) ,diff算法更新视图。
Vue的MVVM实现流程
Vue的MVVM实现是通过数据劫持配合发布者-订阅者模式完成的。首先是new Vue实例化一个Vue对象作为入口,new Vue的时候会创建Observer对象和Compile对象。
Observer负责数据劫持,观察数据变化,创建依赖收集器Dep对象。当数据发生变化的时候,通知Dep,然后Dep会通知所有相关的订阅者Watcher调用更新视图的回调函数,更新视图。
Compile负责解析指令和双大括号的插值表达式,将对应的数据渲染到页面相应的地方,同时创建订阅者Watcher,并添加到依赖收集器Dep中,绑定数据变化时,更新视图的回调函数。
以上流程的对应实现都可以在Vue源码中的src/core/observer和src/compiler目录中找到。
虚拟 DOM、patch() 方法、diff 算法
虚拟DOM: 用一个JS对象来描述一个DOM节点
虚拟DOM实现原理:
- 通过js建立节点描述对象
- diff算法比较分析新旧两个虚拟DOM差异
- 将差异patch到真实dom上实现更新
虚拟DOM概述:虚拟 DOM 中最为核心的部分是 patch() 方法,通过该方法,Vue 可以将最新的 vnode 渲染到页面上,实现组件的重新渲染。
patch() 方法:在重新渲染组件的时候,并不会使用暴力覆盖的方法,而是细心的比较新老 vnode 之间的差异,只对有差异的地方进行真实 DOM 的更新操作。这样,就可以极大的减少操作真实 DOM 的次数,提高性能。patch() 方法中使用的算法就是广为人知的 diff 算法。
diff 算法 主要有四块内容 :创建节点、删除节点、更新节点、删除子节点
什么是patch(), patch的流程
组件页面渲染时,将render返回的新vnode(新节点)和组件实例保存的vnode(旧节点)作为参数,调用patch方法,更新DOM
说一下指令v-el的作用是什么?
提供一个在页面上以存在的DOM元素作为Vue实例的挂载目标,可以是CSS选择器,也可以是一个HTMLElement实例
nextTick 使用场景和原理
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
**主要思路:**采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法。
原理:
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
所以为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
使用场景
在你更新完数据后,需要及时操作渲染好的 DOM时
Vue.set 方法原理
在一个组件实例中,只有在data里初始化的数据才是响应的,Vue不能检测到对象属性的添加/删除,没有在data里声明的属性不是响应的,所以数据改变了但是不会在页面渲染;
原理:
- 为对象添加一个新的响应式数据:调用 defineReactive 方法为对象增加响应式数据,然后执行 dep.notify 进行依赖通知,更新视图
- 为数组添加一个新的响应式数据:通过 splice 方法实现
解决数据层级结构太深的问题
在开发业务时,经常会岀现异步获取数据的情况,有时数据层次比较深,如以下代码: <span 'v-text=“a.b.c.d”>,
可以使用vm. s e t 手动定义一层数据 : v m . set手动定义一层数据: vm. set手动定义一层数据:vm.set(“demo”,a.b.c.d)
为什么有些数组的变更不能被VUE检测到?哪些会?哪些不会?
什么样的数组变更不能被检测到呢?
- 通过索引修改数组的值
- 通过修改长度改变数组
- 调用Array.prototype上的方法
会: push() pop() shift() unshift() splice() sort() reverse()
这些方法都可改变原数组,并且能够被检测到,进行页面更新,这7个方法是vue包装之后方法
不会:filter() concat() slice() map()
新数组替换旧数组,不会改变原数组,页面不更新,不会被拦截
解决: Vue.set / this.$set 强制更新
vue 是怎么检测数组的变化的
1、使用函数劫持的方式,重写了数组的方法(push、pop、shift、unshift、sort、reverse、splice)
2、Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新.如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。
Vue初始化过程中(new Vue(options))都做了什么?
- 处理组件配置项;初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上;初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率;
- 初始化组件实例的关系属性,比如 p a r e n t 、 parent、parent、children、r o o t 、 root、root、refs 等
- 处理自定义事件
- 调用 beforeCreate 钩子函数
- 初始化组件的 inject 配置项,得到 ret[key] = val 形式的配置对象,然后对该配置对象进行响应式处理,并代理每个 key 到 vm 实例上
- 数据响应式,处理 props、methods、data、computed、watch 等选项
- 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
- 调用 created 钩子函数
- 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount 方法,反之,没提供 el 选项则必须调用 $mount
- 接下来则进入挂载阶段
为什么vue采用异步渲染
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图。
原理:
- 调用 notify() 方法,通知watcher 进行更新操作
- 依次调用watcher 的 update 方法
- 对watcher 进行去重操作(通过id),放到队列里
- 执行完后异步清空这个队列, nextTick(flushSchedulerQueue) 进行批量更新操作
vue中data的属性可以和methods中方法同名吗,为什么?
可以同名,methods的方法名会被data的属性覆盖;调试台也会出现报错信息,但是不影响执行;
原因:源码定义的initState函数内部执行的顺序:props>methods>data>computed>watch
data 里面数据量比较大如何优化
vue 把 data、props、store 等数据做成响应式,也就是会对这些响应式数据做深度监听,给每一个object类型的key(包括嵌套object)添加observer(vue3使用proxy)。所以如果我们不需要数据是响应式的,可以在.vue 文件头部直接使用 let、const 定义变量,在组件销毁的时候将该这些变量设为null。
Vue中watch用法详解
在vue中,使用watch来监听数据的变化;
1.监听的数据后面可以写成对象形式,包含handler方法,immediate和deep。
2.immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
3.当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
$route 和 $router的区别
router:为VueRouter的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如history对象…经常用的跳转链接就可以用this.$router.push,和router-link跳转一样。
route:相当于当前正在跳转的路由对象。。可以从里面获取name,path,params,query等
vue-router 路由模式有几种
① hash:
使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
带#,如:http://localhost:8080/#/pageA。改变hash,浏览器本身不会有任何请求服务器动作的,但是页面状态和url已经关联起来了
② history :
依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
不带#, 如:http://localhost:8080/ 正常的而路径,并没有#。基于HTML5的 pushState、replaceState实现:(如果后台没有做相应配置,history页面会在再次刷新的时候,报404错误; )
③ abstract :
支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
vue-router 路由钩子函数是什么?
全局的路由钩子函数:beforeEach、afterEach、 beforeResolve (一般用于全局进行权限跳转)
单个的路由钩子函数:beforeEnter、beforeLeave(路由内部钩子,一般在路由表里)
组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate、
- beforeEach:( 全局前置守卫 )每一次路由该变的之后页面加载之前执行,三个参数(to 将要进入的路由对象、from 即将离开的路由对象、next 跳转方法),next 必须调用
- afterEach:( 全局后置守卫 )每一次路由该变的之后页面加载之后执行;两个参数(to 将要进入的路由对象、from 即将离开的路由对象)
- beforeEnter:( 单个路由守卫 )进入指定路由跳转时需要执行的逻辑
- beforeLeave:离开指定路由跳转时需要执行的逻辑
- beforeRouteEnter、beforeRouteLeave、 beforeRouteUpdate都是写在组件里面,也有三个参数(to、from、next)
路由守卫
【1】全局守卫:是指路由实例上直接操作的钩子函数,特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数
- beforeEach(to,from, next)
- beforeResolve(to,from, next)
- afterEach(to,from)
【2】路由守卫: 是指在单个路由配置的时候也可以设置的钩子函数
- beforeEnter(to,from, next)
【3】组件守卫:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。
- beforeRouteEnter(to,from, next) 无法获取组件 this
- beforeRouteUpdadte(to,from, next) 当前路由改变,但组件被复用时调用;例:foo/1 => foo/2
- beforeRouteLeave(to,from, next) 离开后,禁止用户在未保存修改前离开
虚拟DOM实现原理
用JavaScript对象模拟真实DOM树,对真实DOM进行抽象
diff算法:比较两棵虚拟树的差异
pach算法:将两个虚拟DOM对象的差异应用到真实的DOM树
v-if和v-for为什么不建议一起使用?
vue2.x版本中,当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级;
vue3.x版本中,当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级。
官网明确指出:避免 v-if 和 v-for 一起使用,永远不要在一个元素上同时使用 v-if 和 v-for。可以先对数据在计算数据中进行过滤,然后再进行遍历渲染;
操作和实现起来都没有什么问题,页面也会正常展示。但是会带来不必要的性能消耗;
Vue父子组件的生命周期顺序
- 加载渲染过程: 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程:父beforeUpdate->父updated
- 销毁过程: 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
组件中写 name 选项有哪些好处
- 可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
- 可以通过 name 属性实现缓存功能 (keep-alive)
- 可以通过 name 来识别组件(跨级组件通信时非常重要)
- 使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的
vue 如何快速定位那个组件出现性能问题的
⽤ timeline ⼯具。 通过 timeline 来查看每个函数的调⽤时常,定位出哪个函数的问题,从⽽能判断哪个组件出了问题
性能优化
Vue项目性能优化
1. 编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if 和v-for区分使用场景,v-if和v-for不要连用;
v-for遍历必须为item添加key,key保证唯一性,且避免同时使用vif
computed和method区分使用场景
路由懒加载、图片懒加载、长列表动态加载
第三方模块按需导入
对于短时间的大量操作(缩放、滚动)使用防抖、节流函数
事件的销毁
SPA 页面采用keep-alive缓存组件
组件的延迟加载,可以把页面资源划分为多份,用到的时候才会按需加载,这样减少第一次加载的消耗。
代码精简,去除 console ,可复用的方法、组件提取出来
不要写行内样式,避免dom重绘
2. SEO优化
- 服务端渲染SSR
- 预渲染
3. 打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
说几种如何实现vue首屏加载优化的
- 把不常改变的库放到index.html中,通过cdn引入
- vue路由的懒加载
- vue组件尽量不要全局引入
- 使用轻量级的工具库
SPA首屏加载速度慢的怎么解决?
首屏时间
指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容;
加载慢的原因:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
常见的几种SPA首屏优化方式
-
减小入口文件体积:路由懒加载
-
静态资源本地缓存: 前端合理利用localStorage
-
UI框架按需加载
-
图片资源的压缩: 在线字体图标、雪碧图
-
组件重复打包: 配置CommonsChunkPlugin
// 假设moment.js文件是一个常用的库,现在很多文件都使用了该文件,这就造成了重复加载 // 解决方案: 在webpack的config文件中,修改CommonsChunkPlugin的配置。 minChunks: 3 //把使用3次及以上的包抽离出来 放进公告依赖文件,避免了重复加载
-
开启GZip压缩:compression-webpack-plugin插件
nmp i compression-webpack-plugin -D //安装 compression-webpack-plugin // 并在vue.config.js 中引入并修改webpack配置 在服务器上我们也要做相应的配置。 const CompressionPlugin = require('compression-webpack-plugin') configureWebpack: (config) => { if (process.env.NODE_ENV === 'production') { // 为生产环境修改配置... config.mode = 'production' return { plugins: [new CompressionPlugin({ test: /\.js$|\.html$|\.css/, //匹配文件名 threshold: 10240, //对超过10k的数据进行压缩 deleteOriginalAssets: false //是否删除原文件 })] } } }
-
使用SSR( 服务端渲染):组件或页面通过服务器生成html字符串,再发送到浏览器(Vue建议使用nuxt.js)实现服务端渲染。
SSR是什么,优缺点是什么?
Server-Side Rendering 我们称其为SSR,意为服务端渲染指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程;
解决了以下两个问题:
- seo:搜索引擎优先爬取页面HTML结构,使用ssr时,服务端已经生成了和业务想关联的HTML,有利于seo
- 首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端
优点:
SSR 有着更好的 SEO(搜索引擎优化)、并且首屏加载速度更快。
缺点:
开发条件会受限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。
服务器会有更大的负载需求。
网络请求
axios和ajax的区别
axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。
简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。 axios是ajax, ajax不止axios。
JavaScript
什么是闭包,用途有哪些?
定义在一个函数内部的函数。其中一个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
用途:1.模仿块级作用域,2.存储变量,3.封装私有变量
普通函数,箭头函数的区别
- 箭头函数没有原型,原型是undefined
- 箭头函数this指向全局对象,而函数指向引用对象
- call,apply,bind方法改变不了箭头函数的指向
异步同步
同步:向服务器发送请求,必须等请求到内容,才能刷新页面,用户才能看到新内容
异步:向服务器发送请求,这时可以做其他事情,内容请求到时,用户不用刷新页面,也可以看到新内容
浏览器
cookie和seesion区别
- cookie数据存放在客户的浏览器上,session存放在服务器
- cookie不是很安全,别人可以分析存放在本地的COOKIE进行COOKIE欺骗,考虑安全应该使用seesion
- session会在一定事件内保存在服务器上,当访问增多,会比较占用你的服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
- 单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie