- 对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.defaultconst vnode = getFirstComponentChild(slot)/* 获取该组件节点的componentOptions */const componentOptions = vnode && vnode.componentOptionsif (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 freshestremove(keys, key)keys.push(key)}/* 如果没有命中缓存,则将其设置进缓存 */else {cache[key] = vnodekeys.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.jsconst 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.jsconst 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.jsconst {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 bundlelet 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')// 缓存命中相关代码,略...// 设置请求的urlconst 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 || 8080const 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 } = contextconst fullPath = router.resolve(url).route.fullPathif (fullPath !== url) {return reject({ url: fullPath })}// 切换路由到请求的urlrouter.push(url)router.onReady(() => {const matchedComponents = router.getMatchedComponents()if (!matchedComponents.length) {reject({ code: 404 })}// 执行匹配组件中的asyncDataPromise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({store,route: router.currentRoute,req}))).then(() => {context.state = store.stateif (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 = falseconst 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:Barcomponent: () => 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 选型考虑<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函数 生成 newVnode3. patch(vnode,newVnode)
- 异步渲染
1. $nextTick2. 汇总data修改 一次性更新视图3. 减少dom操作次数 提高性能

