前端面试-经典的Vue面试题

面试总结三大模块:Vue双向绑定及原理、生命周期、组件通信、Vue官方API

目录:1.Vue双向绑定及原理

    1.1你对MVVM是怎么理解的?

    1.2你对Vue响应式原理是怎么理解的?是否可以实现一个简版的?Vue2中是如何监听数组的变化的?Vue3使用Proxy重写,相比Vue2的Object.defineProperty,有哪些优势?

      1.2.1响应式原理

      1.2.2 Vue2 Object.defineProperty简版实现代码

      1.2.3 针对弊端解决 无法监听数组问题:Vue2中是如何监听数组的变化的?

      1.2.4 针对弊端解决 无法劫持对象的新增属性:this.$set能够解决对象新增属性问题。延伸问题:$set为啥能监测数组变动?

      1.2.5 Vue3 proxy简版实现代码

    1.3你对Vue的v-model双向绑定是怎么理解的?是否可以实现一个简版的

      1.3.1 v-model双向绑定理解

      1.3.2 v-model双向绑定实现原理代码简版

      1.3.3 双向绑定原理

    2.生命周期

    2.1Vue有哪些生命周期?它们有哪些使用场景?你的接口请求一般都放在哪个生命周期方法中,为什么?你的获取dom的方法一般都放在哪个生命周期方法中,为什么?

        2.1.1vue3中的选项式生命周期钩子及使用场景

        2.1.2 vue3中的组合式生命周期钩子(使用场景对比选项式生命周期钩子
        2.1.3异步请求一般在哪个生命周期,为什么?
        2.1.4获取DOM的方法放在哪个生命周期方法中?为什么?
   3.组件通信
    3.1Vue的子组件如何调用父组件的方法?
        3.1.1直接在子组件中通过this.$parent.event来调用父组件的方法
        3.1.2在子组件里用$emit向父组件触发一个事件,父组件监听这个事件就行了
        3.1.3父组件把方法传入子组件中,在子组件里直接调用这个方法
    3.2Vue组件通信有哪些方式
   4.vue官方API
    4.1Vue的data为啥是函数
    4.2Vue列表中加的key是干什么的
    4.3Vuex是什么,它的使用场景是什么
    4.4Vue-router的history模式和hash模式
      4.4.1hash模式
      4.4.2hash模式特点
      4.4.3history模式
      4.4.4history模式特点
      4.4.5history模式和hash模式的区别
      4.4.6传统路由和前端路由的区别
    4.5有没有写过Vue的指令,它的实现原理是什么?有没有写过Vue的插件,它的实现原理是什么?
      4.5.1指令api:生命周期和钩子函数参数
      4.5.2指令和插件本质
      4.5.3 实现一键复制文本内容,用于鼠标右键粘贴的指令(配合插件执行全局注册)
          4.5.3.1定义指令对象代码(执行的代码不重要,重要在于编写格式)
          4.5.3.2定义一个能够注册指令的插件(不使用插件注册的方法是直接在main调用directive注册就可以)
          4.5.3.3在main中引入插件来进行指令注册
          4.5.3.4使用指令方法
    4.6nextTick是干什么的?
      4.6.1作用
      4.6.2使用场景
      4.6.1实现原理
    4.7vue性能优化
      4.7.1v-if和v-show区分使用场景
      4.7.2computed、watch、methods区分使用场景
      4.7.3提前处理好数据解决v-if和v-for必须同级的问题
      4.7.4v-for 遍历必须为 item 添加 key
      4.7.5图片大小优化和懒加载
      4.7.6路由懒加载
    4.8vue的虚拟dom你是怎么理解的?是否可以实现一个简版的?
      4.8.1虚拟dom理解
      4.7.2简版实现
      4.7.3实现原理
 
一、Vue双向绑定及原理

1.你对MVVM是怎么理解的?

  MVVM是Model-View-ViewModel缩写。Model层代表数据模型,View代表视图层,ViewModel是MVVM的核心,它是连接View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据,实现数据的双向绑定。可以不用再去低效又麻烦地通过操纵 DOM 去更新视图,专心处理和维护 ViewModel层。

 

2.你对Vue响应式原理是怎么理解的?是否可以实现一个简版的?Vue2中是如何监听数组的变化的?Vue3使用Proxy重写,相比Vue2的Object.defineProperty,有哪些优势?

    2.1响应式原理

  • Vue2的响应式是基于Object.defineProperty实现的
  • Vue3的响应式是基于ES6的Proxy来实现的

   2.2 Vue2 Object.defineProperty简版实现代码

// 响应式函数
function reactive(obj, key, value) {
  Object.defineProperty(data, key, {
    get() {
      console.log(`访问了${key}属性`)
      return value
    },
    set(val) {
      console.log(`将${key}由->${value}->设置成->${val}`)
      if (value !== val) {
        value = val
      }
    }
  })
}

const data = {
  name: '林三心',
  age: 22
}
Object.keys(data).forEach(key => reactive(data, key, data[key]))
console.log(data.name)
// 访问了name属性
// 林三心
data.name = 'sunshine_lin' // 将name由->林三心->设置成->sunshine_lin
console.log(data.name)
// 访问了name属性
// sunshine_lin

  弊端:1.data新增了hobby属性,进行访问和设值,但是都不会触发get和set,所以弊端就是:Object.defineProperty只对初始对象里的属性有监听作用,而对新增的属性无效。这也是为什么Vue2中对象新增属性的修改需要使用Vue.$set来设值的原因;2.不能对数组数据进行劫持,无法监测到数组长度变化,需重写数组方法。

// 接着上面代码

data.hobby = '打篮球'
console.log(data.hobby) // 打篮球
data.hobby = '打游戏'
console.log(data.hobby) // 打游戏

   2.3 针对弊端解决 无法监听数组问题:Vue2中是如何监听数组的变化的?

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  // 缓存原来的方法
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

  看来Vue能对数组进行监听的原因是,把数组的方法重写了。总结起来就是这几步:

  01先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。

  02对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。

  03把需要被拦截的 Array 类型的数据原型指向改造后原型

   2.4 针对弊端解决 无法劫持对象的新增属性:this.$set能够解决对象新增属性问题。延伸问题:$set为啥能监测数组变动?

function set (target, key, val) {
  //... 
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
  }
  //... 
  defineReactive$$1(ob.value, key, val);
  ob.dep.notify();
  return val
}
  • 如果target是一个数组且索引有效,就设置length的属性。
  • 通过splice方法把value设置到target数组指定位置。
  • 设置的时候,vue会拦截到target发生变化,然后把新增的value也变成响应式
  • 最后返回value

  这就是vue重写数组方法的原因,利用数组这些方法触发使得每一项的value都是响应式的。

   2.5 Vue3 proxy简版实现代码(优势是解决了弊端)

  target:源对象   prop:要进行操作的属性   value:set操作时的最新值  

  Reflect作用是反射对象,Reflect.get等同于对读取对象的属性obj.a;Reflect.set等同添加或修改对象属性的值obj.b=c;Reflect.deleProperty等同删除对象属性delete obj.d

  js是单线程遇到错误会直接挂机,Reflect兼容性更好,否则需要使用try catch捕获错误

     

 

 3.你对Vue的v-model双向绑定是怎么理解的?是否可以实现一个简版的?

  3.1 v-model双向绑定理解

      v-model可以实现表单元素和数据之间的双向绑定,即修改表单的值,data中对应变量的值也会被修改。在data对应变量中修改值,被绑定的表单值也会被修改;所以称之为双向绑定。

  3.2 v-model双向绑定实现原理代码简版

  使用v-bind将文本框的value绑定给数据变量,实现变量改变,文本框值也随之改变的单向绑定;然后使用v-on绑定文本框的值改变事件,最后将事件对象中的文本框值传递给变量,即实现了双向绑定。

  不使用方法实现:

<div id="ab">
        <!-- <input type="text" v-model="msg"> -->
        <input type="text" name="" id="" v-bind:value="msg" v-on:input="msg = $event.target.value">
        <h4>{{msg}}</h4>
    </div>
    <script>
        const app = new Vue({
            el:'#ab',
            data:{
                msg:'起飞'
            }
        })
    </script>

  使用方法实现:

   <div id="ab">
        <!-- <input type="text" v-model="msg"> -->
        <input type="text" name="" id="" v-bind:value="msg" v-on:input="Value">
        <h4>{{msg}}</h4>
    </div>
    <script>
        const app = new Vue({
            el:'#ab',
            data:{
                msg:'起飞'
            },
            methods:{
                Value(event){
                    this.msg = event.target.value;
                }
            }
        })
    </script>

   3.3双向绑定原理

  Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

  实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

  实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

  实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

  实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

 

二、生命周期

1.Vue有哪些生命周期?它们有哪些使用场景?你的接口请求一般都放在哪个生命周期方法中,为什么?你的获取dom的方法一般都放在哪个生命周期方法中,为什么?

 1.1 vue3中的选项式生命周期钩子及使用场景

  • beforeCreate: 在实例初始化之后、进行数据侦听和事件/侦听器的配置之前同步调用
  • created:在实例创建完成后被立即同步调用
  • beforeMount:在挂载开始之前被调用
  • mounted:在实例挂载完成后被调用
  • beforeUpdate:在数据发生改变后,DOM 被更新之前被调用
  • updated:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用
  • beforeUnmount(在Vue2中是:beforeDestroy):在卸载组件实例之前调用
  • unmounted (在Vue2中是: destroyed):卸载组件实例后调用
 1.2 vue3中的组合式生命周期钩子(使用场景对比选项式生命周期钩子) 
  
    setup() 内部(组合式api)调用的生命周期钩子里是没有beforeCreateCreate函数的。
  官方解释: 因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
 1.3异步请求一般在哪个生命周期,为什么?
  可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
  • 对于作为子组件被调用的组件里,异步请求应当在mounted里调用,因为这个时候子组件可能需要涉及到对dom的操作;
  • 对于页面级组件,当我们需要使用ssr(服务端渲染)的时候,只有created是可用的,所以这个时候请求数据只能用它;能更快获取到服务端数据,减少页面 loading 时间;
  • 对于页面级组件, 当我们做异步操作时,涉及到要访问dom的操作,我们仍旧只能使用mounted;
  • 对于一般情况,createdmounted都是可以的;
 1.4获取DOM的方法放在哪个生命周期方法中?为什么?

  1. beforeCreated:生成$options选项,并给实例添加生命周期相关属性。在实例初始化之后,在 数据观测(data observer) 和event/watcher 事件配置之前被调用,也就是说,data,watcher,methods都不存在                   这个阶段。但是有一个对象存在,那就是$route,因此此阶段就可以根据路由信息进行重定向等操作。

  2. created:初始化与依赖注入相关的操作,会遍历传入methods的选项,初始化选项数据,从$options获取数据选项(vm.$options.data),给数据添加‘观察器’对象并创建观察器,定义getter、setter存储器属                性。在实例创建之后被调用,该阶段可以访问data,使用watcher、events、methods,也就是说 数据观测(data observer) 和event/watcher 事件配置 已完成。但是此时dom还没有被挂载。该阶段允许执行              http请求操作。

  3. beforeMount:将HTML解析生成AST节点,再根据AST节点动态生成渲染函数。相关render函数首次被调用(划重点)。

  4. mounted:在挂载完成之后被调用,执行render函数生成虚拟dom,创建真实dom替换虚拟dom,并挂载到实例。可以操作dom,比如事件监听

  5. beforeUpdate:$vm.data更新之后,虚拟dom重新渲染之前被调用。在这个钩子可以修改$vm.data,并不会触发附加的冲渲染过程。

  6. updated:虚拟dom重新渲染后调用,若再次修改$vm.data,会再次触发beforeUpdate、updated,进入死循环。

  7. beforeDestroy:实例被销毁前调用,也就是说在这个阶段还是可以调用实例的。

  8. destroyed:实例被销毁后调用,所有的事件监听器已被移除,子实例被销毁。

  总结来说,虚拟dom开始渲染是在beforeMount时,dom实例挂载完成在mounted阶段显示。

  在选项式生命周期中只有mounted时期挂载完成后才能够获取DOM,mounted时期前DOM未生成,mounted时期后调用进入死循环,同理在组合式生命周期只有在onMounted时期,setup时期等都不可以获取DOM。(或者可以在created时期使用$nextTick函数)

 

 三、组件通信

1.Vue的子组件如何调用父组件的方法?

 1.1直接在子组件中通过this.$parent.event来调用父组件的方法

// 父组件
<template>
  <div>
    <child></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod() {
        console.log('测试');
      }
    }
  };
</script>
// 子组件
<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    methods: {
      childMethod() {
        this.$parent.fatherMethod();
      }
    }
  };
</script>

  1.2在子组件里用$emit向父组件触发一个事件,父组件监听这个事件就行了

// 父组件
<template>
  <div>
    <child @fatherMethod="fatherMethod"></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod() {
        console.log('测试');
      }
    }
  };
</script>
// 子组件
<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    methods: {
      childMethod() {
        this.$emit('fatherMethod');
      }
    }
  };
</script>

  1.3父组件把方法传入子组件中,在子组件里直接调用这个方法

// 父组件
<template>
  <div>
    <child :fatherMethod="fatherMethod"></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod() {
        console.log('测试');
      }
    }
  };
</script>
// 子组件
<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    props: {
      fatherMethod: {
        type: Function,
        default: null
      }
    },
    methods: {
      childMethod() {
        if (this.fatherMethod) {
          this.fatherMethod();
        }
      }
    }
  };
</script>

 

2.Vue组件通信有哪些方式

  • 父子通信: 父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
  • 兄弟通信: Bus;Vuex
  • 跨级通信: Bus;Vuex;provide / inject API、$attrs/$listeners
  • https://juejin.cn/post/6877101934600273934

四、vue官方api
1.Vue的data为啥是函数?(单页面应用不受限制)
  在vue中一个组件可能会被其他的组件引用(组件复用),为了防止多个组件实例对象之间共用一个data,产生数据污染。将data定义成一个函数,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响initData时会将其作为工厂函数都会返回全新data对象。
 

2. Vue列表中加的key是干什么的?

  vue中列表循环需加:key="唯一标识" 唯一标识可以是item里面id 等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM。

  key在diff算法中的用法演示:https://juejin.cn/post/7156507746555133965#heading-5

  延申问题:不推荐index作为key值    

  当以数组为下标的index作为key值时,其中一个元素(例如增删改查)发生了变化就有可能导致所有的元素的key值发生改变 diff算法时比较同级之间的不同,以key来进行关联,当对数组进行下标的变换时,比如删除第一条数据,那么以后所有的index都会发生改变,那么key自然也跟着全部发生改变,所以index作为key值是不稳定的,而这种不稳定性有可能导致性能的浪费,导致diff无法关联起上一次一样的数据。因此,能不使用index作为key就不使用index。
 
3.Vuex是什么,它的使用场景是什么
  vuex是一个专为vue.js开发的状态管理模式,实际上在真是开发中我们可能会在下面这种情况使用它:
  1. 登录的状态、以及用户的信息
  2. 购物车的信息,收藏的信息等
  3. 用户的地理位置
4.Vue-router的history模式和hash模式?
  4.1hash模式
    在浏览器中符号“#”,#以及#后面的字符称之为 hash, 用 window.location.hash 读取。特点:hash 虽然在 URL 中,但不被包括在 HTTP 请求中;用来指导浏览器动作,对服务端安全无用,hash 不会重加载页面。路由的哈希模式其实是利用了window.onhashchange事件,也就是说你的url中的哈希值(#后面的值)如果有变化,就会自动调用hashchange的监听事件,在hashchange的监听事件内可以得到改变后的url,这样能够找到对应页面进行加载。
  4.2hash模式特点
  • URL 中的 hash 值只是客户端的一种状态,向服务端发送请求的时候,hash 部分不会被发送。
  • hash 值得改变会在浏览器的历史记增加访问记录,所以可以通过浏览器的回退、前进控制 hash 值的改变。
  • 可以通过 a 标签设置 href 值或者通过 js 给location.hash 赋值来改变 hash 值。
  • 可以通过hashchang 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
  4.3history模式
    history 采用 HTML5 的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以 及 popState 事件的监听到状态变更pushState方法、replaceState方法,只能导致history对象发生变化,从而改变当前地址栏的 URL,但浏览器不会向后端发送请求,也不会触发popstate事件的执行。

    popstate事件的执行是在点击浏览器的前进后退按钮的时候,才会被触发(popstate控制浏览器历史记录的api)

    使用history模式需要更改router.js的mode为history,设置vue.config.js的publicPath:'/‘

  4.4history模式特点
  • 通过 pushState 和 replaceState 两个API 来操作实现 URL 的变化。
  • 可以通过 popstate 事假来监听 URL 的变化,从而对页面进行跳转(渲染)。
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,需要手动触发页面跳转
  4.5history模式和hash模式的区别
    1.形式上:hash模式url里面永远带着#号,开发当中默认使用这个模式。如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合推广宣传;
    2.功能上:比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合,让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok了
  4.6延申:传统路由和前端路由的区别

    传统的路由指的是:当用户访问一个url时,对应的服务器会接收这个请求,然后解析url中的路径,从而执行对应的处理逻辑。这样就完成了一次路由分发

    前端路由是:不涉及服务器的,是前端利用hash或者HTML5的history API来实现的,一般用于不同内容的展示和切换

5.有没有写过Vue的指令,它的实现原理是什么?有没有写过Vue的插件,它的实现原理是什么?

  5.1指令api:生命周期和钩子函数参数

  • bind:指令第一次绑定到元素时调用,此钩子只会调用一次。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。
  • el:指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,包含以下属性:
  • name:指令名,不包括 v- 前缀。
  • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
  • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
  • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
  • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
  • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnodeVue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

  5.2 指令和插件本质

    Vue 插件机制的原理:本质上插件就是一个对象,在对象里面调用install 方法 

    Vue指令的原理:本质上插件就是一个对象

  5.3 实现一键复制文本内容,用于鼠标右键粘贴的指令(配合插件执行全局注册)

     5.3.1定义指令对象代码(执行的代码不重要,重要在于编写格式)
import { Message } from 'ant-design-vue';

const vCopy = { // 名字爱取啥取啥
  /*
    bind 钩子函数,第一次绑定时调用,可以在这里做初始化设置
    el: 作用的 dom 对象
    value: 传给指令的值,也就是我们要 copy 的值
  */
  bind(el, { value }) {
    el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到
    el.handler = () => {
      if (!el.$value) {
      // 值为空的时候,给出提示,我这里的提示是用的 ant-design-vue 的提示,你们随意
        Message.warning('无复制内容');
        return;
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea');
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly';
      textarea.style.position = 'absolute';
      textarea.style.left = '-9999px';
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value;
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 选中值并复制
      textarea.select();
      // textarea.setSelectionRange(0, textarea.value.length);
      const result = document.execCommand('Copy');
      if (result) {
        Message.success('复制成功');
      }
      document.body.removeChild(textarea);
    };
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler);
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value }) {
    el.$value = value;
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    el.removeEventListener('click', el.handler);
  },
};

export default vCopy;

    5.3.2定义一个能够注册指令的插件(不使用插件注册的方法是直接在main调用directive注册就可以)

import copy from './v-copy';
// 自定义指令
const directives = {
  copy,
};
// 这种写法可以批量注册指令
export default {
  install(Vue) {   //install函数是插件的语法
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key]);  //注册指令对象
    });
  },
};

    5.3.3在main中引入插件来进行指令注册

import Vue from 'vue';
import Directives from './directives';

Vue.use(Directives);   //插件使用方法use

    5.3.4使用指令方法

<template>
  <button v-copy="copyText">copy</button>
</template>

<script>
export default {
  data() {
    return {
      copyText: '要 Copy 的内容',
    };
  },
};
</script>

6.nxetTick是干什么用的

   6.1作用
    nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
  6.2使用场景
    想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中
  6.3实现原理

    将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务,把微任务放在DOM 更新后执行;

    Vue2根据环境是否支持,选择的微任务优先级为:Promise---> MutationObserver---> setImmediate---> setTimeout

    Vue3直接选用Promise,添加几个维护队列queueJob -> queueFlush -> flushJobs -> nextTick参数的 fn,详情看 https://juejin.cn/post/7021688091513454622#heading-9
7.vue性能优化
  7.1v-if和v-show区分使用场景
  7.2computed、watch、methods区分使用场景
  7.3提前处理好数据解决v-if和v-for必须同级的问题
    避免v-if和v-for同时使用,v-forv-if具有更高的优先级,意味着v-if 将分别重复运行于每个v-for循环中。
    解决途径:可以在computed中提前把要v-for的数据中v-if的数据项给过滤处理了。
  7.4v-for 遍历必须为 item 添加 key
    在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。
  7.5图片大小优化和懒加载
    关于图片大小的优化,可以用image-webpack-loader进行压缩图片。
    关于图片懒加载,可以用vue-lazyload插件实现。
  7.6路由懒加载
     Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})
8.vue的虚拟dom你是怎么理解的?是否可以实现一个简版的?
  8.1虚拟dom理解
    Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实的DOM上
  8.2简版实现
var element = {
        tagName: 'ul', // 节点标签名
        props: { // DOM的属性,用一个对象存储键值对
            id: 'list'
        },
        children: [ // 该节点的子节点
          {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
          {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
          {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
        ]
    }

  8.3实现原理

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

posted on 2023-01-29 14:34  ChoZ  阅读(302)  评论(0编辑  收藏  举报

导航