对MVVM的理解

image.png

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()。

image.png

如何理解Vue中的模版编译原理

  • 将 template模版转换成 ast 语法树 parserHTML
  • 对静态语法做静态标记 markUp diff 来做优化的 静态节点跳过 diff操作。
  • 重新生成代码 codeGen

    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函数返回一个对象作为组件的数据源。这样保证多个组件间数据互不影响。

  1. function Vue() {}
  2. function Sub() { // 会将data存起来
  3. this.data = this.constructor.options.data;
  4. }
  5. Vue.extend = function(options) {
  6. Sub.options = options;
  7. return Sub;
  8. }
  9. let Child = Vue.extend({
  10. data: { name: 'zf' }
  11. });
  12. // 两个组件就是两个实例, 希望数据互不干扰
  13. let child1 = new Child();
  14. let child2 = new Child();
  15. console.log(child1.data.name);
  16. child1.data.name = 'jw';
  17. 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的操作)。
  • 虚拟DOM不依赖真实平台环境从而也可以实现跨平台。

    Vue中diff算法原理

    既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异

  • 响应式数据变化,Vue确实可以在数据发生变化时,响应式系统可以立刻得知。但是如果给每个属性都添加watcher用于更新的话,会产生大量的watcher从而降低性能。

  • 而且粒度过细也会导致更新不精准的问题,所以vue采用了组件级的watcher配合diff来检测差异。

    Vue中key的作用和原理

  • Vue在patch过程中通过key可以判断两个虚拟节点是否是相同节点。 (可以复用老节点)

  • 无key会导致更新的时候出问题
  • 尽量不要采用索引作为key

image.png

对Vue组件化的理解

  • 组件化开发能大幅提高应用开发效率、测试性、复用性等;
  • 常用的组件化技术:属性、自定义事件、插槽等
  • 降低更新范围,只重新渲染变化的组件
  • 组件的特点:高内聚、低耦合、单向数据流

    Vue的组件渲染流程

    image.png

    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算法大致相同,其核心是基于两个简单的假设:

  1. 两个相同的组件产生的类似的dom结构,不同的组件产生不同的dom结构。
  2. 同一层级的一组节点,它们可以通过唯一的id进行区分。

基于以上的两点假设 使的虚拟dom的diff算法的复杂度从 O(N^3) 降到 O(n)
image.png
当页面的数据发生变化时,diff算法只会比较同一层级的节点:

  • 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点
  • 如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。
  • 当某一层有很多的节点时,也就是列表节点时,diff算法更新过程默认情况也是遵循以上原则。

image.png
我们希望在 B C之间加一个F diff默认执行起来是这样的:
image.png
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

所以我们需要使用 key来做每个节点做一个唯一标识,diff算法就可以正确的识别此节点,
找到正确的位置插入新的节点。
image.png
所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

keep-alive

keep-alive 可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性 include / exclude 允许组件有条件的进行缓存
两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
keep-alive的中还运用了LRU(Least Recently Used)算法。
keep-alive 是 vue中的内置组件 使用render函数实现

  1. export default {
  2. name: 'keep-alive',
  3. abstract: true,
  4. props: {
  5. include: [String, RegExp, Array],
  6. exclude: [String, RegExp, Array],
  7. max: [String, Number]
  8. },
  9. created () {
  10. this.cache = Object.create(null)
  11. this.keys = []
  12. },
  13. destroyed () {
  14. for (const key in this.cache) {
  15. pruneCacheEntry(this.cache, key, this.keys)
  16. }
  17. },
  18. mounted () {
  19. this.$watch('include', val => {
  20. pruneCache(this, name => matches(val, name))
  21. })
  22. this.$watch('exclude', val => {
  23. pruneCache(this, name => !matches(val, name))
  24. })
  25. },
  26. render() {
  27. /* 获取默认插槽中的第一个组件节点 */
  28. const slot = this.$slots.default
  29. const vnode = getFirstComponentChild(slot)
  30. /* 获取该组件节点的componentOptions */
  31. const componentOptions = vnode && vnode.componentOptions
  32. if (componentOptions) {
  33. /* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
  34. const name = getComponentName(componentOptions)
  35. const { include, exclude } = this
  36. /* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
  37. if (
  38. (include && (!name || !matches(include, name))) ||
  39. // excluded
  40. (exclude && name && matches(exclude, name))
  41. ) {
  42. return vnode
  43. }
  44. const { cache, keys } = this
  45. /* 获取组件的key值 */
  46. const key = vnode.key == null
  47. // same constructor may get registered as different local components
  48. // so cid alone is not enough (#3269)
  49. ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
  50. : vnode.key
  51. /* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
  52. if (cache[key]) {
  53. vnode.componentInstance = cache[key].componentInstance
  54. // make current key freshest
  55. remove(keys, key)
  56. keys.push(key)
  57. }
  58. /* 如果没有命中缓存,则将其设置进缓存 */
  59. else {
  60. cache[key] = vnode
  61. keys.push(key)
  62. // prune oldest entry
  63. /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
  64. if (this.max && keys.length > parseInt(this.max)) {
  65. pruneCacheEntry(cache, keys[0], keys, this._vnode)
  66. }
  67. }
  68. vnode.data.keepAlive = true
  69. }
  70. return vnode || (slot && slot[0])
  71. }
  72. }

render 函数 做了哪些事:

  1. 获取默认插槽中的第一个组件节点
  2. 获取该组件节点的componentOptions
  3. 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag
  4. 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode
  5. 获取组件的 key值
  6. 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
  7. 如果没有命中缓存,则将其设置进缓存
  8. 如果配置了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

向子元素或子组件传递 attribute 和事件

SSR

node + vue-server-renderer 实现vue项目的服务端渲染

vuessr原理

image.png

两个入口文件 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上。

  1. <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

  1. // build/webpack.base.js
  2. const path = require('path')
  3. const VueLoaderPlugin = require('vue-loader/lib/plugin')
  4. const resolve = dir => path.resolve(__dirname, dir)
  5. module.exports = {
  6. output: {
  7. filename: '[name].bundle.js',
  8. path: resolve('../dist')
  9. },
  10. // 扩展名
  11. resolve: {
  12. extensions: ['.js', '.vue', '.css', '.jsx']
  13. },
  14. module: {
  15. rules: [
  16. {
  17. test: /\.css$/,
  18. use: ['vue-style-loader', 'css-loader']
  19. },
  20. // .....
  21. {
  22. test: /\.js$/,
  23. use: {
  24. loader: 'babel-loader',
  25. options: {
  26. presets: ['@babel/preset-env']
  27. }
  28. },
  29. exclude: /node_modules/
  30. },
  31. {
  32. test: /\.vue$/,
  33. use: 'vue-loader'
  34. },
  35. ]
  36. },
  37. plugins: [
  38. new VueLoaderPlugin(),
  39. ]
  40. }

webpack.client.js

  1. // build/webpack.client.js
  2. const webpack = require('webpack')
  3. const {merge} = require('webpack-merge');
  4. const HtmlWebpackPlugin = require('html-webpack-plugin')
  5. const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
  6. const path = require('path')
  7. const resolve = dir => path.resolve(__dirname, dir)
  8. const base = require('./webpack.base')
  9. const isProd = process.env.NODE_ENV === 'production'
  10. module.exports = merge(base, {
  11. entry: {
  12. client: resolve('../src/entry-client.js')
  13. },
  14. plugins: [
  15. new VueSSRClientPlugin(),
  16. new HtmlWebpackPlugin({
  17. filename: 'index.csr.html',
  18. template: resolve('../public/index.csr.html')
  19. })
  20. ]
  21. })

webpack.server.js

  1. // build/webpack.server.js
  2. const {merge} = require('webpack-merge');
  3. const HtmlWebpackPlugin = require('html-webpack-plugin')
  4. const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
  5. const path = require('path')
  6. const resolve = dir => path.resolve(__dirname, dir)
  7. const base = require('./webpack.base')
  8. module.exports = merge(base, {
  9. entry: {
  10. server: resolve('../src/entry-server.js')
  11. },
  12. target:'node',
  13. output:{
  14. libraryTarget:'commonjs2'
  15. },
  16. plugins: [
  17. new VueSSRServerPlugin(),
  18. new HtmlWebpackPlugin({
  19. filename: 'index.ssr.html',
  20. template: resolve('../public/index.ssr.html'),
  21. minify: false,
  22. excludeChunks: ['server']
  23. })
  24. ]
  25. })

server.js 部分代码

  1. const app = express()
  2. const ServerBundle = require('./dist/vue-ssr-server-bundle.json')
  3. const VueServerRender = require('vue-server-renderer')
  4. const bundle = require('./dist/vue-ssr-server-bundle.json')
  5. // 引入由 vue-server-renderer/client-plugin 生成的客户端构建 manifest 对象。此对象包含了 webpack 整个构建过程的信息,从而可以让 bundle renderer 自动推导需要在 HTML 模板中注入的内容。
  6. const clientManifest = require('./dist/vue-ssr-client-manifest.json')
  7. // 分别读取构建好的ssr和csr的模版文件
  8. const ssrTemplate = fs.readFileSync(resolve('./dist/index.ssr.html'), 'utf-8')
  9. const csrTemplate = fs.readFileSync(resolve('./dist/index.csr.html'), 'utf-8')
  10. // 调用vue-server-renderer的createBundleRenderer方法创建渲染器,并设置HTML模板,之后将服务端预取的数据填充至模板中
  11. function createRenderer (bundle, options) {
  12. return VueServerRender.createBundleRenderer(ServerBundle, Object.assign(options, {
  13. template: ssrTemplate,
  14. basedir: resolve('./dist'),
  15. runInNewContext: false
  16. }))
  17. }
  18. // vue-server-renderer创建bundle渲染器并绑定server bundle
  19. let renderer = createRenderer(bundle, {
  20. clientManifest
  21. })
  22. // 相关中间件 压缩响应文件 处理静态资源等
  23. app.use(...)
  24. // 设置缓存时间
  25. const microCache = LRU({
  26. maxAge: 1000 * 60 * 1
  27. })
  28. function render (req, res) {
  29. const s = Date.now()
  30. res.setHeader('Content-Type', 'text/html')
  31. // 缓存命中相关代码,略...
  32. // 设置请求的url
  33. const context = {
  34. title: '',
  35. url: req.url,
  36. }
  37. if(/**与需要降级为ssr的相关 url参数、err异常错误、获取全局配置文件...条件*/){
  38. res.end(csrTemplate)
  39. return
  40. }
  41. // 将Vue实例渲染为字符串,传入上下文对象。
  42. renderer.renderToString(context, (err, html) => {
  43. if (err) {
  44. // 偶发性错误避免抛500错误 可以降级为csr的html文件
  45. //打日志操作.....
  46. res.end(csrTemplate)
  47. return
  48. }
  49. res.end(html)
  50. })
  51. }
  52. // 启动一个服务并监听8080端口
  53. app.get('*', render)
  54. const port = process.env.PORT || 8080
  55. const server = http.createServer(app)
  56. server.listen(port, () => {
  57. console.log(`server started at localhost:${port}`)
  58. })

entry.server.js

  1. import { createApp } from './app'
  2. export default context => {
  3. return new Promise((resolve, reject) => {
  4. const { app, router, store } = createApp()
  5. const { url, req } = context
  6. const fullPath = router.resolve(url).route.fullPath
  7. if (fullPath !== url) {
  8. return reject({ url: fullPath })
  9. }
  10. // 切换路由到请求的url
  11. router.push(url)
  12. router.onReady(() => {
  13. const matchedComponents = router.getMatchedComponents()
  14. if (!matchedComponents.length) {
  15. reject({ code: 404 })
  16. }
  17. // 执行匹配组件中的asyncData
  18. Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
  19. store,
  20. route: router.currentRoute,
  21. req
  22. }))).then(() => {
  23. context.state = store.state
  24. if (router.currentRoute.meta) {
  25. context.title = router.currentRoute.meta.title
  26. }
  27. resolve(app)
  28. }).catch(reject)
  29. }, reject)
  30. })
  31. }

entry-client.js

  1. import 'es6-promise/auto'
  2. import { createApp } from './app'
  3. const { app, router, store } = createApp()
  4. // 由于服务端渲染时,context.state 作为 window.__INITIAL_STATE__ 状态,自动嵌入到最终的 HTML 中。在客户端,在挂载到应用程序之前,state为window.__INITIAL_STATE__。
  5. if (window.__INITIAL_STATE__) {
  6. store.replaceState(window.__INITIAL_STATE__)
  7. }
  8. router.onReady(() => {
  9. router.beforeResolve((to, from, next) => {
  10. const matched = router.getMatchedComponents(to)
  11. const prevMatched = router.getMatchedComponents(from)
  12. let diffed = false
  13. const activated = matched.filter((c, i) => {
  14. return diffed || (diffed = prevMatched[i] !== c)
  15. })
  16. const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
  17. if (!asyncDataHooks.length) {
  18. return next()
  19. }
  20. Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))
  21. .then(() => {
  22. next()
  23. })
  24. .catch(next)
  25. })
  26. // 挂载在DOM上
  27. app.$mount('#app')
  28. })

createApp

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. import createRouter from './router'
  4. import createStore from './store'
  5. // 箭头函数是默认返回一个 =>后面的
  6. // 如果是服务端渲染,每个人都应该有一个自己的vue实例
  7. export default ()=>{
  8. const router = createRouter()
  9. const store = createStore()
  10. const app = new Vue({
  11. router,
  12. store,
  13. render:h=>h(App)
  14. })
  15. return {app,router,store}
  16. }

createRouter.js

  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router'
  3. import Foo from './components/Foo.vue'
  4. import Bar from './components/Bar.vue'
  5. Vue.use(VueRouter)
  6. export default () => {
  7. const router = new VueRouter({
  8. mode: 'history',
  9. routes: [
  10. {
  11. path: '/',
  12. component: Foo
  13. },
  14. {
  15. path: '/bar',
  16. // component:Bar
  17. component: () => import('./components/Bar.vue')
  18. }
  19. ]
  20. })
  21. return router
  22. }

createStore.js

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. export default (context)=>{
  5. const store = new Vuex.Store({
  6. state:{
  7. name:''
  8. },
  9. mutations:{
  10. changeName(state){
  11. state.name = 'justdoit'
  12. }
  13. },
  14. actions:{
  15. changeName({commit}){
  16. return new Promise((resolve,reject)=>{
  17. setTimeout(()=>{
  18. commit('changeName')
  19. resolve()
  20. },1000)
  21. })
  22. }
  23. }
  24. })
  25. // 如果浏览器执行的时候,我需要将服务器设置的最新状态 替换掉客户端的状态
  26. //if(typeof window !=='undefined' && window.__INITIAL_STATE__){
  27. // store.replaceState(window.__INITIAL_STATE__)
  28. // }
  29. return store
  30. }

做过哪些Vue的性能优化

  1. v-if 和 v-show 区分使用场景
  2. computed 和watche区分使用场景
  3. v-for遍历必须为item添加key,且避免同时使用v-if v-for 比 v-if优先级高
  4. 事件销毁
  5. 图片懒加载
  6. 路由懒加载
  7. 第三方插件按需引入
  8. ssr
  9. 优化无限列表的性能
  10. data 层级不能嵌套太深。

    Vue-Router 导航守卫

    登录权限验证。
    vue-router 有三种路由导航

  11. 全局路由导航 ``` // 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 全局后置钩子’); });

  1. <a name="NhWE2"></a>
  2. #### to,from,next 这三个参数:
  3. to和from是**将要进入和将要离开的路由对象**,路由对象指的是平时通过this.$route获取到的路由对象。<br />**next:Function** 这个参数是个函数,且**必须调用,否则不能进入路由**(页面空白)。
  4. - next() 进入该路由。
  5. - next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
  6. - next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
  7. 2. 单个路由独享
  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: '/foo',
  5. component: Foo,
  6. beforeEnter: (to, from, next) => {
  7. // 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
  8. // ...
  9. }
  10. }
  11. ]
  12. })
  1. 3. 组件路由导航

beforeRouteEnter (to, from, next) { // 在路由独享守卫后调用 不!能!获取组件实例 this,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 this // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用,可以访问组件实例 this }

  1. <a name="ZpBVz"></a>
  2. ## 触发钩子的完整顺序
  3. 将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:
  4. 1. beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
  5. 2. beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
  6. 3. beforeEnter: 路由独享守卫
  7. 4. beforeRouteEnter: 路由组件的组件进入路由前钩子。
  8. 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)
  9. 6. afterEach:路由全局后置钩子
  10. 7. beforeCreate:组件生命周期,不能访问this。
  11. 8. created:组件生命周期,可以访问this,不能访问dom。
  12. 9. beforeMount:组件生命周期
  13. 10. deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
  14. 11. mounted:访问/操作dom。
  15. 12. activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
  16. 13. 执行beforeRouteEnter回调函数next。
  17. <a name="HzNLx"></a>
  18. ## Vue2.x 和 Vue3.x 区别
  19. <a name="OM0lS"></a>
  20. ### 速度更快
  21. - 重写了虚拟Dom实现
  22. - 编译模板的优化
  23. - 更高效的组件初始化
  24. - undate性能提高1.3~2倍
  25. - SSR速度提高了2~3倍
  26. <a name="vxjLq"></a>
  27. ### 体积更小
  28. 通过webpack的tree-shaking功能,可以将无用模块“剪辑”,仅打包需要的<br />能够tree-shaking,有两大好处:
  29. - 对开发人员,能够对vue实现更多其他的功能,而不必担忧整体体积过大
  30. - 对使用者,打包出来的包体积变小了
  31. vue可以开发出更多其他的功能,而不必担忧vue打包出来的整体体积过多
  32. <a name="cxGMr"></a>
  33. ### 更易维护
  34. <a name="wOMWS"></a>
  35. #### compositon Api
  36. - 可与现有的Options API一起使用
  37. - 灵活的逻辑组合与复用
  38. - Vue3模块可以和其他框架搭配使用
  39. <a name="mKplL"></a>
  40. ### 更好的Ts支持
  41. <a name="KcIxV"></a>
  42. ### 编译器重写
  43. <a name="WaRZZ"></a>
  44. ### 更接近原生
  45. 可以自定义渲染api
  46. <a name="vSbPS"></a>
  47. ### 更易使用
  48. 响应式 Api 暴露出来
  49. <a name="YyIsH"></a>
  50. ## Vue 和 React 选型考虑
  51. ![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=)
  52. <a name="Xu34B"></a>
  53. ## 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. 1. 解析模版为 render函数 (在开发环境已完成,vue-loader
    2. 2. 触发响应式 监听data 属性
    3. 3. 执行render函数 生成 vnode patch (elem,vnode)
  • 更新过程
    1. 1. 修改data 触发 setter (此前在geetter中已被监听)
    2. 2. 重新执行 render函数 生成 newVnode
    3. 3. patch(vnode,newVnode)
  • 异步渲染
    1. 1. $nextTick
    2. 2. 汇总data修改 一次性更新视图
    3. 3. 减少dom操作次数 提高性能

image.png