- 对MVVM的理解
- Vue2 和 Vue3 响应式数据的理解
- Vue中如何检测数组变化
- Vue中如何进行依赖收集
- 如何理解Vue中的模版编译原理
- Vue生命周期钩子是如何实现的
- Vue的生命周期方法有哪些?一般在哪一步发送请求及原因
- Vue.mixin的使用场景和原理
- Vue组件data为什么必须是个函数
- nextTick在哪里使用?原理是?
- computed 和 watch 区别
- Vue.set方法是如何实现的
- Vue为什么需要虚拟DOM
- Vue中diff算法原理
- 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异
- Vue中key的作用和原理
- 对Vue组件化的理解
- Vue的组件渲染流程
- Vue组件更新流程
- Vue中异步组件的原理
- Vue2.x和Vue3.x渲染器的diff算法
- 再说一下虚拟Dom以及key属性的作用
- Vue2.0 v-for 中 :key 到底有什么用?
- keep-alive
- Vue中组件生命周期调用顺序
- Vue2.x 组件通信有哪些方式
- SSR
- 做过哪些Vue的性能优化
- Vue-Router 导航守卫
- 组件 渲染/更新过程
对MVVM的理解
Vue2 和 Vue3 响应式数据的理解
数组和对象类型当值变化时如何劫持到。对象内部通过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。 多层对象是通过递归来实现劫持。Vue3则采用proxy。
Vue中如何检测数组变化
数组考虑性能原因没有用 defineProperty对数组的每一项进行拦截,而是选择重写数组( push shift pop splice unshift sort reverse) 方法。
- 数组中如果是对象数据类型也会进行递归劫持
-
Vue中如何进行依赖收集
每个属性都拥有自己的dep属性,存放他所依赖的watcher,当属性变化后通知自己对应的watcher去更新。
- 默认在初始化时会调用render函数,此时会触发属性依赖收集 dep.depend
- 当属性发生修改时会触发watcher更新 dep.notify()。
如何理解Vue中的模版编译原理
- 将 template模版转换成 ast 语法树 parserHTML
- 对静态语法做静态标记 markUp diff 来做优化的 静态节点跳过 diff操作。
-
Vue生命周期钩子是如何实现的
Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法。
-
Vue的生命周期方法有哪些?一般在哪一步发送请求及原因
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
- created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
- beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
- mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
- beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
- updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
- keep-alive (activated 和 deactivated)
Vue.mixin的使用场景和原理
- Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用mergeOptions方法进行合并,采用策略模式针对不同的属性进行合并。如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。
- mixin中有很多缺陷 “命名冲突问题”、”依赖问题”、”数据来源问题”。
Vue组件data为什么必须是个函数
每次使用组件时都会对组件进行实例化操作,并且调用data函数返回一个对象作为组件的数据源。这样保证多个组件间数据互不影响。
function Vue() {}
function Sub() { // 会将data存起来
this.data = this.constructor.options.data;
}
Vue.extend = function(options) {
Sub.options = options;
return Sub;
}
let Child = Vue.extend({
data: { name: 'zf' }
});
// 两个组件就是两个实例, 希望数据互不干扰
let child1 = new Child();
let child2 = new Child();
console.log(child1.data.name);
child1.data.name = 'jw';
console.log(child2.data.name);
nextTick在哪里使用?原理是?
- nextTick中的回调在下次DOM更新循环结束之后执行的延迟回调。
- 可用于获取更新后的DOM
Vue中数据更新是异步的,使用 nextTick方法可以保证用户定义的逻辑在更新之后执行。
computed 和 watch 区别
computed和watch都是基于Watcher来实现的
- computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新执行
watch则是监控值的变化,当值发生变化时调用对应的回调函数
Vue.set方法是如何实现的
我们给对象和数组本身都增加了dep属性
- 当给对象新增不存在的属性则触发对象依赖的watcher去更新
当修改数组索引时我们调用数组本身的splice方法去更新数组
Vue为什么需要虚拟DOM
Virtual DOM就是用js对象来描述真实DOM,是对真实DOM的抽象
- 由于直接操作DOM性能低但是js层的操作效率高,可以将DOM操作转化成对象操作,最终通过diff算法比对差异进行更新DOM(减少了对真实DOM的操作)。
-
Vue中diff算法原理
既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异
响应式数据变化,Vue确实可以在数据发生变化时,响应式系统可以立刻得知。但是如果给每个属性都添加watcher用于更新的话,会产生大量的watcher从而降低性能。
而且粒度过细也会导致更新不精准的问题,所以vue采用了组件级的watcher配合diff来检测差异。
Vue中key的作用和原理
Vue在patch过程中通过key可以判断两个虚拟节点是否是相同节点。 (可以复用老节点)
- 无key会导致更新的时候出问题
- 尽量不要采用索引作为key
对Vue组件化的理解
- 组件化开发能大幅提高应用开发效率、测试性、复用性等;
- 常用的组件化技术:属性、自定义事件、插槽等
- 降低更新范围,只重新渲染变化的组件
-
Vue的组件渲染流程
Vue组件更新流程
Vue中异步组件的原理
Vue2.x和Vue3.x渲染器的diff算法
简单来说,diff算法有以下过程
同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心diff)
- 递归比较子节点
正常diff两个树的时间复杂度是O(n^3) 但实际情况下我们很少会进行 跨层级的移动dom,所以Vue将diff进行了优化, 从 O(n^3) -> O(n) ,只有当新旧childern都为多个节点时才需要用核心diff算法进行同层比较。
Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
再说一下虚拟Dom以及key属性的作用
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的 产生的原因。
Virtual DOM 本质就是用一个原生的js对象去描述一个DOM节点。是对真实DOM的一层抽象。
Virtual DOM映射到真实的DOM要经历VNode的create diff patch等阶段。
key的作用是尽可能的复用DOM元素。
新旧children中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧children的节点中保存映射关系,以便能够在旧 children的节点中找到可复用的节点。
key也就是children中节点的唯一标识。
Vue2.0 v-for 中 :key 到底有什么用?
Vue2.0 v-for 中 :key 到底有什么用?
vue 和 react 都实现一套 虚拟dom,使我们可以不直接操作dom元素,只操作数据便可以重新渲染页面。
vue 和 react 的虚拟 dom 的 diff算法大致相同,其核心是基于两个简单的假设:
- 两个相同的组件产生的类似的dom结构,不同的组件产生不同的dom结构。
- 同一层级的一组节点,它们可以通过唯一的id进行区分。
基于以上的两点假设 使的虚拟dom的diff算法的复杂度从 O(N^3) 降到 O(n)
当页面的数据发生变化时,diff算法只会比较同一层级的节点:
- 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点
- 如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。
- 当某一层有很多的节点时,也就是列表节点时,diff算法更新过程默认情况也是遵循以上原则。
我们希望在 B C之间加一个F diff默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用 key来做每个节点做一个唯一标识,diff算法就可以正确的识别此节点,
找到正确的位置插入新的节点。
所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
keep-alive
keep-alive 可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性 include / exclude 允许组件有条件的进行缓存
两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
keep-alive的中还运用了LRU(Least Recently Used)算法。
keep-alive 是 vue中的内置组件 使用render函数实现
export default {
name: 'keep-alive',
abstract: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render() {
/* 获取默认插槽中的第一个组件节点 */
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
/* 获取该组件节点的componentOptions */
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
const name = getComponentName(componentOptions)
const { include, exclude } = this
/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
if (
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
/* 获取组件的key值 */
const key = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
/* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
}
/* 如果没有命中缓存,则将其设置进缓存 */
else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
render 函数 做了哪些事:
- 获取默认插槽中的第一个组件节点
- 获取该组件节点的componentOptions
- 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag
- 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode
- 获取组件的 key值
- 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
- 如果没有命中缓存,则将其设置进缓存
- 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个
Vue中组件生命周期调用顺序
组件的调用顺序都是 先父后子 渲染完成的顺序是 先子后父
组件的销毁操作是 先父后子 销毁完成的顺序是先子后父加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated父组件更新过程
父 beforeUpdate -> 父 updated销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
Vue2.x 组件通信有哪些方式
- 父子组件通信
- 父->子props,
- 子->父 $on、$emit
- 获取父子组件实例 $parent、$children
- Ref 获取实例的方式调用组件的属性或者方法
- Provide、inject 官方不推荐使用,但是写组件库时很常用
- 兄弟组件通信
- Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue
- Vuex
- 跨级组件通信
- Vuex
- $attrs、$listeners
- Provide、inject
SSR
node + vue-server-renderer 实现vue项目的服务端渲染
vuessr原理
两个入口文件 Server entry 和 Client entry, 分别经 webapck 打包成服务端用的 Server Bundle 和客户端用的用的Client Bundle。
服务端:当Node Server 收到来自客户端的请求后,BundleRenderer会读取Server Bundle,并且执行它,而Server Bundle 实现了数据预取并将填充数据的Vue实例挂载在HTML模版上,接下来BundleRenderer将HTML渲染为字符串,
客户端:浏览器收到html后,客户端加载了Client Bundle,通过 app.$mount(‘#app’) 的方式将 Vue实例挂载到服务端返回的静态html上。
<div id="app" data-server-rendered="true">
data-server-rendered 特殊属性,让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的,并且应该以激活模式(Hydration:https://ssr.vuejs.org/zh/hydration.html)进行挂载。
vuessr项目改造
webapck.base.js
// build/webpack.base.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const resolve = dir => path.resolve(__dirname, dir)
module.exports = {
output: {
filename: '[name].bundle.js',
path: resolve('../dist')
},
// 扩展名
resolve: {
extensions: ['.js', '.vue', '.css', '.jsx']
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
// .....
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
},
{
test: /\.vue$/,
use: 'vue-loader'
},
]
},
plugins: [
new VueLoaderPlugin(),
]
}
webpack.client.js
// build/webpack.client.js
const webpack = require('webpack')
const {merge} = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
const base = require('./webpack.base')
const isProd = process.env.NODE_ENV === 'production'
module.exports = merge(base, {
entry: {
client: resolve('../src/entry-client.js')
},
plugins: [
new VueSSRClientPlugin(),
new HtmlWebpackPlugin({
filename: 'index.csr.html',
template: resolve('../public/index.csr.html')
})
]
})
webpack.server.js
// build/webpack.server.js
const {merge} = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
const base = require('./webpack.base')
module.exports = merge(base, {
entry: {
server: resolve('../src/entry-server.js')
},
target:'node',
output:{
libraryTarget:'commonjs2'
},
plugins: [
new VueSSRServerPlugin(),
new HtmlWebpackPlugin({
filename: 'index.ssr.html',
template: resolve('../public/index.ssr.html'),
minify: false,
excludeChunks: ['server']
})
]
})
server.js 部分代码
const app = express()
const ServerBundle = require('./dist/vue-ssr-server-bundle.json')
const VueServerRender = require('vue-server-renderer')
const bundle = require('./dist/vue-ssr-server-bundle.json')
// 引入由 vue-server-renderer/client-plugin 生成的客户端构建 manifest 对象。此对象包含了 webpack 整个构建过程的信息,从而可以让 bundle renderer 自动推导需要在 HTML 模板中注入的内容。
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
// 分别读取构建好的ssr和csr的模版文件
const ssrTemplate = fs.readFileSync(resolve('./dist/index.ssr.html'), 'utf-8')
const csrTemplate = fs.readFileSync(resolve('./dist/index.csr.html'), 'utf-8')
// 调用vue-server-renderer的createBundleRenderer方法创建渲染器,并设置HTML模板,之后将服务端预取的数据填充至模板中
function createRenderer (bundle, options) {
return VueServerRender.createBundleRenderer(ServerBundle, Object.assign(options, {
template: ssrTemplate,
basedir: resolve('./dist'),
runInNewContext: false
}))
}
// vue-server-renderer创建bundle渲染器并绑定server bundle
let renderer = createRenderer(bundle, {
clientManifest
})
// 相关中间件 压缩响应文件 处理静态资源等
app.use(...)
// 设置缓存时间
const microCache = LRU({
maxAge: 1000 * 60 * 1
})
function render (req, res) {
const s = Date.now()
res.setHeader('Content-Type', 'text/html')
// 缓存命中相关代码,略...
// 设置请求的url
const context = {
title: '',
url: req.url,
}
if(/**与需要降级为ssr的相关 url参数、err异常错误、获取全局配置文件...条件*/){
res.end(csrTemplate)
return
}
// 将Vue实例渲染为字符串,传入上下文对象。
renderer.renderToString(context, (err, html) => {
if (err) {
// 偶发性错误避免抛500错误 可以降级为csr的html文件
//打日志操作.....
res.end(csrTemplate)
return
}
res.end(html)
})
}
// 启动一个服务并监听8080端口
app.get('*', render)
const port = process.env.PORT || 8080
const server = http.createServer(app)
server.listen(port, () => {
console.log(`server started at localhost:${port}`)
})
entry.server.js
import { createApp } from './app'
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
const { url, req } = context
const fullPath = router.resolve(url).route.fullPath
if (fullPath !== url) {
return reject({ url: fullPath })
}
// 切换路由到请求的url
router.push(url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
reject({ code: 404 })
}
// 执行匹配组件中的asyncData
Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
store,
route: router.currentRoute,
req
}))).then(() => {
context.state = store.state
if (router.currentRoute.meta) {
context.title = router.currentRoute.meta.title
}
resolve(app)
}).catch(reject)
}, reject)
})
}
entry-client.js
import 'es6-promise/auto'
import { createApp } from './app'
const { app, router, store } = createApp()
// 由于服务端渲染时,context.state 作为 window.__INITIAL_STATE__ 状态,自动嵌入到最终的 HTML 中。在客户端,在挂载到应用程序之前,state为window.__INITIAL_STATE__。
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const prevMatched = router.getMatchedComponents(from)
let diffed = false
const activated = matched.filter((c, i) => {
return diffed || (diffed = prevMatched[i] !== c)
})
const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
if (!asyncDataHooks.length) {
return next()
}
Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))
.then(() => {
next()
})
.catch(next)
})
// 挂载在DOM上
app.$mount('#app')
})
createApp
import Vue from 'vue'
import App from './App.vue'
import createRouter from './router'
import createStore from './store'
// 箭头函数是默认返回一个 =>后面的
// 如果是服务端渲染,每个人都应该有一个自己的vue实例
export default ()=>{
const router = createRouter()
const store = createStore()
const app = new Vue({
router,
store,
render:h=>h(App)
})
return {app,router,store}
}
createRouter.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from './components/Foo.vue'
import Bar from './components/Bar.vue'
Vue.use(VueRouter)
export default () => {
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: Foo
},
{
path: '/bar',
// component:Bar
component: () => import('./components/Bar.vue')
}
]
})
return router
}
createStore.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default (context)=>{
const store = new Vuex.Store({
state:{
name:''
},
mutations:{
changeName(state){
state.name = 'justdoit'
}
},
actions:{
changeName({commit}){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
commit('changeName')
resolve()
},1000)
})
}
}
})
// 如果浏览器执行的时候,我需要将服务器设置的最新状态 替换掉客户端的状态
//if(typeof window !=='undefined' && window.__INITIAL_STATE__){
// store.replaceState(window.__INITIAL_STATE__)
// }
return store
}
做过哪些Vue的性能优化
- v-if 和 v-show 区分使用场景
- computed 和watche区分使用场景
- v-for遍历必须为item添加key,且避免同时使用v-if v-for 比 v-if优先级高
- 事件销毁
- 图片懒加载
- 路由懒加载
- 第三方插件按需引入
- ssr
- 优化无限列表的性能
-
Vue-Router 导航守卫
登录权限验证。
vue-router 有三种路由导航 全局路由导航 ``` // main.js 入口文件 import router from ‘./router’; // 引入路由 router.beforeEach((to, from, next) => { next(); }); router.beforeResolve((to, from, next) => { next(); }); router.afterEach((to, from) => { console.log(‘afterEach 全局后置钩子’); });
<a name="NhWE2"></a>
#### to,from,next 这三个参数:
to和from是**将要进入和将要离开的路由对象**,路由对象指的是平时通过this.$route获取到的路由对象。<br />**next:Function** 这个参数是个函数,且**必须调用,否则不能进入路由**(页面空白)。
- next() 进入该路由。
- next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
- next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
2. 单个路由独享
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
})
3. 组件路由导航
beforeRouteEnter (to, from, next) {
// 在路由独享守卫后调用 不!能!获取组件实例 this
,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 this
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,可以访问组件实例 this
}
<a name="ZpBVz"></a>
## 触发钩子的完整顺序
将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:
1. beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
2. beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
3. beforeEnter: 路由独享守卫
4. beforeRouteEnter: 路由组件的组件进入路由前钩子。
5. beforeResolve:[路由全局解析守卫](https://link.juejin.cn?target=https%3A%2F%2Frouter.vuejs.org%2Fzh%2Fguide%2Fadvanced%2Fnavigation-guards.html%23%25E5%2585%25A8%25E5%25B1%2580%25E8%25A7%25A3%25E6%259E%2590%25E5%25AE%2588%25E5%258D%25AB)
6. afterEach:路由全局后置钩子
7. beforeCreate:组件生命周期,不能访问this。
8. created:组件生命周期,可以访问this,不能访问dom。
9. beforeMount:组件生命周期
10. deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
11. mounted:访问/操作dom。
12. activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
13. 执行beforeRouteEnter回调函数next。
<a name="HzNLx"></a>
## Vue2.x 和 Vue3.x 区别
<a name="OM0lS"></a>
### 速度更快
- 重写了虚拟Dom实现
- 编译模板的优化
- 更高效的组件初始化
- undate性能提高1.3~2倍
- SSR速度提高了2~3倍
<a name="vxjLq"></a>
### 体积更小
通过webpack的tree-shaking功能,可以将无用模块“剪辑”,仅打包需要的<br />能够tree-shaking,有两大好处:
- 对开发人员,能够对vue实现更多其他的功能,而不必担忧整体体积过大
- 对使用者,打包出来的包体积变小了
vue可以开发出更多其他的功能,而不必担忧vue打包出来的整体体积过多
<a name="cxGMr"></a>
### 更易维护
<a name="wOMWS"></a>
#### compositon Api
- 可与现有的Options API一起使用
- 灵活的逻辑组合与复用
- Vue3模块可以和其他框架搭配使用
<a name="mKplL"></a>
### 更好的Ts支持
<a name="KcIxV"></a>
### 编译器重写
<a name="WaRZZ"></a>
### 更接近原生
可以自定义渲染api
<a name="vSbPS"></a>
### 更易使用
响应式 Api 暴露出来
<a name="YyIsH"></a>
## Vue 和 React 选型考虑
![image.png](https://cdn.nlark.com/yuque/0/2022/png/756869/1653815081904-f9180b78-f8c0-4bc4-a8f0-4be71b6b7c56.png#clientId=ue5317528-b45c-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ua8d0d170&margin=%5Bobject%20Object%5D&name=image.png&originHeight=299&originWidth=557&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31369&status=done&style=none&taskId=u616be060-60f5-4791-bd1f-80f80a835a9&title=)
<a name="Xu34B"></a>
## with语法
const obj = { a:100,b:200};
// 使用 with 能改变 {} 内自由变量的查找方式 // 将 {} 内自由变量,当做 obj 的属性来查找
with(obj){ console.log(a); console.log(b); console.log(c); // 会报错 } ```
组件 渲染/更新过程
- 响应式 对象 数组 嵌套 监听属性 getter setter
- 模版编译 模版编译 render 函数 再到 vnode
- vdom path(elem,vnode) patch(vnode,newVnode)
- 初次渲染过程
1. 解析模版为 render函数 (在开发环境已完成,vue-loader)
2. 触发响应式 监听data 属性
3. 执行render函数 生成 vnode patch (elem,vnode)
- 更新过程
1. 修改data 触发 setter (此前在geetter中已被监听)
2. 重新执行 render函数 生成 newVnode
3. patch(vnode,newVnode)
- 异步渲染
1. $nextTick
2. 汇总data修改 一次性更新视图
3. 减少dom操作次数 提高性能