路由的hash和history模式的区别
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
1. hash模式
简介: hash模式是开发中默认的模式,它的URL带着一个#
特点:hash值会呈现在URL外面,然而不会呈现在HTTP申请中,对后端齐全没有影响。所以扭转hash值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的IE浏览器也反对这种模式。hash路由被称为是前端路由,曾经成为SPA(单页面利用)的标配。
原理: hash模式的次要原理就是onhashchange()事件:
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1);}
应用onhashchange()事件的益处就是,在页面的hash值发生变化时,无需向后端发动申请,window就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash值变动对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的hash值和对应的URL关联起来了。
2. history模式
简介: history模式的URL中没有#,它应用的是传统的路由散发模式,即用户在输出一个URL时,服务器会接管这个申请,并解析这个URL,而后做出相应的逻辑解决。 特点: 相比hash模式更加难看。然而,history模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回404。 API: history api能够分为两大部分,切换历史状态和批改历史状态:
- 批改历史状态:包含了 HTML5 History Interface 中新增的
pushState()
和replaceState()
办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了url,但浏览器不会立刻向后端发送申请。如果要做到扭转url但又不刷新页面的成果,就须要前端用上这两个API。 - 切换历史状态: 包含
forward()
、back()
、go()
三个办法,对应浏览器的后退,后退,跳转操作。
尽管history模式抛弃了俊俏的#。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。
如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({ mode: 'history', routes: [...]})
3. 两种模式比照
调用 history.pushState() 相比于间接批改 hash,存在以下劣势:
- pushState() 设置的新 URL 能够是与以后 URL 同源的任意 URL;而 hash 只可批改 # 前面的局部,因而只能设置与以后 URL 同文档的 URL;
- pushState() 设置的新 URL 能够与以后 URL 截然不同,这样也会把记录增加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录增加到栈中;
- pushState() 通过 stateObject 参数能够增加任意类型的数据到记录中;而 hash 只可增加短字符串;
- pushState() 可额定设置 title 属性供后续应用。
- hash模式下,仅hash符号之前的url会被蕴含在申请中,后端如果没有做到对路由的全笼罩,也不会返回404谬误;history模式下,前端的url必须和理论向后端发动申请的url统一,如果没有对用的路由解决,将返回404谬误。
hash模式和history模式都有各自的劣势和缺点,还是要依据理论状况选择性的应用。
vue和react的区别
=> 相同点:
1. 数据驱动页面,提供响应式的试图组件2. 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents标准3. 数据流动单向,都反对服务器的渲染SSR4. 都有反对native的办法,react有React native, vue有wexx
=> 不同点:
1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的 2.数据渲染:大规模的数据渲染,react更快 3.应用场景:React配合Redux架构适宜大规模多人合作简单我的项目,Vue适宜小快的我的项目 4.开发格调:react举荐做法jsx + inline style把html和css都写在js了 vue是采纳webpack + vue-loader单文件组件格局,html, js, css同一个文件
虚构 DOM 实现原理?
虚构 DOM 的实现原理次要包含以下 3 局部:
- 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
- diff 算法 — 比拟两棵虚构 DOM 树的差别;
- pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。
Vue.js的template编译
简而言之,就是先转化成AST树,再失去的render函数返回VNode(Vue的虚构DOM节点),具体步骤如下:
首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创立编译器的。另外compile还负责合并option。
而后,AST会通过generate(将AST语法树转化成render funtion字符串的过程)失去render函数,render的返回值是VNode,VNode是Vue的虚构DOM节点,外面有(标签名、子节点、文本等等)
Vue是如何收集依赖的?
在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由一般对象变成响应式对象,在这个过程中便会进行依赖收集的相干逻辑,如下所示∶
function defieneReactive (obj, key, val){ const dep = new Dep(); ... Object.defineProperty(obj, key, { ... get: function reactiveGetter () { if(Dep.target){ dep.depend(); ... } return val } ... })}
以上只保留了要害代码,次要就是 const dep = new Dep()
实例化一个 Dep 的实例,而后在 get 函数中通过 dep.depend()
进行依赖收集。 (1)Dep Dep是整个依赖收集的外围,其要害代码如下:
class Dep { static target; subs; constructor () { ... this.subs = []; } addSub (sub) { this.subs.push(sub) } removeSub (sub) { remove(this.sub, sub) } depend () { if(Dep.target){ Dep.target.addDep(this) } } notify () { const subs = this.subds.slice(); for(let i = 0;i < subs.length; i++){ subs[i].update() } }}
Dep 是一个 class ,其中有一个关 键的动态属性 static,它指向了一个全局惟一 Watcher,保障了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的治理,再看看 Watcher 的相干代码∶
(2)Watcher
class Watcher { getter; ... constructor (vm, expression){ ... this.getter = expression; this.get(); } get () { pushTarget(this); value = this.getter.call(vm, vm) ... return value } addDep (dep){ ... dep.addSub(this) } ...}function pushTarget (_target) { Dep.target = _target}
Watcher 是一个 class,它定义了一些办法,其中和依赖收集相干的次要有 get、addDep 等。
(3)过程
在实例化 Vue 时,依赖收集的相干过程如下∶
初 始 化 状 态 initState , 这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 局部便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher ,进入 Watcher 中,便会执行 this.get() 办法,
updateComponent = () => { vm._update(vm._render())}new Watcher(vm, updateComponent)
get 办法中的 pushTarget 实际上就是把 Dep.target 赋值为以后的 watcher。
this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 办法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 办法,也就会执行 Dep.target.addDep(this)。方才 Dep.target 曾经被赋值为 watcher,于是便会执行 addDep 办法,而后走到 dep.addSub() 办法,便将以后的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目标是为后续数据变动时候能告诉到哪些 subs 做筹备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便曾经实现了一个依赖收集的过程。
MVVM、MVC、MVP的区别
MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,次要通过拆散关注点的形式来组织代码构造,优化开发效率。
在开发单页面利用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简略我的项目时,可能看不出什么问题,如果我的项目变得复杂,那么整个文件就会变得简短、凌乱,这样对我的项目开发和前期的我的项目保护是十分不利的。
(1)MVC
MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 利用了观察者模式,当 Model 层产生扭转的时候它会告诉无关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。
(2)MVVM
MVVM 分为 Model、View、ViewModel:
- Model代表数据模型,数据和业务逻辑都在Model层中定义;
- View代表UI视图,负责数据的展现;
- ViewModel负责监听Model中数据的扭转并且管制视图的更新,解决用户交互操作;
Model和View并无间接关联,而是通过ViewModel来进行分割的,Model和ViewModel之间有着双向数据绑定的分割。因而当Model中的数据扭转时会触发View层的刷新,View中因为用户交互操作而扭转的数据也会在Model中同步。
这种模式实现了 Model和View的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作DOM。
(3)MVP
MVP 模式与 MVC 惟一不同的在于 Presenter 和 Controller。在 MVC 模式中应用观察者模式,来实现当 Model 层数据发生变化的时候,告诉 View 层的更新。这样 View 层和 Model 层耦合在一起,当我的项目逻辑变得复杂的时候,可能会造成代码的凌乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过应用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只晓得 Model 的接口,因而它没有方法管制 View 层的更新,MVP 模式中,View 层的接口裸露给了 Presenter 因而能够在 Presenter 中将 Model 的变动和 View 的变动绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还蕴含了其余的响应逻辑。
vue 中应用了哪些设计模式
1.工厂模式 - 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3.公布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装璜模式: (@装璜器的用法)
6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略
参考:前端vue面试题具体解答
为什么vue组件中data必须是一个函数?
对象为援用类型,当复用组件时,因为数据对象都指向同一个data对象,当在一个组件中批改data时,其余重用的组件中的data会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object的实例),援用地址不同,则不会呈现这个问题。
Vue中封装的数组办法有哪些,其如何实现页面更新
在Vue中,对响应式解决利用的是Object.defineProperty对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行hack,让Vue能监听到其中的变动。 那Vue是如何实现让这些数组办法实现元素的实时更新的呢,上面是Vue中对这些办法的封装:
// 缓存数组原型const arrayProto = Array.prototype;// 实现 arrayMethods.__proto__ === Array.prototypeexport const arrayMethods = Object.create(arrayProto);// 须要进行性能拓展的办法const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse"];/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function(method) { // 缓存原生数组办法 const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { // 执行并缓存原生数组性能 const result = original.apply(this, args); // 响应式解决 const ob = this.__ob__; let inserted; switch (method) { // push、unshift会新增索引,所以要手动observer case "push": case "unshift": inserted = args; break; // splice办法,如果传入了第三个参数,也会有索引退出,也要手动observer。 case "splice": inserted = args.slice(2); break; } // if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听 // notify change ob.dep.notify();// 告诉依赖更新 // 返回原生数组办法的执行后果 return result; });});
简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的Observer对象,如果有新的值,就调用observeArray持续对新的值察看变动(也就是通过target__proto__ == arrayMethods
来扭转了数组实例的型),而后手动调用notify,告诉渲染watcher,执行update。
vue-cli 工程罕用的 npm 命令有哪些
- 下载
node_modules
资源包的命令:
npm install
- 启动
vue-cli
开发环境的 npm命令:
npm run dev
vue-cli
生成 生产环境部署资源 的npm
命令:
npm run build
- 用于查看
vue-cli
生产环境部署资源文件大小的npm
命令:
npm run build --report
在浏览器上自动弹出一个 展现vue-cli
工程打包后app.js
、manifest.js
、vendor.js
文件外面所蕴含代码的页面。能够具此优化vue-cli
生产环境部署的动态资源,晋升 页面 的加载速度
简述 mixin、extends 的笼罩逻辑
(1)mixin 和 extends mixin 和 extends均是用于合并、拓展组件的,两者均通过 mergeOptions 办法实现合并。
- mixins 接管一个混入对象的数组,其中混入对象能够像失常的实例对象一样蕴含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子依照传入程序顺次调用,并在调用组件本身的钩子之前被调用。
extends 次要是为了便于扩大单文件组件,接管一个对象或构造函数。
(2)mergeOptions 的执行过程
- 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
- 对未合并的选项,进行判断
if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm); } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } }}
- 合并解决。依据一个通用 Vue 实例所蕴含的选项进行分类逐个判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并后果存储在新定义的 options 对象里。
- 返回合并后果 options。
Vue我的项目本地开发实现后部署到服务器后报404是什么起因呢
如何部署
前后端拆散开发模式下,前后端是独立布署的,前端只须要将最初的构建物上传至指标服务器的web
容器指定的动态目录下即可
咱们晓得vue
我的项目在构建后,是生成一系列的动态文件
惯例布署咱们只须要将这个目录上传至指标服务器即可
让web
容器跑起来,以nginx
为例
server { listen 80; server_name www.xxx.com; location / { index /data/dist/index.html; }}
配置实现记得重启nginx
// 查看配置是否正确nginx -t // 平滑重启nginx -s reload
操作完后就能够在浏览器输出域名进行拜访了
当然下面只是提到最简略也是最间接的一种布署形式
什么自动化,镜像,容器,流水线布署,实质也是将这套逻辑形象,隔离,用程序来代替重复性的劳动,本文不开展
404问题
这是一个经典的问题,置信很多同学都有遇到过,那么你晓得其真正的起因吗?
咱们先还原一下场景:
vue
我的项目在本地时运行失常,但部署到服务器中,刷新页面,呈现了404谬误
先定位一下,HTTP 404 谬误意味着链接指向的资源不存在
问题在于为什么不存在?且为什么只有history
模式下会呈现这个问题?
为什么history模式下有问题
Vue
是属于单页利用(single-page application)
而SPA
是一种网络应用程序或网站的模型,所有用户交互是通过动静重写以后页面,后面咱们也看到了,不论咱们利用有多少页面,构建物都只会产出一个index.html
当初,咱们回头来看一下咱们的nginx
配置
server { listen 80; server_name www.xxx.com; location / { index /data/dist/index.html; }}
能够依据 nginx
配置得出,当咱们在地址栏输出 www.xxx.com
时,这时会关上咱们 dist
目录下的 index.html
文件,而后咱们在跳转路由进入到 www.xxx.com/login
要害在这里,当咱们在 website.com/login
页执行刷新操作,nginx location
是没有相干配置的,所以就会呈现 404
的状况
为什么hash模式下没有问题
router hash
模式咱们都晓得是用符号#示意的,如 website.com/#/login
, hash
的值为 #/login
它的特点在于:hash
尽管呈现在 URL
中,但不会被包含在 HTTP
申请中,对服务端齐全没有影响,因而扭转 hash
不会从新加载页面
hash
模式下,仅 hash
符号之前的内容会被蕴含在申请中,如 website.com/#/login
只有 website.com
会被蕴含在申请中 ,因而对于服务端来说,即便没有配置location
,也不会返回404
谬误
解决方案
看到这里我置信大部分同学都能想到怎么解决问题了,
产生问题的实质是因为咱们的路由是通过JS来执行视图切换的,
当咱们进入到子路由时刷新页面,web
容器没有绝对应的页面此时会呈现404
所以咱们只须要配置将任意页面都重定向到 index.html
,把路由交由前端解决
对nginx
配置文件.conf
批改,增加try_files $uri $uri/ /index.html;
server { listen 80; server_name www.xxx.com; location / { index /data/dist/index.html; try_files $uri $uri/ /index.html; }}
批改完配置文件后记得配置的更新
nginx -s reload
这么做当前,你的服务器就不再返回 404 谬误页面,因为对于所有门路都会返回 index.html
文件
为了防止这种状况,你应该在 Vue
利用外面笼罩所有的路由状况,而后在给出一个 404 页面
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ]})
父子组件生命周期调用程序(简略)
渲染程序:先父后子,实现程序:先子后父
更新程序:父更新导致子更新,子更新实现后父
销毁程序:先父后子,实现程序:先子后父
SPA首屏加载速度慢的怎么解决
一、什么是首屏加载
首屏工夫(First Contentful Paint
),指的是浏览器从响应用户输出网址地址,到首屏内容渲染实现的工夫,此时整个网页不肯定要全副渲染实现,但须要展现以后视窗须要的内容
首屏加载能够说是用户体验中最重要的环节
对于计算首屏工夫
利用performance.timing
提供的数据:
通过DOMContentLoad
或者performance
来计算出首屏工夫
// 计划一:document.addEventListener('DOMContentLoaded', (event) => { console.log('first contentful painting');});// 计划二:performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]// 会返回一个 PerformancePaintTiming的实例,构造如下:{ name: "first-contentful-paint", entryType: "paint", startTime: 507.80000002123415, duration: 0,};
二、加载慢的起因
在页面渲染的过程,导致加载速度慢的因素可能如下:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否反复发送申请去加载了
- 加载脚本的时候,渲染内容梗塞了
三、解决方案
常见的几种SPA首屏优化形式
- 减小入口文件积
- 动态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件反复打包
- 开启GZip压缩
- 应用SSR
1. 减小入口文件体积
罕用的伎俩是路由懒加载,把不同路由对应的组件宰割成不同的代码块,待路由被申请的时候会独自打包路由,使得入口文件变小,加载速度大大增加
在vue-router
配置路由的时候,采纳动静加载路由的模式
routes:[ path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue')]
以函数的模式加载路由,这样就能够把各自的路由文件别离打包,只有在解析给定的路由时,才会加载路由组件
2. 动态资源本地缓存
后端返回资源问题:
- 采纳
HTTP
缓存,设置Cache-Control
,Last-Modified
,Etag
等响应头 - 采纳
Service Worker
离线缓存
前端正当利用localStorage
3. UI框架按需加载
在日常应用UI
框架,例如element-UI
、或者antd
,咱们经常性间接援用整个UI
库
import ElementUI from 'element-ui'Vue.use(ElementUI)
但实际上我用到的组件只有按钮,分页,表格,输出与正告 所以咱们要按需援用
import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';Vue.use(Button)Vue.use(Input)Vue.use(Pagination)
4. 组件反复打包
假如A.js
文件是一个罕用的库,当初有多个路由应用了A.js
文件,这就造成了反复下载
解决方案:在webpack
的config
文件中,批改CommonsChunkPlugin
的配置
minChunks: 3
minChunks
为3示意会把应用3次及以上的包抽离进去,放进公共依赖文件,防止了反复加载组件
5. 图片资源的压缩
图片资源尽管不在编码过程中,但它却是对页面性能影响最大的因素
对于所有的图片资源,咱们能够进行适当的压缩
对页面上应用到的icon
,能够应用在线字体图标,或者雪碧图,将泛滥小图标合并到同一张图上,用以加重http
申请压力。
6. 开启GZip压缩
拆完包之后,咱们再用gzip
做一下压缩 装置compression-webpack-plugin
cnmp i compression-webpack-plugin -D
在vue.congig.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 //是否删除原文件 })] } }
在服务器咱们也要做相应的配置 如果发送申请的浏览器反对gzip
,就发送给它gzip
格局的文件 我的服务器是用express
框架搭建的 只有装置一下compression
就能应用
const compression = require('compression')app.use(compression()) // 在其余中间件应用之前调用
7. 应用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器
从头搭建一个服务端渲染是很简单的,vue
利用倡议应用Nuxt.js
实现服务端渲染
四、小结
缩小首屏渲染工夫的办法有很多,总的来讲能够分成两大部分 :资源加载优化
和 页面渲染优化
下图是更为全面的首屏优化的计划
大家能够依据本人我的项目的状况抉择各种形式进行首屏渲染的优化
Vue我的项目性能优化-具体
Vue
框架通过数据双向绑定和虚构DOM
技术,帮咱们解决了前端开发中最脏最累的DOM
操作局部, 咱们不再须要去思考如何操作DOM
以及如何最高效地操作DOM
;但Vue
我的项目中依然存在我的项目首屏优化、Webpack
编译配置优化等问题,所以咱们依然须要去关注Vue
我的项目性能方面的优化,使我的项目具备更高效的性能、更好的用户体验
代码层面的优化
1. v-if 和 v-show 辨别应用场景
v-if
是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块v-show
就简略得多, 不论初始条件是什么,元素总是会被渲染,并且只是简略地基于CSS
display
的none/block
属性进行切换。- 所以,
v-if
实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show
则实用于须要十分频繁切换条件的场景
2. computed 和 watch 辨别应用场景
computed
: 是计算属性,依赖其它属性值,并且computed
的值有缓存,只有它依赖的属性值产生扭转,下一次获取computed
的值时才会从新计算 computed 的值;watch
: 更多的是「察看」的作用,相似于某些数据的监听回调 ,每当监听的数据变动时都会执行回调进行后续操作
使用场景:
- 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用
computed
,因为能够利用computed
的缓存个性,防止每次获取值时,都要从新计算; - 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用
watch
,应用watch
选项容许咱们执行异步操作 ( 拜访一个API
),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的
3. v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
v-for
遍历必须为item
增加key
- 在列表数据进行遍历渲染时,须要为每一项
item
设置惟一key
值,不便Vue.js
外部机制精准找到该条列表数据。当state
更新时,新的状态值和旧的状态值比照,较快地定位到diff
- 在列表数据进行遍历渲染时,须要为每一项
v-for
遍历防止同时应用v-if
vue2.x
中v-for
比v-if
优先级高,如果每一次都须要遍历整个数组,将会影响速度,尤其是当之须要渲染很小一部分的时候,必要状况下应该替换成computed
属性
举荐:
<ul> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} </li></ul>computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) }}
不举荐:
<ul> <li v-for="user in users" v-if="user.isActive" :key="user.id"> {{ user.name }} </li></ul>
4. 长列表性能优化
Vue
会通过Object.defineProperty
对数据进行劫持,来实现视图响应数据的变动,然而有些时候咱们的组件就是纯正的数据展现,不会有任何扭转,咱们就不须要 Vue 来劫持咱们的数据,在大量数据展现的状况下,这可能很显著的缩小组件初始化的工夫,那如何禁止Vue
劫持咱们的数据呢?能够通过Object.freeze
办法来解冻一个对象,一旦被解冻的对象就再也不能被批改了
export default { data: () => ({ users: {} }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); }};
5. 事件的销毁
Vue
组件销毁时,会主动清理它与其它实例的连贯,解绑它的全副指令及事件监听器,然而仅限于组件自身的事件。 如果在 js
内应用 addEventListener
等形式是不会主动销毁的,咱们须要在组件销毁时手动移除这些事件的监听,免得造成内存泄露,如:
created() { addEventListener('click', this.click, false)},beforeDestroy() { removeEventListener('click', this.click, false)}
6. 图片资源懒加载
对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的晋升,也进步了用户体验。咱们在我的项目中应用 Vue
的 vue-lazyload
插件
npm install vue-lazyload --save-dev
在入口文件 man.js
中引入并应用
import VueLazyload from 'vue-lazyload'Vue.use(VueLazyload)// 或者增加自定义选项Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', loading: 'dist/loading.gif', attempt: 1})
在 vue
文件中将 img
标签的 src
属性间接改为 v-lazy
,从而将图片显示方式更改为懒加载显示
<img v-lazy="/static/img/1.png">
以上为 vue-lazyload
插件的简略应用,如果要看插件的更多参数选项,能够查看 vue-lazyload 的 github 地址(opens new window)
7. 路由懒加载
Vue 是单页面利用,可能会有很多的路由引入 ,这样应用 webpcak
打包后的文件很大,当进入首页时,加载的资源过多,页面会呈现白屏的状况,不利于用户体验。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,然而可能其余的页面的速度就会降下来
路由懒加载:
const Foo = () => import('./Foo.vue')const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ]})
8. 第三方插件的按需引入
咱们在我的项目中常常会须要引入第三方插件,如果咱们间接引入整个插件,会导致我的项目的体积太大,咱们能够借助 babel-plugin-component
,而后能够只引入须要的组件,以达到减小我的项目体积的目标。以下为我的项目中引入 element-ui
组件库为例
npm install babel-plugin-component -D
将 .babelrc
批改为:
{ "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ]}
在 main.js
中引入局部组件:
import Vue from 'vue';import { Button, Select } from 'element-ui';Vue.use(Button)Vue.use(Select)
9. 优化有限列表性能
如果你的利用存在十分长或者有限滚动的列表,那么须要采纳虚构列表
的技术来优化性能,只须要渲染少部分区域的内容,缩小从新渲染组件和创立 dom
节点的工夫。 你能够参考以下开源我的项目 vue-virtual-scroll-list (opens new window) 和 vue-virtual-scroller (opens new window)来优化这种有限列表的场景的
10. 服务端渲染 SSR or 预渲染
服务端渲染是指 Vue
在客户端将标签渲染成的整个 html
片段的工作在服务端实现,服务端造成的 html
片段间接返回给客户端这个过程就叫做服务端渲染。
- 如果你的我的项目的
SEO
和首屏渲染
是评估我的项目的要害指标,那么你的我的项目就须要服务端渲染来帮忙你实现最佳的初始加载性能和SEO
- 如果你的
Vue
我的项目只需改善多数营销页面(例如/
,/about
,/contact
等)的SEO
,那么你可能须要预渲染,在构建时简略地生成针对特定路由的动态HTML
文件。 长处是设置预渲染更简略 ,并能够将你的前端作为一个齐全动态的站点,具体你能够应用 prerender-spa-plugin (opens new window) 就能够轻松地增加预渲染
Webpack 层面的优化
1. Webpack 对图片进行压缩
对小于 limit
的图片转化为 base64
格局,其余的不做操作。所以对有些较大的图片资源,在申请资源的时候,加载会很慢,咱们能够用 image-webpack-loader
来压缩图片
npm install image-webpack-loader --save-dev
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use:[ { loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { loader: 'image-webpack-loader', options: { bypassOnDebug: true, } } ]}
2. 缩小 ES6 转为 ES5 的冗余代码
Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如上面的 ES6 代码
class HelloWebpack extends Component{...}
这段代码再被转换成能失常运行的 ES5 代码时须要以下两个辅助函数:
babel-runtime/helpers/createClass // 用于实现 class 语法babel-runtime/helpers/inherits // 用于实现 extends 语法
在默认状况下, Babel
会在每个输入文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会呈现很屡次,造成代码冗余。为了不让这些辅助函数的代码反复呈现,能够在依赖它们时通过 require('babel-runtime/helpers/createClass')
的形式导入,这样就能做到只让它们呈现一次。babel-plugin-transform-runtime
插件就是用来实现这个作用的,将相干辅助函数进行替换成导入语句,从而减小 babel 编译进去的代码的文件大小
npm install babel-plugin-transform-runtime --save-dev
批改 .babelrc
配置文件为:
"plugins": [ "transform-runtime"]
3. 提取公共代码
如果我的项目中没有去将每个页面的第三方库和公共模块提取进去,则我的项目会存在以下问题:
- 雷同的资源被反复加载,节约用户的流量和服务器的老本。
- 每个页面须要加载的资源太大,导致网页首屏加载迟缓,影响用户体验。
所以咱们须要将多个页面的公共代码抽离成独自的文件,来优化以上问题 。Webpack
内置了专门用于提取多个Chunk
中的公共局部的插件 CommonsChunkPlugin
,咱们在我的项目中 CommonsChunkPlugin
的配置如下:
// 所有在 package.json 外面依赖的包,都会被打包进 vendor.js 这个文件中。new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function(module, count) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ); }}),// 抽取出代码模块的映射关系new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor']})
4. 模板预编译
- 当应用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常状况下这个过程曾经足够快了,但对性能敏感的利用还是最好防止这种用法。
- 预编译模板最简略的形式就是应用单文件组件——相干的构建设置会主动把预编译解决好,所以构建好的代码曾经蕴含了编译进去的渲染函数而不是原始的模板字符串。
- 如果你应用 webpack,并且喜爱拆散 JavaScript 和模板文件,你能够应用 vue-template-loader (opens new window),它也能够在构建过程中把模板文件转换成为 JavaScript 渲染函数
5. 提取组件的 CSS
当应用单文件组件时,组件内的 CSS 会以 style 标签的形式通过 JavaScript 动静注入。这有一些小小的运行时开销,如果你应用服务端渲染,这会导致一段 “无款式内容闪动 (fouc) ” 。将所有组件的 CSS 提取到同一个文件能够防止这个问题,也会让 CSS 更好地进行压缩和缓存
6. 优化 SourceMap
咱们在我的项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且通过压缩、去掉多余的空格、babel编译化后,最终将编译失去的代码会用于线上环境,那么这样解决后的代码和源代码会有很大的差异,当有 bug的时候,咱们只能定位到压缩解决后的代码地位,无奈定位到开发环境中的代码,对于开发来说不好调式定位问题,因而 sourceMap
呈现了,它就是为了解决不好调式代码问题的
SourceMap
的可选值如下(+
号越多,代表速度越快,-
号越多,代表速度越慢,o
代表中等速度)
- 开发环境举荐:
cheap-module-eval-source-map
- 生产环境举荐:
cheap-module-source-map
起因如下:
cheap
: 源代码中的列信息是没有任何作用,因而咱们打包后的文件不心愿蕴含列相干信息,只有行信息能建设打包前后的依赖关系。因而不论是开发环境或生产环境,咱们都心愿增加cheap
的根本类型来疏忽打包前后的列信息;module
:不论是开发环境还是正式环境,咱们都心愿能定位到bug
的源代码具体的地位,比如说某个Vue
文件报错了,咱们心愿能定位到具体的Vue
文件,因而咱们也须要module
配置;soure-map
:source-map
会为每一个打包后的模块生成独立的soucemap
文件 ,因而咱们须要减少source-map
属性;eval-source-map
:eval
打包代码的速度十分快,因为它不生成map
文件,然而能够对eval
组合应用eval-source-map
应用会将map
文件以DataURL
的模式存在打包后的js
文件中。在正式环境中不要应用eval-source-map
, 因为它会减少文件的大小,然而在开发环境中,能够试用下,因为他们打包的速度很快。
7. 构建后果输入剖析
Webpack 输入的代码可读性十分差而且文件十分大,让咱们十分头疼。为了更简略、直观地剖析输入后果,社区中呈现了许多可视化剖析工具。这些工具以图形的形式将后果更直观地展现进去,让咱们疾速理解问题所在。接下来解说咱们在 Vue 我的项目中用到的剖析工具:webpack-bundle-analyzer
if (config.build.bundleAnalyzerReport) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; webpackConfig.plugins.push(new BundleAnalyzerPlugin());}
执行 $ npm run build --report
后生成剖析报告如下
根底的 Web 技术优化
1. 开启 gzip 压缩
gzip
是GNUzip
的缩写,最早用于UNIX
零碎的文件压缩。HTTP
协定上的gzip
编码是一种用来改良web
应用程序性能的技术,web
服务器和客户端(浏览器)必须独特反对 gzip。目前支流的浏览器,Chrome,firefox,IE等都反对该协定。常见的服务器如 Apache,Nginx,IIS 同样反对,zip
压缩效率十分高,通常能够达到70%
的压缩率,也就是说,如果你的网页有30K
,压缩之后就变成了9K
左右
以下咱们以服务端应用咱们相熟的 express
为例,开启 gzip
非常简单,相干步骤如下:
npm install compression --save
var compression = require('compression');var app = express();app.use(compression())
重启服务,察看网络面板外面的 response header
,如果看到如下红圈里的字段则表明 gzip
开启胜利
Nginx开启gzip压缩
#是否启动gzip压缩,on代表启动,off代表开启gzip on;#须要压缩的常见动态资源gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;#因为nginx的压缩产生在浏览器端而微软的ie6很坑爹,会导致压缩后图片看不见所以该选项是禁止ie6产生压缩gzip_disable "MSIE [1-6]\.";#如果文件大于1k就启动压缩gzip_min_length 1k;#以16k为单位,依照原始数据的大小以4倍的形式申请内存空间,个别此项不要批改gzip_buffers 4 16k;#压缩的等级,数字抉择范畴是1-9,数字越小压缩的速度越快,耗费cpu就越大gzip_comp_level 2;
要想配置失效,记得重启nginx
服务
nginx -tnginx -s reload
2. 浏览器缓存
为了进步用户加载页面的速度,对动态资源进行缓存是十分必要的,依据是否须要从新向服务器发动申请来分类,将 HTTP 缓存规定分为两大类(强制缓存,比照缓存)
3. CDN 的应用
浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连贯,而大部分服务器的带宽无限,如果超过限度,网页就半天反馈不过去。而 CDN 能够通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具备更好的可用性,更低的网络提早和丢包率
4. 应用 Chrome Performance 查找性能瓶颈
Chrome
的 Performance
面板能够录制一段时间内的 js
执行细节及工夫。应用 Chrome
开发者工具剖析页面性能的步骤如下。
- 关上
Chrome
开发者工具,切换到Performance
面板 - 点击
Record
开始录制 - 刷新页面或开展某个节点
- 点击
Stop
进行录制
Vue模版编译原理晓得吗,能简略说一下吗?
简略说,Vue的编译过程就是将template
转化为render
函数的过程。会经验以下阶段:
- 生成AST树
- 优化
- codegen
首先解析模版,生成AST语法树
(一种用JavaScript对象的模式来形容整个模板)。 应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM也不会变动。那么优化过程就是深度遍历AST树,依照相干条件对树节点进行标记。这些被标记的节点(动态节点)咱们就能够跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是将优化后的AST树转换为可执行的代码
。
SPA、SSR的区别是什么
咱们当初编写的Vue
、React
和Angular
利用大多数状况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,因为良好的用户体验逐步成为支流的开发模式。但同时也会有首屏加载工夫长,SEO
不敌对的问题,因而有了SSR
,这也是为什么面试中会问到两者的区别
SPA
(Single Page Application)即单页面利用。个别也称为 客户端渲染(Client Side Render), 简称CSR
。SSR
(Server Side Render)即 服务端渲染。个别也称为 多页面利用(Mulpile Page Application),简称MPA
SPA
利用只会首次申请html
文件,后续只须要申请JSON
数据即可,因而用户体验更好,节约流量,服务端压力也较小。然而首屏加载的工夫会变长,而且SEO
不敌对。为了解决以上毛病,就有了SSR
计划,因为HTML
内容在服务器一次性生成进去,首屏加载快,搜索引擎也能够很不便的抓取页面信息。但同时SSR计划也会有性能,开发受限等问题- 在抉择上,如果咱们的利用存在首屏加载优化需要,
SEO
需要时,就能够思考SSR
- 但并不是只有这一种代替计划,比方对一些不常变动的动态网站,SSR反而浪费资源,咱们能够思考预渲染(
prerender
)计划。另外nuxt.js/next.js
中给咱们提供了SSG(Static Site Generate)
动态网站生成计划也是很好的动态站点解决方案,联合一些CI
伎俩,能够起到很好的优化成果,且能节约服务器资源
内容生成上的区别:
SSR
SPA
部署上的区别
Vue中常见性能优化
编码优化 :
- 应用
v-show
复用DOM
:防止反复创立组件
<template> <div class="cell"> <!-- 这种状况用v-show复用DOM,比v-if成果好 --> <div v-show="value" class="on"> <Heavy :n="10000"/> </div> <section v-show="!value" class="off"> <Heavy :n="10000"/> </section> </div></template>
- 正当应用路由懒加载、异步组件,无效拆分
App
尺寸,拜访时才异步加载
const router = createRouter({ routes: [ // 借助webpack的import()实现异步组件 { path: '/foo', component: () => import('./Foo.vue') } ]})
keep-alive
缓存页面:防止反复创立组件实例,且能保留缓存组件状态
<router-view v-slot="{ Component }"> <keep-alive> <component :is="Component"></component> </keep-alive></router-view>
v-once
和v-memo
:不再变动的数据应用v-once
<!-- single element --><span v-once>This will never change: {{msg}}</span><!-- the element have children --><div v-once> <h1>comment</h1> <p>{{msg}}</p></div><!-- component --><my-component v-once :comment="msg"></my-component><!-- `v-for` directive --><ul> <li v-for="i in list" v-once>{{i}}</li></ul>
按条件跳过更新时应用v-momo
:上面这个列表只会更新选中状态变动项
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p></div>
- 长列表性能优化:如果是大数据长列表,可采纳虚构滚动,只渲染少部分区域的内容
<recycle-scroller class="items" :items="items" :item-size="24"> <template v-slot="{ item }"> <FetchItemView :item="item" @vote="voteItem(item)" /> </template></recycle-scroller>
- 避免外部透露,组件销毁后把全局变量和事件销毁:
Vue
组件销毁时,会主动解绑它的全副指令及事件监听器,然而仅限于组件自身的事件
export default { created() { this.timer = setInterval(this.refresh, 2000) }, beforeUnmount() { clearInterval(this.timer) }}
- 图片懒加载
对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载
<!-- 参考 https://github.com/hilongjw/vue-lazyload --><img v-lazy="/static/img/1.png">
- 滚动到可视区域动静加载
https://tangbc.github.io/vue-virtual-scroll-list(opens new window)
- 第三方插件按需引入:(
babel-plugin-component
)
像element-plus
这样的第三方组件库能够按需引入防止体积太大
import { createApp } from 'vue';import { Button, Select } from 'element-plus';const app = createApp()app.use(Button)app.use(Select)
- 服务端渲染:SSR
如果SPA
利用有首屏渲染慢的问题,能够思考SSR
以及上面的其余办法
- 不要将所有的数据都放在
data
中,data
中的数据都会减少getter
和setter
,会收集对应的watcher
v-for
遍历为item
增加key
v-for
遍历防止同时应用v-if
- 辨别
computed
和watch
的应用 - 拆分组件(进步复用性、减少代码的可维护性,缩小不必要的渲染 )
- 防抖、节流
用户体验
app-skeleton
骨架屏pwa
serviceworker
SEO优化
- 预渲染插件
prerender-spa-plugin
- 服务端渲染
ssr
打包优化
Webpack
对图片进行压缩- 应用
cdn
的形式加载第三方模块 - 多线程打包
happypack
splitChunks
抽离公共文件- 优化
SourceMap
- 构建后果输入剖析,利用
webpack-bundle-analyzer
可视化剖析工具
根底的 Web 技术的优化
- 服务端
gzip
压缩 - 浏览器缓存
CDN
的应用- 应用
Chrome Performance
查找性能瓶颈
Vue我的项目中你是如何解决跨域的呢
一、跨域是什么
跨域实质是浏览器基于同源策略的一种平安伎俩
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最外围也最根本的平安性能
所谓同源(即指在同一个域)具备以下三个相同点
- 协定雷同(protocol)
- 主机雷同(host)
- 端口雷同(port)
反之非同源申请,也就是协定、端口、主机其中一项不雷同的时候,这时候就会产生跨域
肯定要留神跨域是浏览器的限度,你用抓包工具抓取接口数据,是能够看到接口曾经把数据返回回来了,只是浏览器的限度,你获取不到数据。用postman申请接口可能申请到数据。这些再次印证了跨域是浏览器的限度。
如果让你从零开始写一个vuex,说说你的思路
思路剖析
这个题目很有难度,首先思考vuex
解决的问题:存储用户全局状态并提供治理状态API。
vuex
需要剖析- 如何实现这些需要
答复范例
- 官网说
vuex
是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
- 要实现一个
Store
存储全局状态 - 要提供批改状态所需API:
commit(type, payload), dispatch(type, payload)
- 实现
Store
时,能够定义Store
类,构造函数接管选项options
,设置属性state
对外裸露状态,提供commit
和dispatch
批改属性state
。这里须要设置state
为响应式对象,同时将Store
定义为一个Vue
插件 commit(type, payload)
办法中能够获取用户传入mutations
并执行它,这样能够按用户提供的办法批改状态。dispatch(type, payload)
相似,但须要留神它可能是异步的,须要返回一个Promise
给用户以解决异步后果
实际
Store
的实现:
class Store { constructor(options) { this.state = reactive(options.state) this.options = options } commit(type, payload) { this.options.mutations[type].call(this, this.state, payload) }}
vuex简易版
/** * 1 实现插件,挂载$store * 2 实现store */let Vue;class Store { constructor(options) { // state响应式解决 // 内部拜访: this.$store.state.*** // 第一种写法 // this.state = new Vue({ // data: options.state // }) // 第二种写法:避免外界间接接触外部vue实例,避免内部强行变更 this._vm = new Vue({ data: { $$state: options.state } }) this._mutations = options.mutations this._actions = options.actions this.getters = {} options.getters && this.handleGetters(options.getters) this.commit = this.commit.bind(this) this.dispatch = this.dispatch.bind(this) } get state () { return this._vm._data.$$state } set state (val) { return new Error('Please use replaceState to reset state') } handleGetters (getters) { Object.keys(getters).map(key => { Object.defineProperty(this.getters, key, { get: () => getters[key](this.state) }) }) } commit (type, payload) { let entry = this._mutations[type] if (!entry) { return new Error(`${type} is not defined`) } entry(this.state, payload) } dispatch (type, payload) { let entry = this._actions[type] if (!entry) { return new Error(`${type} is not defined`) } entry(this, payload) }}const install = (_Vue) => { Vue = _Vue Vue.mixin({ beforeCreate () { if (this.$options.store) { Vue.prototype.$store = this.$options.store } }, })}export default { Store, install }
验证形式
import Vue from 'vue'import Vuex from './vuex'// this.$storeVue.use(Vuex)export default new Vuex.Store({ state: { counter: 0 }, mutations: { // state从哪里来的 add (state) { state.counter++ } }, getters: { doubleCounter (state) { return state.counter * 2 } }, actions: { add ({ commit }) { setTimeout(() => { commit('add') }, 1000) } }, modules: { }})