前端常见面试题 (二) vue专篇

Vue专篇

今天浅整理一下vue专篇面试题

一、 什么是 MVVM?比之 MVC 有什么区别?

  • MVC、MVVM 是两种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化我们的开发效率。

1.MYYM

  • MVVM即Model-View-ViewModel的简写。即模型-视图-视图模型

  • 模型(Model)指的是后端传递的数据

  • 视图(View)指的是所看到的页面

  • 视图模型(ViewModel)是mvvm模式的核心,它是连接view和model的桥梁 – vue也充当这个角色

    分为以下两种方法:

    • 将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面,实现的方式是:数据绑定

    • 将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据,实现的方式是:DOM事件监听

    • 这两个方向都实现的,我们称之为数据的双向绑定

2.MVC

  • MVC是Model-View-Controller的简写。即模型-视图-控制器
  • M和V指的意思和MVVM中的M和V意思一样
  • C即Controller指的是页面业务逻辑,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新
  • MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。
  • 使用MVC的目的:将M和V的代码分离
  • MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下
3.MVC和MVVM的区别
  • MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念
  • ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。
  • MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性
  • MVVM主要解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验
  • MVVM 实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)
4.拓展:Vue 并没有完全遵循 MVVM 的思想
  • 严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。

二、说说你对vue的理解

  • vue是一个用于创建用户界面的开源的JavaScript框架,也是一个创建单页应用的web应用框架

  • vue是一套用于构建用户界面的渐进式MVVM框架

    • 渐进式:强制主张最少
  • Vue.js 实现了一套声明式渲染引擎,并在runtime或者预编译时将声明式的模板编译成渲染函数挂载在观察者 Watcher 中在渲染函数中(touch),响应式系统使用响应式数据的getter方法对观察者进行依赖收集(Collect as Dependency),使用响应式数据的setter方法通知(notify)所有观察者进行更新,此时观察者 Watcher 会触发组件的渲染函数(Trigger re-render),组件执行的 render 函数,生成一个新的 Virtual DOM Tree,此时 Vue 会对新老 Virtual DOM Tree 进行 Diff,查找出需要操作的真实 DOM 并对其进行更新

三、什么是双向绑定(Vue2必会)

  • Vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter在数据变动时* 发布消息给订阅者*,触发相应的监听回调

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5Ql4BuW-1661400413821)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a2da161e3eb54f8483ca0b0df506d47f~tplv-k3u1fbpfcp-zoom-1.image)]

  • 简单分析一下流程:一个 JS 对象作为数据传给Vue 实例时,Vue 会遍历它的属性,并使用Object.defineProperty 将它们转化为 getter/setter,Vue在内部会对它们监听,在属性被访问或修改时及时进行通知

  • 图上四个位置分别的作用

    • Observer :数据监听器,对Vue的数据对象中所有属性进行监听,一旦属性发生改变,将最新值传给watcher(订阅者)
    • Compile :指令编译器,对每个DOM元素节点指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
    • Dep :消息订阅器,内部有一个维护的数组用来收集订阅者(Watcher),如果数据发生变动,则触发notify 函数,然后调用wacher订阅者的 update 方法
    • Watcher :订阅者,连接observer 与compile,获取属性变化的通知并将数据及时更新到视图当中
  • 详细步骤:

    • 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
    • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
    • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情 是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发 Compile中绑定的回调,则功成身退。
    • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者, 通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令, 最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视 图更新;视图交互变化(input)->数据model变更的双向绑定效果。

四、v-show和v-if的区别

跳过了简单的

  • v-show的转换不会触发组件生命周期,但是有更高的初始渲染消耗(适合非常频繁切换条件的场景)

  • v-if --有更高的切换消耗(适合不需要频繁切换的)

    • 由FALSE变为true时,会触发beforecreate,create,beforeMount,mounted钩子
    • 有true变为false时,会触发beforeDestory,destoryed方法

五、为什么不建议v-if和v-for一起使用

  • 因为v-for的优先级大于v-if,每次渲染都会先循环再进行条件判断,会造成性能方面的浪费

  • 解决方式: 可以在外面嵌套一层template,在这一层进行v-if判断,里面再进行v-for

六、为什么Vue中的data属性是一个函数而不是一个对象

1.实例和组件的区别
  • vue实例的时候定义data可以是一个对象,也可以是一个函数
  • 组件中定义data只能是一个函数,如果定义为对象则会报错
2.原因
  • 根实例对象data可以是对象也可以是函数(根实例是单例),是因为其不会造成数据污染

  • 组件实例对象的data必须为函数

    • 防止多个组件实例对象之间共有同一个data,产生数据污染
    • 采用函数的形式,initDate时会将其作为工厂函数都会返回全新的data对象,这样不会造成数据污染

七、Vue中给对象添加新属性时,页面不刷新怎么办?

1.原因
  • vue2采用Object.defineProperty实现数据响应式,对新增的属性,并没有通过Object.defineProperty设置响应式数据
2.解决方案
  • Vue.set()–增加少量数据的时候推荐

    • Vue.set(target,propertyName/index,value)
  • Object.assign()—增加大量新属性的时候推荐

    • 创建一个新的对象,合并原对象和混入对象的属性
    • this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})

八、Vue中的$nextTick有什么用?

  • 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM数据
  • 出现原因:因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM, 而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更; 这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数, 如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的;
  • 作用:nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行
2.分析
  • vue在更新DOM时是异步执行的,当数据发生变化的时候,Vue将开启一个异步更新队列,视图需要等队列中的所有数据变化完成之后,再统一进行更新
  • 如果一直修改相同的数据,异步操作队列还会进行去重
for(let i=0; i<100000; i++){
    num = i
}
  • 如果没有nextTick更新机制,每次num更新都会触发视图更新—太多了,有了nextTick机制后只需要更新一次—优化策略
3.使用
  • 使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中

    • 组件内使用 vm.$nextTick() 实例方法只需要通过this.$nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上
// 修改数据
vm.message = '修改后的值'
// DOM 还没有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {
  // DOM 更新了
  console.log(vm.$el.textContent) // 修改后的值
})
​
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {
    console.log(this.$el.textContent) // => '修改后的值'
})
  • ** vue2的基本使用**:
<div id="app">{{ msg }}</div>
​
new Vue({
  el: '#app',
  data(){
    return {
      msg: 'hello vue'
    }
  },
  mounted() {
    this.msg = 'mouche'
    console.log(this.msg) // mouche
    // 虽然msg更新了,但是直接这样获取到的还是hello vue
    console.log(document.getElementById('app').innerText) // hello vue
    // 如果是使用了$nextTick,获取到的是最新的值
    this.$nextTick(() => {
      console.log(document.getElementById('app').innerText) // mouche
    })
  }
  • vue3 的基本使用
import { nextTick } from 'vue'
//可以配合异步进行使用
setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
//也可以直接使用
 nextTick(()=>{
      doSomething.....
 })
​

九、说说你对Vue中Keep-alive的理解

1.是什么
  • keep-alive是vue中的内置组件,能在组件切换的过程中将状态保留在内存中防止重复渲染DOM

  • keep-alive包裹动态组件的时候,会缓存不活动的组件实例,而不是销毁它们

  • Keep-alive可以设置以下props属性:

    • include – 字符串或者正则表达式,只有名称匹配的组件会被缓存
    • exclude --字符串或者正则表达式,任何名称匹配的组件都不会被缓存
    • max–数字,最多可以缓存多少组件实例,如果超过了这个数字的话,在下一个新实例创建之前,就会将以缓存组件中最久没有被访问到的实例销毁掉(LRU)
  • 用法:

<!--基本用法-->
<keep-alive>
  <component :is="view"></component>
</keep-alive>
​
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>
​
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>
​
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>
  • 匹配首先检查组件自身的name选择,如果name选项不可用,则匹配它的局部注册名称匿名组件不能被匹配
2.原理
  • 获取 keep-alive 包裹着的第一个子组件对象及其组件名; 如果 keep-alive 存在多个子元

素,keep-alive 要求同时只有一个子元素被渲染。所以在开头会获取插槽内的子元素,调用 getFirstComponentChild 获取到第一个子元素的 VNode

根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则开启缓存策略。

根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键)。

如果不存在,则在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。最后将该组件实例的keepAlive属性值设置为true

3.使用场景
  • 原则:当我们在某些场景下不需要让页面重新加载时,可以使用KeepAlive

  • 例如:从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive

    • 在路由设置KeepAlive属性判断是否需要缓存
    {
      path: 'list',
      name: 'itemList', // 列表页
      component (resolve) {
        require(['@/pages/item/list'], resolve)
     },
     meta: {
      keepAlive: true,
      title: '列表页'
     }
    }
    
    • 使用
    <div id="app" class='wrapper'>
        <keep-alive>
            <!-- 需要缓存的视图组件 --> 
            <router-view v-if="$route.meta.keepAlive"></router-view>
        </keep-alive>
          <!-- 不需要缓存的视图组件 -->
         <router-view v-if="!$route.meta.keepAlive"></router-view>
    </div>
    
4.缓存后如何获取数据
  • beforeRouteEnter:每次组件渲染的时候都会执行
beforeRouteEnter(to, from, next){
    next(vm=>{
        console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据
    })
},
  • actived
activated(){
      this.getData() // 获取数据
},

十、Vue的修饰符有哪些?

1.表单修饰符
  • lazy:填完消息,光标离开标签的时候,才会将值赋予给value
<input type="text" v-model.lazy="value">
<p>{{value}}</p>
  • trim:自动过滤用户输入的首空格字符,而中间的空格不会省略
<input type="text" v-model.trim="value">
  • number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值
<input v-model.number="age" type="number">
2.事件修饰符
  • stop:阻止事件冒泡(重要),相当于event.stopPropagation
<div @click="shout(2)">
  <button @click.stop="shout(1)">ok</button>
</div>
//只输出1
  • prevent:阻止事件默认行为,相当于event.preventDefault
<form v-on:submit.prevent="onSubmit"></form>
  • self: 只当在event.target是当前元素自身的时候触发处理函数
<div v-on:click.self="doThat">...</div>
​
// v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击
  • once:绑定了事件以后只能触发一次
<button @click.once="shout(1)">ok</button>
  • capture:使事件触发从包含该元素的顶层从下触发
  • passive:在移动端,当我们监听元素滚动时候,会一直触发onscroll,使用passive相当于给onsrcoll整了个.lazy修饰符(重要)
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
3.鼠标按钮修饰符
  • left 左键点击
  • right 右键点击
  • middle 中键点击
<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>
4.键盘修饰符
5.v-bind修饰符
  • sync:能对props进行一个双向绑定

    • 实际上就是一个语法糖
    • 子组件传递的事件名格式必须为 update:value,value必须与子组件中的props中声明的完全一直
    • 不可以用在一个字面量的对象上
//父组件
<comp :myMessage.sync="bar"></comp> 
//子组件
this.$emit('update:myMessage',params);

相当于以下的简写

//父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){
 this.bar = e;
}
//子组件js
func2(){
  this.$emit('update:myMessage',params);
}

十一、Vue项目有封装过axios吗?怎么封装的?

1.安装
// 项目中安装
npm install axios --S
// cdn 引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2.导入
import axios from 'axios'
3.发送请求
  • 普通请求
axios({
    url:'',
    method:'GET',
    params:{
        type:''.
        page:1
    }
}).then(res=>{})
  • 并发请求
function getUsetAccount(){
    return axios.get('/user/123')
}
function getUserPermission(){
    return axios.get('/user/123/123')
}
axios.all([getUsetAccount,getUserPermission]).then(axios.spread(res1,res2)=>{
    //两个请求都执行完成才会执行
})
4.封装
  • 设置接口请求前缀

    • 利用node环境变量来判断,用来区分开发,测试,生产环境
    if(process.env.NODE_ENV === 'development'){
          axios.defaults.baseURL = 'http://dev.xxx.com'
    }else if(process.env.NODE_ENV === 'production'){
         axios.defaults.baseURL = 'http://prod.xxx.com'
    }
    
  • 跨域

    • 在vue.config.js文件中配置devServer实现代理转发
    devServer:{
        proxy:{
            '/api':{
                target:'http://dev.xxx.com',
                changeOrigin:true,
                pathRewrite:{
                    '/api':''
                }
            }
        }
    }
    
  • 设置请求头与超时时间

    • 大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,这里将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
    const service = axios.create({
        ...
        timeout: 30000,  // 请求 30s 超时
    	  headers: {
            get: {
              'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
              // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
            },
            post: {
              'Content-Type': 'application/json;charset=utf-8'
              // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
            }
      },
    })
    
  • 封装请求方法

    • 先引入封装好的方法,在要调用的接口重新封装一个方法暴露出去
    // get 请求
    export function httpGet({
      url,
      params = {}
    }) {
      return new Promise((resolve, reject) => {
        axios.get(url, {
          params
        }).then((res) => {
          resolve(res.data)
        }).catch(err => {
          reject(err)
        })
      })
    }
    ​
    // post
    // post请求
    export function httpPost({
      url,
      data = {},
      params = {}
    }) {
      return new Promise((resolve, reject) => {
        axios({
          url,
          method: 'post',
          transformRequest: [function (data) {
            let ret = ''
            for (let it in data) {
              ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
            }
            return ret
          }],
          // 发送的数据
          data,
          // •url参数
          params
    ​
        }).then(res => {
          resolve(res.data)
        })
      })
    }
    
  • 请求拦截器

axios.interceptors.request.use(config=>{
        token && (config.header.Authrization = token);
        return config
    },
    err=>{
        return Promise.error(err)
    }
)
  • 响应拦截器
axios.interceptors.response.use(response = >{
    if(response.status == 200){
        if(response.data.code == 511){
            //未授权
        }else if(response.data.code == 510){
            //未登录
        }else{
            return Promise.resolve(response)
        }
         else{
             //不是200请求
            return Promise.reject(response)
        }
    },
    err=>{
      // 我们可以在这里对异常状态作统一处理
  if (error.response.status) {
    // 处理请求失败的情况
    // 对不同返回码对相应处理
    return Promise.reject(error.response)
  }
}
})

十二、大型项目中,Vue项目怎么划分结构和划分组件比较合理呢?

1.原则
  • 文件夹和文件夹内部文件的语义一致性
  • 单一入口/出口
  • 就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
  • 公共的文件应该以绝对路径的方式从根目录引用
  • /src 外的文件不应该被引入
2.目录结构
  • 多页面目录结构
my-vue-test:.
│  .browserslistrc
│  .env.production
│  .eslintrc.js
│  .gitignore
│  babel.config.js
│  package-lock.json
│  package.json
│  README.md
│  vue.config.js
│  yarn-error.log
│  yarn.lock
│
├─public
│      favicon.ico
│      index.html
│
└─src
    ├─apis //接口文件根据页面或实例模块化
    │      index.js
    │      login.js
    │
    ├─components //全局公共组件
    │  └─header
    │          index.less
    │          index.vue
    │
    ├─config //配置(环境变量配置不同passid等)
    │      env.js
    │      index.js
    │
    ├─contant //常量
    │      index.js
    │
    ├─images //图片
    │      logo.png
    │
    ├─pages //多页面vue项目,不同的实例
    │  ├─index //主实例
    │  │  │  index.js
    │  │  │  index.vue
    │  │  │  main.js
    │  │  │  router.js
    │  │  │  store.js
    │  │  │
    │  │  ├─components //业务组件
    │  │  └─pages //此实例中的各个路由
    │  │      ├─amenu
    │  │      │      index.vue
    │  │      │
    │  │      └─bmenu
    │  │              index.vue
    │  │
    │  └─login //另一个实例
    │          index.js
    │          index.vue
    │          main.js
    │
    ├─scripts //包含各种常用配置,工具函数
    │  │  map.js
    │  │
    │  └─utils
    │          helper.js
    │
    ├─store //vuex仓库
    │  │  index.js
    │  │
    │  ├─index
    │  │      actions.js
    │  │      getters.js
    │  │      index.js
    │  │      mutation-types.js
    │  │      mutations.js
    │  │      state.js
    │  │
    │  └─user
    │          actions.js
    │          getters.js
    │          index.js
    │          mutation-types.js
    │          mutations.js
    │          state.js
    │
    └─styles //样式统一配置
        │  components.less
        │
        ├─animation
        │      index.less
        │      slide.less
        │
        ├─base
        │      index.less
        │      style.less
        │      var.less
        │      widget.less
        │
        └─common
                index.less
                reset.less
                style.less
                transition.less

十三、Vue怎么实现权限管理?控制到按钮级别的权限怎么做?

1.接口权限
  • 采用JWT的形式来验证,登录完拿到token,将token存起来,通过axios拦截器进行拦截,每次请求的时候头部携带token
axios.interceptors.request.use(config => {
    //这里是存在cookie,请求的时候不设错
    config.headers['token'] = cookie.get('token')
    return config
})
axios.interceptors.response.use(res=>{},{response}=>{
    //看返回
    if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误
        router.push('/login')
    }
})
2.按钮权限
3.菜单权限
4.路由权限
  • 方案一:初始化的时候就挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验(

    • 全局路由守卫,每次路由跳转都要做权限判断
    • 菜单信息写死在前端,要改个显示文字或者权限信息,需要重新编译
    • 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
  • 方案二:初始化的时候先挂载不需要权限控制的路由,比如登录页,404等错误页。如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制

十四、vue2 和vue3 的区别

1.绑定原理发生了改变
  • vue2的双向数据绑定事利用ES5的一个APIObject.defineProperty()对数据进行劫持,结合,发布,订阅来实现
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    //拦截get,当我们访问data.key时会被这个方法拦截到
    get: function getter () {
        //我们在这里收集依赖
        return obj[key];
    },
    //拦截set,当我们为data.key赋值时会被这个方法拦截到
    set: function setter (newVal) {
        //当数据变更时,通知依赖项变更UI
    } 
})
  • vue3使用了es6的proxyAPI
  1. defineProperty只能监听某个属性,不能对全对象监听
  2. 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
  3. 可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化
2.Vue3支持碎片
  • 在组件中可以拥有多个根节点
3.Composition API
  • Vue2使用选项类型API(options API
  • Vue3使用合成型API(Composition API

旧的选项型API在代码里分割了不同的属性: data,computed属性,methods,等等。新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,这样代码会更加简便和整洁

4.生命周期

setup 执行时机是在 beforeCreate 之前执行

Vue2--------------vue3
beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount //组件挂载到节点上之前执行的函数
mounted       -> onMounted  //组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate //组件更新之前执行的函数
updated       -> onUpdated //组件更新完成之后执行的函数
beforeDestroy -> onBeforeUnmount //组件卸载之前执行的函数
destroyed     -> onUnmounted // 组件卸载完成后执行的函数
activated     -> onActivated
deactivated   -> onDeactivated
5.父子传参不同
  • setup 函数时,它将接受两个参数:(props、context(包含attrs、slots、emit))
  • 执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined
  • 与模板一起使用:需要返回一个对象 (在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用)
  • 使用渲染函数:可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态
  • setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性

十五、什么是Vue.extend

  • Vue.extend是一个全局的API,实际是创建一个构造器,并将其挂载到HTML的元素上(创建一个template标签)。可以通过propsData传参
  • 使用场景:
  • 组件模板都是事先定义好的,如果我要从接口动态渲染组件怎么办? 所有内容都是在 #app下渲染,注册组件都是在当前位置渲染。如果我要实现一个类似于 window.alert()提示组件要求像调用 JS 函数一样调用它,该怎么办? 这时候,Vue.extend + vm.$mount 组合就派上用场了。
    <h1>Vue.extend</h1>
    <hr>
    <div id="element">
        <h1>Hello!</h1>
    </div>
    <hr>
    <author></author>
    <hr>
    <div id="transmit"></div>
​
​
var Profile1 = Vue.extend({
    template: '<p>My name is {{Name}}</p>',
    data: function () {
        return {
        Name: 'ElucidatorSky'
        }
      }
    })
    // 输出 Profile1 实例,在控制台输出为VueComponent{}
    console.log(new Profile1());
    // 创建 Profile1 实例,并挂载到一个元素上。
    new Profile1().$mount('#element')
​
    var Profile2 = Vue.extend({
        template:"<p>{{Name}}</p>",
        data:function(){
            return{
                Name:'ElucidatorSky'
            }
        }
    });   
    // 创建 Profile2 实例,并挂载到一个元素上。
    new Profile2().$mount('author');
​
    var Profile3 = Vue.extend({
        template: '<p>{{extendData}}</br>实例传入的数据为:{{propsExtend}}</p>',
        data: function () {
            return {
            extendData: '这是extend扩展的数据',
            }
        },
        props:['propsExtend']
    })
    // 创建 Profile 实例,并挂载到一个元素上。可以通过propsData传参.
    new Profile3({propsData:{propsExtend:'我是实例传入的数据'}}).$mount('#transmit')
​

十六、什么是Vue.mixin和minxins

  • Vue.mixin即是混入,mixin的作用是多个组件可以共享数据和方法
  • 可以通过mixin抽离公共的业务逻辑
  • 当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
// mixins示例        
//全局混入
Vue.mixin({
    created:function(){
        console.log('我是全局被混入的');
    }
})
var mixin = {
  created: function () { console.log('我是外部被混入的') }
}
var app = new Vue({
// 局部混入
  mixins: [mixin]
})
​

十七、说说Vue中的diff算法

  • 在vue中,作用于虚拟Dom渲染成真实Dom的新旧VNode节点比较

  • diff整体策略:深度优先,同层比较

    • 只会在同层级进行,不会跨层级比较
    • 比较的过程中,循环从两边向中间靠拢

十八、Vue路由中,history和hash两种模式有什么区别

1、hash模式
  • 把前端路由用井号 #拼接在真实URL后面。当#后面的路劲发生变化时,浏览器不会重新发起请求,而是触发hashchange事件
  • 浏览器兼容较好,但是路径在#后面比较丑
2、history模式
  • 允许开发者直接更改前端路由,即更新浏览器的地址而不重新发起请求
  • 是H5提供的新特性
  • 路径比较正规,没有#
  • 兼容性不如hash,需要服务端支持,否则一刷新页面就404

十九、Vue2.0为什么不能检查数组的变化,该怎么解决?

  • vue2的不足:

    • 无法检测数组/对象的新增:因为在构造函数中就已经为所有的属性做了检测绑定操作
    • 无法检测通过索引改变数组的操作:Object.definePropety是可以检测通过索引改变数组的操作,没有实现的原因是因为性能代价和用户体验收益不成正比
  • 解决方案

    • this.$set(array, index, data)
    this.dataArr = this.originArr
    this.$set(this.dataArr,0,{data:'修改第一个元素'})
    console.log(this.dataArr) 
    console.log(this.originArr)  //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果 
    
    • 使用splice。splice会被监听,同时又可以做到增删改
    • 利用临时变量进行中转
    let tempArr = [...this.targetArr];
    temArr[0] = {data:'text'}
    this.targetArr = tmp
    

二十、Vue组件通讯有哪几种方式

1、props$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的

2、$parent,$children获取当前组件的父组件和当前组件的子组件

3、$attrs$listeners A->B->C。Vue 2.4 开始提供了 a t t r s 和 attrs 和 attrslisteners 来解决这个问题

4、父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量

4、$refs 获取组件实例

5、eventBus 兄弟组件数据传递 这种情况下可以使用事件总线的方式

6、vuex 状态管理

二十一、说说vue的内容指令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SiVJ8Cn-1661400413823)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/97ad95a72d964b6f9f4a4b2a1ecb9280~tplv-k3u1fbpfcp-zoom-1.image)]

二十二、怎么理解Vue的单向数据流

  • 数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据

注意:在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告

  • 如果实在要改变父组件的 prop 值 可以再定义一个变量 并用 prop 的值初始化它 之后用$emit 通知父组件去修改

二十三、v-for为什么要加Key

  • 如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法
  • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
  • 更准确: a.key==b.key,通过判断key是否相等可以避免不同VNode复用的情况
// 判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用
function isSameVnode(oldVnode, newVnode) {
  return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key;
}
  • 更快速:利用key的唯一性生成map对象来获取对应节点,比起遍历快
// 根据key来创建v-for的index映射表  类似 {'a':0,'b':1} 代表key为'a'的节点在第一个位置 key为'b'的节点在第二个位置
function makeIndexByKey(children) {
  let map = {};
  children.forEach((item, index) => {
    map[item.key] = index;
  });
  return map;
}
// 生成的映射表
let map = makeIndexByKey(oldCh);

二十四、 使用过 Vue SSR 吗?说说 SSR

SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端

优点:

SSR 有着更好的 SEO、并且首屏加载速度更快

缺点: 开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。

服务器会有更大的负载需求

二十五、vue 中使用了哪些设计模式

  • 工厂模式 - 传入参数即可创建实例

虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode

  • 单例模式 - 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉

  • 发布-订阅模式 (vue 事件机制)
  • 观察者模式 (响应式数据原理)
  • 装饰模式: (@装饰器的用法)
  • 策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值