2023前端面试基础知识点-VUE篇(一)

vue2

1. v-if/v-show的区别?

  • 效果: 控制元素显示和隐藏的

区别:

  • v-if
    • 原理: 移除或添加dom元素
    • 优点: 懒渲染, 默认首次如果是false,元素不会创建, 如果是组件,可以重新触发生命周期
    • 缺点: 频繁的删除重建
    • 使用场景: dom首次判断是否显示还是隐藏,或者组件需要重新触发生命周期
  • v-show:
    • 原理:通过css样式 display:none/block
    • 优点: 不会频繁创建dom
    • 缺点: 首次渲染 false 但是也会创建
    • 使用场景: 频繁的显示和隐藏使用

2. $route和 router作用?

$route: 获取当前路由信息 params query path

$router: 和new VueRouter完全是同一个对象,全局路由实例 可以使用路由方法进行前进后退跳转等

路由编程式导航都是通过this.$router实现的

3. 聊聊vuex?

vuex的作用? vuex的5个属性? vuex的优缺点? vuex的流程?  vuex中的命名空间 ?如何触发mutation、action?

  • vuex是什么?
    vuex是基于vue状态管理的库
  • vuex的作用和优点
    实现组件数据共享,同时vuex的数据也是响应式,vuex数据操作远比组件内数据传递方便,方便维护和管理
  • vuex的5个属性
    state: 存储或定义共享数据,如果像访问数据,$store\mapState
    mutations: 修改vuex数据的唯一来源,为了方便后期的调试和维护,当然也可以直接修改state数据,但是很不建议, commit提交mutaiotn
    actions: mutations是用来处理同步的,devtools不准确,actions处理异步,dispatch,但是actions只用来处理异步,如果想修改数据,context中的commit提交mutations,
    getters: 基于state进行派生数据
    moudles: 将vuex数据进行模块化,方便维护,划分模块,每个模块中都拥有state/actions/mutaions/getters,每个模块可以设置命名空间,如果不设置,实际mutations或actions中的方法和全局的并无区别,开启命名空间后,必须通过模块名去访问
    说一下两个流程
    同步流程: commit > mutations > state
    异步流程: dispatch > actions > mutations > state
    开发: 统一异步流程
  • vuex中的缺点\解决方案
    实际vuex也是有缺陷的,不能持久化,想解决该问题/利用本地存储,或者利用插件实现自动存储vuex-persistedstate
  • vuex-persistedstate:
  • 默认还是localstorage
  • cookie
  • sesstion
  • 应用场景
    在开发项目的时候,一般用户信息或者token/或者网页的配置数据存入vuex

4. vue.use作用?

Vue.use({install(){}}, 1,2)

  • 参数1: 可以传一个函数或者对象,如果是对象,对象中需要一个install函数,如果是一个函数,相当于是install函数
  • 其他参数: 依次传给了install函数

install函数默认第一个参数是Vue,后面的参数来自于Vue.use后续参数

Vue.use本质是无法注册全局组件或者给Vue原型添加方法,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过Vue.component注册了全局组件或者给Vue.prototype手动添加方法

5. $nextTick有什么用?

作用

问题: vue中数据发生变化同步视图是异步的,所以数据发生变化不能立即获取最新的视图, 想获取最新的视图通过this.$nextTick

原理

this.$nextTick函数和vue源码中的nextTick函数,是同一个函数,源码中调用nextTick函数为了异步更新视图,我们在使用this.$nextTick函数的时候回调会在源码的nextTick函数之后执行,所以获取到最新的视图,

源码的nextTick函数异步原理利用的就是向下兼容可宏可微,源码中依次进行判断Promise/mutationobserver/setImmdiate/setTimeout

6. 生命周期

组件

本质:就是函数,会在特定的阶段自动调用,生命周期函数

作用:可以让我们在某个阶段做一些事情

4个阶段

阶段1: 创建阶段

  • beforeCreate: 开始创建实例,此时实例的数据和方法还没有
  • created:
    • 作用:实例已经创建完成,数据和方法都已存在
    • 应用场景: 发送请求获取数据, 页面进入的方法需要立即执行
    • 扩展: 如果非要在created中操作dom也可以,利用$nextTick

阶段2: 挂载阶段(dom)

  • beforeMount: 开始挂载dom,真正的dom元素还没有挂载完成, 操作dom不可以
  • mounted:
    • dom已经挂载完成,可以操作真实dom
    • 应用场景: 页面已进入操作dom

阶段3: 更新阶段

  • beforeUpdate: 数据变了,但是视图还没变
  • updated: 数据和视图都变了

阶段4: 销毁阶段

  • beforeDestory: 即将销毁
  • destoryed: 组件销毁
    • 应用场景: 清除挂载在window相关的行为,例如定义器\事件
父子

创建挂载阶段

父beforeCreated > 父created > 父 beforeMounted > 子beforeCreate > 子created > 子beforeMount > 子>mounted > 父mounted

更新阶段

如果更新的数据不涉及到子组件,只会父组件更新 父beforeUpdate > 父updated

如果更新的数据涉及到子组件, 父beforeUpdate  > 子beforeUpdate > 子updated > 父updated

销毁阶段

父beforeDestory  > 子beforeDestory  > 子destoryed> 父destoryed

7. 插槽

默认插槽:

直接在组件标签中间进行传递

组件内部通过vue内置的slot组件进行接收

如果组件想给组件内部不同的区域进行自定义,需要使用具名插槽

多插槽: 具名插槽

直接在组件标签中间进行传递,但是需要通过slot或#指定是哪一个具名插槽

<sg-button> 
        <span slot="icon">🎨</span> 
</sg-button>

组件内部: 通过多slot进行接收,slot通过name进行区分

<div @click="$emit('click')" :class="[type, size]" class="btn"> 
        <slot name="icon" />
        <!-- 组件内部通过slot向外传递数据 -->
<slot name="text" /> </div>

不管是普通插槽还是具名插槽都无法在父组件的插槽结构中获取子组件的数据,所以还需要使用作用域插槽

作用域插槽

直接在组件内部进行传递,但是同时可以通过v-slot="scope" 获取组件内部通过slot组件传递的数据

<sg-button> 
        <!-- v-slot 可以获取到对应的slot上所有属性 -->
        <template v-slot:text="scope"> 
        <span>注册</span> 
        {{ scope.age }}
        {{ scope.data }} 
        </template> 
        <span slot="icon">🎨</span> 
</sg-button>

组件内部通过slot组件属性向外传递数据,冒号传递的是变量,否则传递的就是字符串

<div @click="$emit('click')" :class="[type, size]" class="btn"> 
        <slot name="icon" /> 
        <!-- 组件内部通过slot向外传递数据 --> <slot age="19" :data="count"         name="text" /> 
</div>

应用场景:  element ui很多组件,例如树形\表格

8. v-model

作用: 数据双向绑定(mode >> view),进行组件通信

原理: v-model就一个语法糖, 动态绑定了value和注册了input事件

使用场景:

在表单中使用

在组件上使用, 既要将数据传给子组件,子组件还要修改数据的时候,此时可以使用v-model进行简化

解析规则:

@input="color = $event" :value="color"

修改默认解析,在子组件内部

model: {
        prop: 'color',
        event: 'setColor'
        },

v-model有一个缺点: 一个组件上只能使用一次

9 .sync修饰符

作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)

原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发

.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件

<text-document v-bind.sync="doc"></text-document> 这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

10. vue组件通信(传值)

  • 父传子: 父组件属性方式传递,子组件(props)
  • 子传父: 子组件通过$emit,父组件通过自定义事件
  • eventbus: // 手写发布订阅模式
class EventBus { 
        // 记录事件和回调 
        clientList = { 
            send: [() => {}, () => {}], 
        } 
        // 订阅事件,参数event事件名,callback 回调 
        $on = function (event, callback) { 
            // 将事件和函数记录 
            // 如果事件记录过,那就将回调push 
            if (this.clientList[event]) { 
                this.clientList[event].push(callback) 
            } else { 
                this.clientList[event] = [callback] } 
            } 
        $emit = function (event, val) {
            if (!this.clientList[event]) {
                 throw new Error(event + ' is not a event') }             this.clientList[event].forEach((cb) => {
                 cb(val) 
            }) 
          } 
        } 
        const eventBus = new EventBus() 
        // 订阅事件 
        eventBus.$on('send', (val) => {
            console.log('send订阅' + val) 
        }) 
        eventBus.$on('send', (val) => { 
            console.log('send订阅' + val) 
        }) 
eventBus.$emit('send', 1)
  • 本质: vue实例对象
    • 实现原理: 利用发布订阅模式
    • 传递数据的组件,通过eventBus的$emit发布自定义事件,并传递数据
    • 获取数据的组件,通过eventBus的$on订阅自定义事件,并通过回调函数接收数据
    • 设计模式:发布订阅设计模式: 一对多的依赖关系, 发布方是一,依赖方是多,发布方发布消息,所有的依赖(进行订阅了)方收到通知
  • vuex
  • ref获取子组件
  • v-model
  • .sync
  • $children: 可以获取当前组件的所有子组件,并以数组的格式返回数据
  • $parent: 可以获取到当前组件的父组件, 返回当前组件的父组件
  • provide/inject: 跨组件传值provide,进行给后代组件提供值,inject在后代组件上进行注入值
  • $attrs: 获取到当前组件节点上的所有属性集合
  • 数据提升将数据定义到父组件,一个组件通过子向父传值,父组件在传递给另一个组件

11-虚拟dom+++

什么是虚拟dom?

虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom

为什么出现?

虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)

如何实现高效更新?

初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法

diff算法?

同级比较.深度优先,而且采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升

diff对比之后的情况?

元素不同: 删除重建

元素相同,属性不同: 元素复用,属性更新

v-for:

无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能

无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响

有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比

12-mixins

mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可

可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中

优先级:

  • 生命周期,组件和混入的都会调用(混入的先调用
  • data/computed数据: 进行合并,冲突以组件为主,mixins被覆盖
  • methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对

13-路由模式的区别++

  1. abstract支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
  • 是否有#/

hash有

history: 没有

  • 是否经过服务器

hash: 不会

history: 会

  • 是否需要后端配合

hash: 不需要

history: 需要

  • 底层原理

hash: 跳转 window.location.href, 监听 onhashchange

history: 跳转history API, history.pushState和history.repleaceState 监听 onpopState(前进/后退)

封装的方法: pushState(history.pushState/history.repleaceState)

14. 用过哪些修饰符

修饰符: 在指令后面通过.语法使用的

  • trim
  • once
  • number
  • lazy
  • native
  • sync
  • .enter

15. vue优缺点

优点:

  • 简单易用
  • 渐进式
  • 响应式
  • 双向数据绑定
  • 虚拟dom
  • 耦合低
  • 用户体验好
  • 结合vuerouter实现spa

缺点:

  • vue2: 逻辑分散
  • 无法兼容ie8一下,无法shim, 底层采用的Object.defineproperty,无法兼容ie8一下
  • seo很不友好

16. vue-i18n

概念:

实现国际化(多语言),项目可以进行语言的切换

使用:

  1. 装包
  2. 在单独的模块中进行封装/引入/注册/配置语言包
  3. 封装一个组件用来进行语言切换(this.$i18n.locale)
  4. element-ui: 直接导入element-ui的语言包,同时在use element ui时候并配置i18n

17. computed和watch的区别

computed:

  • 计算属性: 数据发生变化,重新进行计算从而得到新的数据
  • 逻辑计算的时候一般使用计算属性
  • 定义的时候是方法,使用的是属性
  • computed内部return依赖的数据发生变化,,逻辑会进行重新计算
  • computed实际只会计算1次,只要数据不变,不管使用多少次,后续的使用的是缓存
  • computed不能处理异步

watch

  • 进行监听,监听某一个数据如果发生变化,我们想进行逻辑的处理
  • watch可以监视:props\data\computed\$route
  • 当监听的数据发生变化,对应的函数会重新执行
  • 监视的是一个对象: 开启深度监听 deep:true
  • watch内部是可以处理异步的

项目应用:

计算属性:

vuex的getter/state数据: 都映射到计算属性中使用

统计审批次数:入职/离职

收货地址: 后端返回的是多个数据

小程序: 订单金额,订单数量

watch:

父组件异步向子组件传递数据

路由变化但是组件复用了

封装的对话框组件:数据变化,需要将数据通过自定义事件传给父组件

18. watch原理+++

  1. vue内部通过initWatch进行初始化,循环watch对象,但是监视的值可以写成数组,所以进行两层循环
  2. 每一个监视的回调都会创建一个用户watcher,内部通过createWatcher创建
  3. watcher内部兼容了三种watcher, 渲染watcher/用户watcher/计算属性watcher
  4. 如果是渲染watcher,this.getter等于expOrFn,如果是用户watcher通过parsePath生成了用户的getter,判断expOrFn是否为函数,还是一个字符串
  5. 数据发生变化进入更新阶段,执行进入run函数,调用创建watcher传入的回调,这个回调实际就是watch监视的数据的回调
  6. 旧值在watcher初始化的时候调用的get函数中获取到
  7. 新值在run里执行的get函数中获取到,将新旧交替,将新旧值传给回调
  8. watch的使用还可以通过实例.$watch访问,所以源码内部再createWatcher内部并没有直接new Watcher,而是将创建watcher的逻辑封装Vue.prototype上,然后在createWatcher中调用vm.$watch创建用于watcher

19. computed原理+++

  1. 将计算属性的get和set转换为Object.defineProperty的get和set,并且计算属性如果写为简写的方式,函数直接交给了Object.defineProperty的get
  2. 循环计算属性给每一个属性创建计算属性 创建计算属性watcher
  3. 缓存原理:重写计算属性Object.defineProperty的get, 通过createComputedGetter进行生成,返回一个函数,在函数内部主要通过获取到计算属性对应的watcher的dirty,默认为true,计算过一次变为false,根据dirty判断是否重新计算(evaluate)重新计算,
  4. 依赖的数据发生变化,重新渲染页面,依赖的数据收集渲染watcher:
  • 通过stack记录多个watcher
  • 在计算属性get中判断是否还有watcher
  • 通过渲染watcher的depend方法内部循环deps,获取到每一个dep,计算属性依赖的每一个数据,每一个dep收集watcher,dep内部的depend方法

20. computed和watch执行顺序

初始化阶段computed会比watch先执行,原因就是在initState中先判断的是否有computed,初始化computed,再去判断是否有watch,如果有watch再去初始化watch

21. 单项数据流

数据是单项流动,数据可以从父组件流向子组件,父组件的数据发生变化,会自动同步子组件,反之不允许

22. v-if和v-for为什么避免同时使用

v2中:

v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,利用v-if进行移除

  • v-if写到外层
  • 先通过计算属性将数据计算好
     

v3中: v-if优先级高

23. vue 组件中的 data 为什么是一个函数,返回一个对象?

如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据

但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的

24. 简易数据响应式原理+++

  1. 概念: 数据发生变化,数据使用的地方会进行同步更新
  2. vue中数据响应式的核心Object.defineProperty可以对对象的进行定义属性,内部有两个方法get,set,get数据读取会触发,set,数据变化会触发
  3. vue劫持数据的时候必须要通过一个数据进行周转,出发get或者set直接用原数据,就会堆栈溢出
  4. Object.definePropert一次只能劫持一个数据,所以源码中需要对数据进行循环遍历劫持,类似于递归的方式对所有的数据进行劫持
  5. 对象新增的数据是无法劫持到的,因为新增的数据,需要劫持的数据已经被劫持了,对象新增的数据也就不是响应式的,vue中通过this.$set解决
  6. 数组的下标修改数据Object.defineProperty.可以劫持的到,vue因为性能和用户体验的关系,取消了vue中检测数组下标的变化
  7. 数组的7个方法是响应式的原因是因为vue对7个方法进行重写了,并不是完全重写,利用了切面编程,对方法进行封装和逻辑的处理,最终还是会调用原始的方法
  8. 观察者设计模式: 一对多的数据依赖关系,和发布订阅的区别,没有调度中心,被观察者和观察者直接通信, 观察者内部会有update方法进行消息更新,被观察者内部会有add收集观察者的方法,通知观察者的方法notify,通过notify通过所有的观察者,观察者通过update更新消息
  9. vue源码中: get中进行依赖收集,会给每个数据添加一个watcher观察者(每一个watcher对应的就是一个组件)
  10. set中: a数据发生变化通知观察者(组件)进行更新,vue中更新是以组件为单位

25. vue源码+++

数据响应式数据劫持

  1. data数据通过选项对象传递给Vue构造函数,Vue类接收数据,将数据传递给initMixin方法,方法内部通过_init接收数据,最终将数据initData,初始化data数据,
  2. 首先判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data进行数据劫持
  3. 将data数据传递到observe函数中,对data类型进行判断,将data传递到Observer类中的walk,walk起始函数
  4. 在walk函数中循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
  5. 处理通过this实例访问data选项数据,此时在initData内部通过proxy将vm进行劫持,当访问vm上的数据时,代理到_data数据等同于data,进入walk内部的数据劫持
  6. Observer中会给需要劫持的数据添加一个__ob__是否被劫持的标识

简易版本: 核心

data选项数据传入到initMixin(初始化所有), 内部将传入到initdata(初始化data数据), 获取data数据, 判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data数据传入到observe函数中,将数据传入,判断数据是否是对象, 如果是对象将数据传递给Observer类中的walk内部循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe

数组劫持:

Observer中进行判断数据类型是否为数组,如果是数组,走observeArray方法,对数组中的复杂数据类型进行劫持

7个数组方法重写: ['push', 'pop', 'shift', 'unshift', 'slice', 'reverse', 'sort']

AOP 切面编程实现, vue内部重写了这7个方法,但不是完全重写,自身的push unshift功能还是保留所以还会调用原来的方法,又对unshift push splice方法的增加的数据进行了劫持,其他的只做了通知notify更新通知操作

模板编译:

compileTofunction这个方法生成render函数,通过render函数生成虚拟dom,虚拟dom疏对比.利用patch方法进行dom的更新

通过parseHtml方法解析模板,解析模板的过程就是使用大量正则匹配,生成AST语法树,将AST语法树生成了render函数

数据响应式数据发生变化:模板更新:

创建了一个watcher,内部通过get调用了updateComponent这个方法: 编译更新整个组件内部会进行虚拟dom树的对比

收集watcher:

编译模板模板中用到哪些数据,给哪些数据收集watcher,通过dep进行watcher收集,每一个数据通过闭包的方式都有自己dep,通过deo收集watcher,所以每个数据都收集了自己的watcher, 数据劫持的get中收集watcher,拿不到watcher,通过Dep.target进行中转, watcher中的getter调用前将this存给Dep.target,然后在get中将Dep.target进行

通知:

数据发生变化触发set,获取到数据对应的dep,dep中通过subs存着watcher,dep中有一个notify方法循环收集所有的watcher,调用watcher的update方法进行组件更新 => get >getter > updatecomponent

vue数据响应式:

观察者模式:多对多

dep > watcher

watcher > dep

dep => depend > 调用watcher的addDep方法进行收集dep, 通过dep收集watcher通过addSub

异步更新:

quereWatcher watcher队列收集, 根据watcher的id进行将watcher存入quere对列中,调用watcher的get方法,不能同步调用,只会更新第一个数据的视图,保证所有数据更新完后在统一的更新视图,将watcher的get方法放到异步任务里

next原理:

this.$nextTick就是vue异步更新视图中封装的nextTick方法,利用异步更新视图,异步优先使用微任务,因为同步代码更新完成后进入微任务更快,优先使用Promise.resolve,如果Promise没有,使用MutationObserver也是微任务,如果MutationObserver也没有使用setImmediate是宏任务,他比setTimeout快,如果setImmediate也没有使用setTImeout

总结:

数据劫持 + 模板编译 + 观察者模式 + 异步更新视图nextTick

数据劫持:

数据传入到initMixin,将数据传入到initData,获取data数据,可能是函数/对象, 将数据传给observe函数中,数据判断是否为对象,以及是否被观测过__ob__,继续将数据传入到Observer中的walk函数,循环对象,对每一个对象通过defineReactive函数进行数据劫持

Observer中判断数据是否为数组,如果是数组observeArray对数组的复杂数据进行劫持,数组的7个方法通过AOP切面变成进行重写

模板编译:

通过compileTofunction生成render函数,内部通过大量的正则匹配生成AST语法树,将AST语法树生成render函数,通过render函数创建虚拟dom,将虚拟dom渲染成真实dom

观察者模式:

依赖收集,依赖通知

在数据劫持中get函数中进行watcher收集,因为watcher对应的一个组件的更新,通过dep进行收集,观察者模式多对多,dep收集watcher,watcher也会收集dep,数据发生变化在set中通过dep.notify进行依赖的watcher通知

异步更新视图:

通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout

26. vue 异步队列+++

异步更新视图:

通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout

27. 路由守卫/路由钩子

  1. 全局守卫

对所有路由全都生效,进行权限的处理,比如是否登录的路由权限控制

  • 路由前置守卫

beforeEach

  • 路由后置守卫

arterEach

  1. 路由独享守卫

针对某一条路由进行单向控制

beforeEnter

  1. 组件内守卫

针对组件

beforeRouteEnter: 只要通过路由,不管是哪一条路由显示该组件就会触发,控制改页面是否展示

beforeRouteUpdate: 路由组件被服用时,例如/a/1 进入a页面,在当前组件重新进入/a/2 显示a页面,a页面就会被复用, 组件没有销毁,组件不会重新走created不会执行,此时可以使用beforeRouteUpdate进行解决

beforeRouteLeave: 应用场景: 表单页面的返回,提示用户当前表单未提交是否离开,离开当前路由组件

28. 获取数据created和mounted

为什么有时候在created获取数据,为什么数据可视化大屏可能会在mounted中获取数据

如果涉及到dom的操作了,应该在mounted中

如果没有涉及到dom操作,在created中

29. 数据不响应的情况+++

  • 对象新增属性
  • 数组通过下标直接更改数据: arr[0] = 1 arr[0].name = '李四'
  • 对象删除

解决方案:

更新对象: this.$set(对象, 'key', value)

更新数组: this.$set(数组. 下标, value)

对象删除: this.$fourceupdate()

上述情况在后面进行了可以支持数据响应式的,上面的也会同步更新

30. spa的优缺点

1、单页面应用的优点

  • 良好的交互体验
    • 单页应用的内容的改变不需要重新加我整个页面,获取数据也是通过Ajx异步获取,没有页面之问的切换,就不会出现“白屏现象”,也不会出现限死并有“闪烁”现象,页面显示流畅
  • 良好的前后端工作分离模式
    • 后端不再负贡模板渲染、输出页面工作,后端AP川通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端
  • 喊轻服务器压力
    • 单页应用相对服务器压力小,服务器只用出数据就可以,不用管晨示罗担和页面合成,吞吐能力会提高几倍

2、缺点

  • ·首屏加载慢

解决方案:

1、vue-routert懒幼加载
      Vue-router懒勋加载就是按需加载组件,只有当路由被访问时才会加载对应的组件,而不是在加载首页的时候就加载,项目越大,对首屏加载的速度提升得越明显
2、使用CDN加速
      在做项目时,我们会用到很多库,采用cd加载可以加快加载速度。
3、月服务端渲染upengy
4、异步加载组件
      服务端渲染还能对so优化起到作用,有利于搜索引擎取更多有用的信息(如果页面纯前端渲
      染,搜索引擎抓取到的就只是空页面)

  • 不利于SEO

SEO本质是一个服务器向另一个服务器发起请求,解析请求内容。但一股来说搜索引擎是不会去执
行请求到的s的。也就是说,搜索引擎的基础吧虫的原理就是抓取ul,然后获取ht源代码并解
析。如果一个单页应用,ht在服务器端还没有渲染部分数据数据,在浏览器才渲染出数据,即
搜索引清求到的tm是模型页面而不是最终数据的渲染页面。这样就很不利于内容被搜索引擎
搜索到

解决方案:

1、服务端渲染
        服务器合成完整的html文件再输出到浏览器

2、页面预渲染
3、路由采用h5 history模式

        不适合开发大型项目

大型项目中可能会涉及大量的DOM操作、复杂的动画效果,也就不适合使用Vue、react框架进行
开发

31. 服务端渲染ssr

  • 为什么?

spa单页面应用的seo不友好,spa单页面,由js进行渲染页面,不同的路由规则渲染不同的组件,对应html就是一个空页面,百度爬虫在爬,什么都爬不到,导致seo不友好

  • ssr

服务端渲染: 先由服务端将页面解析好返回,返回之后,浏览器进行渲染,直接将页面的内容进行渲染,html不在是一个空页面,爬虫可以爬到

  • 配置

路由: 在pages下创建文件,自动生成路由

ui组件库: 在nuxt.config.js中通过plugins节点指定配置文件的路径,在对应的路径文件中进行配置

css全局样式: 可以在nuxt.config.js中的css节点中进行配置

seo优化: nuxt.config.js中通过head进行title和meta的配置

在pages中也可以通过head进行页面级别的配置

  • 获取数据

生命周期分为

服务端

nuxtServerInit: 服务端初始化

RouteMiddleware: 中间件

validate: ok

asyncData: 获取服务端数据

asyncData中进行获取数据,该生命周期是在服务端执行的,所以不能使用this

客户端

created => fetch(fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。) = mounted

32. vue中使用了哪些设计模式+++

  1. 发布订阅($on/$emit)
  2. 观察者模式(vue源码的依赖收集dep和watcher)
  3. 单例模式(router/vuex)只能vue.use一次,对应这些实例只能有一个

33.keep-alive

作用: 缓存不活动的组件

使用: keep-alive组件包裹路由占位

使用场景: 列表进详情需要把列表页缓存,将列表的滚动条的位置记录下来,当重新进入列表页的时候设置滚动条位置

按需缓存:

  • kepp-alive上的include或exclude匹配的是组件的name
  • 结合路由的meta,添加一个缓存标识,通过在keep-alive缓存的位置获取当前路由信息上的meta中的缓存标识进行控制是否显示keep-alive包裹router-view还是直接显示router-view

生命周期:

被缓存的组件激活: actived

被缓存的组件失活: deactived

vue3

1. v2和v3的区别

api

  • v2: options API(选项式API):
    • 优点:对于新手容易,按照固定选项实现功能
    • 缺点: 对于代码的阅读\维护\复用(逻辑)不是很友好,
    • v2中也可以通过Mixins实现逻辑的复用
  • v3: composition API(组合式API)
    • 优点: 功能代码组合在一起

使用

  • v2: 配合vuex,对与ts的支持不是很好,不允许使用多个根节点
  • v3: 推荐配合pina,底层就使用ts重构的,对ts支持很好,多个根节点

性能

  • v3的性能会比v2快1.2_2倍:
  • v3采用了静态标记,给静态节点打上标记,diff的过程只对比动态节点,忽略静态节点
  • 虚拟dom的创建:v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom,v3中数据发生变化,看该虚拟dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom
  • 事件函数的缓存: 事件函数每次在模板编译的时候会进行缓存
  • v3的tree shaking:实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的,v3的打包比v2的小

数据劫持

  • v2:Object.defineProperty
  • v3: Proxy

2. vite和webpack的区别

  • vite: 工程化工具
  • vite原理: 启动项目不会对项目进行完全打包,启动服务,将资源通过服务进行托管,项目加载而是通过浏览器利用esm(es module)的方式加载资源,使用到资源才会加载才会处理,这实际上是让浏览器接管了打包程序的部分工作。
  • webpack 真正的打包: 先读取配置文件,根据配置文件的规则进行打包,读打包的入口文件,根据入口文件的依赖进行打包,js转低级语法:babel,三方文件:loader,html:html-webpack-plugin,css文件的抽离:mini-css-extract-plugin,启动浏览器,浏览器加载资源,直接加载不会再次处理

3. vue3 如何定义响应式数据

  • ref: 将简单数据类型或复杂数据类型处理为响应式

             模板中使用自动解套value,在js中使用需要通过value访问到数据

  • reactive: 只能将复杂数据类型处理为响应式,直接用数据不需要value

推荐的使用方式:

  • 语法层面: 简单数据类型推荐使用ref, 复杂数据类型推荐使用reactive
  • 性能: 更推荐全部使用ref, ref的性能在vue3.2之后比reactive高260%

4. script setup语法

  1. 语法: 在script标签上定义setup属性
  2. 特性:
  • vue3.2版本更新
  • setup函数的语法糖,可以直接在script中定义数据和方法,默认可以直接在模板中使用
  • 不需要export default 对象

5. computed计算属性

  1. computed: 计算属性, 就是vue提供的一个函数.所以需要通过引入vue得到
  2. 语法: 调用computed函数, 传递一个回调函数,在回调函数内部进行计算,将计算好的数据return出
  3. 特性: 具有缓存, 多次调用多次定义, 除了可以传递函数,还可以传递对象, 包含get和set函数

6. watch监视

  1. 概念: 监听数据变化, 也是vue提供的一个函数, 所以需要通过引入vue得到
  2. 语法:
  • 参数1: 监视的数据
    • 监视单个数据: 直接写数据
    • 监视多个数据: 写数组,数组内放数据
    • 监视reactive对象中的某一个属性: 函数返回属性
  • 参数2: 处理函数, 两个参数
    • 参数1: 新值, 如果监视的是数组,参数1还是新值,以数组的方式体现
    • 参数2: 旧值, 如果监视的是数组,参数1还是新值,以数组的方式体现
  • 参数3: 配置对象
    • 深度监听: deep: true, 监视的是ref的对象需要开启深度监听,监视的是reactive定义的对象不需要开启深度监听
    • 首次监听: immediate: true, 开启首次监听

7. 生命周期函数

vue3中的生命周期函数来自vue,所以需要通过vue按需引入,生命周期函数可以多次调用

创建阶段

  • vue2: beforeCreate/created
  • vue3: setup

挂载阶段

  • vue2: beforeMount/mounted
  • vue3: onBeforeMount/onMounted

更新阶段

  • vue2: beforeUpdate/updated
  • vue3: onBeforeUpdate/onUpdated

销毁阶段

  • vue2: beforeDestory/destoryde
  • vue3: onBeforeUnMounte/onUnMounted

8. 组件通信

8.1 父传子

父组件通过属性绑定传递

<script setup lang='ts'>
    import { ref } from 'vue'
    import Child from './components/Child.vue'
    const money = ref(10000000)
</script> 
<template> 
    <h1> 这是App组件: {{ money }} </h1> 
    <div>------------------------</div> 
    <!-- 1. 父组件通过属性绑定去传 --> 
    <Child :money="money" /> 
    </template> 
<style></style>

子组件通过defineProps接收, 子组件接收的数据在模板中可以直接使用在js中,通过defineProps的返回值使用

<script setup lang='ts'> 
    const props = defineProps({
        money: { 
             type: Number, 
             required: true 
             } 
        }) 
</script> 
<template> 
     <h2>这是子组件: {{ money }}</h2> 
</template> 
<style></style>
8.2 子传父

子组件获取emit对象

const emit = defineEmits(['buy-car'])

通过emit触发自定义事件,并传递数据

const buyCar = () => { 
        // console.log('买车', 1000000) 
        // 2. 通过emit对象触发一个自定义事件, 并且 
        emit('buy-car', 1000000) 
}

父组件定义自定义事件和事件函数

<script setup lang='ts'>
        import { ref } from 'vue' 
        import Child from './components/Child.vue' 
        const money = ref(10000000) 
        // 4. 定义事件函数,内部修改money数据 
        const setMoney = (val) => { 
              money.value = money.value - val 
        } 
</script> 
<template> 
        <!-- 3. 定义自定义事件 --> 
        <Child @buy-car="setMoney" /> 
</template> 
<style></style>
8.3 跨组件向下传值
        1.父级组件通过provide提供数据
const money = ref(100000000) 
provide('money', money)
        2.后代组件通过inject注入数据
import { ref, inject } from 'vue' 
const money = inject('money')
8.4 跨组件向上传值
  1. 父组件通过provide传递函数
  2. 后代组件通过inject注入函数
  3. 调用函数将数据通过参数的方式传给父级组件
8.5 ref获取组件
  1. 创建空的ref
  2. 给组件绑定ref属性值是空的ref的名字
  3. 组件和空ref的value绑定在一起
  4. 利用ref.value获取子组件
<script setup lang='ts'> 
        import { ref } from 'vue' 
        import ComA from './components/ComA.vue' 
        // 1. 定义空ref 
        const comARef = ref(null) 
        const getComA = () => {
         // 3. 利用ref的value获取组件 
        console.log(comARef.value?.updateMoney) 
        } 
</script> 
<template> 
        <!-- 2. 空的ref的value绑定为组件 --> 
        <ComA ref="comARef" /> 
        <button @click="getComA">获取coma组件</button> 
</template> 
<style></style>

子组件通过defineExpose向外暴漏数据

<script setup lang='ts'> 
        import { ref } from 'vue' 
        const money = ref(1000) 
        const updateMoney = () => {
        } 
        defineExpose({ 
            money, 
            updateMoney 
        }) 
</script> 
<template> 
        <h2>这是comA组件</h2> 
</template> 
<style></style>

9. 过滤器

  1. v3废除了过滤器
  2. 想过滤数据: 定义函数

10. toRefs的使用

作用: 保证解构的数据还是响应式

原理: 将解构的数据变为ref处理

11. 路由

vue3的路由结合vue-router@4.x版本

  1. 创建路由实例, 通过createRouter进行创建,createRouter就是一个函数,来自于vue-router的按需引入
  2. 配置路由模式,通过createWebHashHistory(hash路由模式)、createWebHistory创建history路由模式
import { createRouter, createWebHashHistory as createRouterMode } from 'vue-router' 
        // 可以创建路由实例 
        // 通过参数对象进行配置 
const router = createRouter({ 
        // createWebHashHistory 创建hash模式 
        // createWebHistory 创建history模式 
        history: createRouterMode(), 
        // 路由规则 
        routes: [ 
            {
                path: '/home', 
                component: () => import('../views/Home/index.vue') 
             }, 
             { 
                path: '/about/:id', 
                component: () => import('../views/About/index.vue') 
             } 
          ] 
}) 

export default router

获取路由实例, 通过useRouter, useRouter函数来自于vue-router, 调用useRouter获取到router实例

import { ref } from 'vue' 
// 引入useRouter 
import { useRouter } from 'vue-router' 
// 创建router实例 const 
router = useRouter() 
const goAbout = () => { 
        router.push('/about/1') 
}

获取当前的路由信息, 通过useRoute, useRoute函数来自于vue-router, 调用useRoute获取到route

import { ref } from 'vue'
// 引入useRoute
import { useRoute } from 'vue-router'
// 创建route
const route = useRoute()

12. pinia

12.1 pinia相比较vuex的优势
  • 比vuex对vue3的兼容会更好
  • pinia对ts的支持更好
  • pinia和vuex5的提案很像
  • pinia的使用比vuex使用更简单方便
12.2 pinia如何使用
  • main.js中进行createPinia的引入,通过createPinia创建pinia实例,并进行挂载
// 引入创建pinia实例
import { createPinia } from 'pinia'
const pinia = createPinia()

const app = createApp(App)
// 挂载pinia
app.use(pinia)
  • 创建pinia模块, 在js文件中通过defineStore(来自于pinia),创建一个模块数据管理
    • 参数1: 模块标识
    • 参数2:配置对象(state,actions, getters)
    • 返回值: 函数
import { defineStore } from 'pinia'
// 参数1: 模块标识
// 参数2: 配置对象
// 返回值: 函数
const cartStore = defineStore('cart', {
  state: () => {
    return {
      total: '111.00'
    }
  },
  actions: {},
  getters: {}
})

export default cartStore
  • 使用模块的数据或方法, 导入模块, 直接调用模块的属性(state,getters)或方法(actions)
  • 如果想对模块的数据进行解构,通过storeToRefs处理后解构的数据变为响应式了
  • 模块肯定进行统一整合
12.3 pinia有哪些属性
  • state: 定义数据
  • actions: 定义方法, 同步和异步都可以处理
  • getters: 定义派生数据
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值