Vue面试题学习__日常积累

166 阅读19分钟

1.简述V-if和v-show的理解

v-ifv-show能够实现的效果相同,都是用来实现控制元素在页面的出现和隐藏效果。

用法也相同:

<Model v-show="isShow" />
<Model v-if = "isShow" />

当表达式为true时,都会占据页面位置;当表达式为false时,都不会占据页面的位置。

两者的区别:

  • 控制手段不同:v-show隐藏原则是为该元素添加css display:nonedom元素依旧存在。v-if显示隐藏是将dom元素整个添加或者删除。

  • 编译过程:v-show是简单的基于css的样式切换。v-if在切换过程中存在一个局部编译/卸载的过程,切换过程中合适的销毁和重建内部的事件监听和组件。

  • 编译条件:v-if是真正的条件渲染,会确保在切换过程中条件块内的事件监听器和子组件适当的销毁和重建。只有渲染条件为假时,并不会操作,直到条件为真才渲染。

    v-showfalse变为true时并不会触发组件的生命周期;

    v-iffalse变为true时,会触发组件的beforeCreateCreatedbeforeMountMounted钩子,由true变为false时。触发beforeDestorydestoryed

性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;

使用场景:

v-ifv-show 都能控制dom元素在页面的显示

v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除)

如果需要非常频繁地切换,则使用 v-show 较好

如果在运行时条件很少改变,则使用 v-if 较好

2. 为什么Vue中的v-if和v-for不建议一起作用?

v-if和v-for的作用

v-if指令用于条件性的来渲染一块内容,只有在表达式返回结果为true时才会进行渲染;

v-for基于一个数组来渲染列表。该指令在使用时需要item in item形式的特殊语法来实现(items是元数据数组或者对象,item是被迭代的数组元素的别名)。

在使用v-for时,建议要设置key值,并保证key值是独一无二的,便于diff算法优化。

两者的使用:

<Model v-if = 'isShow' />
<li v-for = 'item in items' :key="item.id"> 
    {{item.label}}
</li>

v-for和v-show的优先级是什么?

结论:v-for优先级高于v-if

注意事项:

  1. 永远不要把v-ifv-for同时作用到同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)。
  2. 如果避免出现这种情况,则额、可以在外层嵌套templete(页面渲染不生成dom节点),在这层进行v-if判断,在内部进行v-for循环。
<template v-if = 'isShow'>
    <p v-for = 'item in items' />    
</template>

3. 如果条件出现在循环内部,可以通过计算属性computed提前过滤掉那些不需要现实的项

computed: {
    items: function() {
      return this.list.filter(function (item) {
        return item.isShow
      })
    }
}

3. 简述对Vue生命周期的理解?

Vue的生命周期有哪些?

Vue的生命周期 总共可以分为8个阶段:创建前后、再如前后、更新前后、销毁前销毁后,以及一些特殊场景的生命周期。

image.png

生命周期整体流程:

image.png

Vue生命周期的具体分析:

beforeCreate -> created

  • 初始化vue实例,进行数据观测

created

  • 完成数据观测,属性与方法的运算,watchevent事件回调的配置
  • 可调用methods中的方法,访问和修改data数据触发响应式渲染dom,可通过computedwatch完成数据计算
  • 此时vm.$el 并没有被创建

created -> beforeMount

  • 判断是否存在el选项,若不存在则停止编译,直到调用vm.$mount(el)才会继续编译
  • 优先级:render > template > outerHTML
  • vm.el获取到的是挂载DOM

beforeMount

  • 在此阶段可获取到vm.el
  • 此阶段vm.el虽已完成DOM初始化,但并未挂载在el选项上

beforeMount -> mounted

  • 此阶段vm.el完成挂载,vm.$el生成的DOM替换了el选项所对应的DOM

mounted

  • vm.el已完成DOM的挂载与渲染,此刻打印vm.$el,发现之前的挂载点及内容已被替换成新的DOM

beforeUpdate

  • 更新的数据必须是被渲染在模板上的(eltemplaterender之一)
  • 此时view层还未更新
  • 若在beforeUpdate中再次修改数据,不会再次触发更新方法

updated

  • 完成view层的更新
  • 若在updated中再次修改数据,会再次触发更新方法(beforeUpdateupdated

beforeDestroy

  • 实例被销毁前调用,此时实例属性与方法仍可访问

destroyed

  • 完全销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
  • 并不能清除DOM,仅仅销毁实例

使用场景:

image.png

数据请求在created和mounted的区别:

  • created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并没有生成;

  • mounted是在页面dom渲染完毕之后立刻执行的。触发时机上created要早于mounted

    两者的相同点:----都能拿到实例对象的属性和方法。

讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中。

4. 浏览器首屏加载时间及性能优化?

什么是首屏加载:

首屏加载:指的是浏览器在用户输入URL网址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染,但需要展示当前视窗需要的内容。

前端首屏性能优化方式:

image.png

image.png

5. 为什么data属性是一个函数而不是一个对象?

  • 根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染的情况;
  • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象。

6.Vue中的组件和插件有什么区别?

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  • 添加全局方法或者属性。如: vue-custom-element
  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如vue-router

总结:

  • 组件(component)是用来构成你的App的业务模块,目标是App.vue;
  • 插件(Plugin)是用来增强你的技术栈的功能模块,目标是Vue本身;简单来说,插件就是指对Vue的功能的增强或补充。

7.Vue组件之间的通信方式有哪些?

整理vue中8中常规的通信方案

  1. 通过 props 传递
  2. 通过 $emit 触发自定义事件
  3. 使用 ref
  4. EventBus
  5. parentparent 或root
  6. attrs 与 listeners
  7. Provide 与 Inject
  8. Vuex
(1)父组件给子组件传值

props传递数据:

  • 子组件设置props属性,定义接收父组件传递过来的参数

  • 父组件在使用子组件标签中通过字面量来传递值

    children.vueprops:{
      //字符串形式
        name:String   //接收的类型数据
        //对象形式
        age: {
            type:Number,   //接收的类型为数值
            defaule:18,    //默认值为18
            require:true  // age属性必须传递
        }
    }
    ​
    father.vue
    ​
    <Children name='jack' age=18 />
    

ref:

  • 父组件在使用子组件的时候设置ref

  • 父组件通过设置子组件ref来获取数据

    父组件
    <Children ref="foo"  //获取子组件实例,通过子组件实例就能拿到相对应的数据
    
(2)子组件传递数据给父组件

$emit触发自定义事件:

  • 子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

  • 父组件绑定监听器获取到子组件传递过来的参数

    Children.vue
    ​
    this.$emit('add',good)
    ​
    Father.vue
    ​
    <Children @add='cartAdd($event)' />
    
(3)兄弟组传值

EventBus:

  • 创建一个中央事件总线EventBus

  • 兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

  • 另一个兄弟组件通过$on监听自定义事件

    BUS.js// 创建一个中央时间总线类  
    class Bus {  
      constructor() {  
        this.callbacks = {};   // 存放事件的名字  
      }  
      $on(name, fn) {  
        this.callbacks[name] = this.callbacks[name] || [];  
        this.callbacks[name].push(fn);  
      }  
      $emit(name, args) {  
        if (this.callbacks[name]) {  
          this.callbacks[name].forEach((cb) => cb(args));  
        }  
      }  
    }  
      
    // main.js  
    Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
    // 另一种方式  
    Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  
    
    Children1.vue
    this.$bus.$emit('foo')  
    
    Children2.vue
    this.$bus.$on('foo', this.handle)  
    

parentparent或root:

  • 通过共同祖辈$parent或者$root搭建通信桥梁

兄弟组件

this.$parent.on('add',this.add)

另一个兄弟组件

this.$parent.emit('add')
(4)祖先传递数据给子孙

attrsattrs与listeners:

  • 设置批量向下属性$attrs$listeners

  • 包含了腹肌作用域中不作为prop被识别(且获取)的特性绑定(class和style除外)

  • 可以通过v-bind='$attrs'传入内部组件

    // child:并未在props中声明foo  
    <p>{{$attrs.foo}}</p>  
      
    // parent  
    <HelloWorld foo="foo"/>  
    
    // 给Grandson隔代传值,communication/index.vue  
    <Child2 msg="lalala" @some-event="onSomeEvent"></Child2>  
      
    // Child2做展开  
    <Grandson v-bind="$attrs" v-on="$listeners"></Grandson>  
      
    // Grandson使⽤  
    <div @click="$emit('some-event', 'msg from grandson')">  
    {{msg}}  
    </div>  
    

provider与inject:

  • 在祖先组件定义provide属性,返回传递的值
  • 在后代组件通过inject接收组件传递过来的值

祖先组件

provide() {
    return {
        foo:"foo"
    }
}

后代组件

inject:['foo']   //获取到祖先组件传递过来的值
(5)复杂关系的组件数据传递

vuex:

  • Vuex作用相当于一个用来存储共享变量的容器

img

  • state用来存放共享变量的地方
  • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
  • mutations用来存放修改state的方法。
  • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作。

8. 项目中你是如何解决跨域的?

跨域

跨域是基于浏览器的同源策略(Sameoriginpolicy)的一种安全手段,而同源策略可以说是浏览器最核心也最基本的安全功能了。 而非同源策略是指存在协议(prorocol)、主机(host)、端口(post)有其中一项不相同时,就会产生跨域现象。

如何解决跨域问题呢?
解决跨域的方法有很多种,比如:

  • JSONP
  • CORS
  • Proxy 在平常开发的Vue项目中,主要可以针对corsProxy两种方案

CORS:
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应 `CORS` 实现起来非常方便,只需要增加一些 `HTTP` 头,让服务器能声明允许的访问来源 只要后端实现了 `CORS`,就实现了跨域。
Proxy:
代理(Proxy)也被称为网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务来与另一个网络终端(一般为服务器)进行非直接的连接。实现一些网管、路由器等网络设备具备网络代理功能。
方案一:
`vue`项目如果是通过`vue-cli`脚手架来搭建的话,可通过`webpack`来实现一个本地服务器作为请求的代理对象。 通过该服务器转发请求到目标服务器,得到结果后再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍然会出现跨域的问题。

vue.config.js文件中,新增加以下代码

amodule.exports = {
    devServer: {
        host: '127.0.0.1',
        port: 8084,
        open: true,// vue项目启动时自动打开浏览器
        proxy: {
            '/api': { // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
                target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址
                changeOrigin: true, //是否跨域
                pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'""代替
                    '^/api': "" 
                }
            }
        }
    }
}

通过axios发送请求中,配置请求的根路径

axios.defaults.baseURL = '/api'

方案二:
通过服务端实现代理请求转发
express框架为例

var express = require('express');
const proxy = require('http-proxy-middleware')
const app = express()
app.use(express.static(__dirname + '/'))
app.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false
                      }));
module.exports = app

方案三:
通过配置nginx实现代理

server {
    listen    80;
    # server_name www.josephxia.com;
    location / {
        root  /var/www/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    location /api {
        proxy_pass  http://127.0.0.1:3000;
        proxy_redirect   off;
        proxy_set_header  Host       $host;
        proxy_set_header  X-Real-IP     $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

9.slot是什么?

1.slot是什么?: 在HTML中slot元素,作为web Components技术套件的一部分,是Web组件内的一个占位符,该占位符可以在后期使用自己的标记语言来填充。 在Vue中,slot艺名插槽,花名“占坑”,可以理解为slot在组件模块汇总占好了位置,当要使用改组件标签的时候,组件标签里面的内容就会自动填坑(替换掉组件模块中slot位置),作为承载分发内容的出口。

2.使用场景:: 通过插槽可以让用户拓展组件,去更好地服用组件和对齐做定制化处理。
如果父组件在使用到一个服用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不太明智的事情。
通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用。
比如布局组件、表格列、下拉选、弹框显示内容等。

3.slot的分类::

  • 默认插槽
  • 具名插槽
  • 作用域插槽
默认插槽

子组件用<slot>标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面

父组件在使用的时候,直接在子组件的标签内写入内容即可

子组件Child.vue

<template>
   <slot>
     <p>插槽后备的内容</p>
   </slot>
</template>

父组件

<Child>
    <div>默认插槽</div>
</Child>
具名插槽

子组件用name属性来表示插槽的名字,不传为默认插槽
父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值

子组件Child.vue

<template>
    <slot>插槽后备的内容</slot>
  <slot name="content">插槽后备的内容</slot>
</template>

父组件

<child>
    <template v-slot:default>具名插槽</template>
    <!-- 具名插槽⽤插槽名做参数 -->
    <template v-slot:content>内容...</template>
</child>
作用域插槽

子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上

父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用

子组件Child.vue

<template> 
  <slot name="footer" testProps="子组件的值">
          <h3>没传footer插槽</h3>
    </slot>
</template>

父组件

<child> 
    <!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
    <template v-slot:default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
    <template #default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
</child>

小结:

  • v-slot属性只能在<template>上使用,但在只有默认插槽时可以在组件标签上使用
  • 默认插槽名为default,可以省略default直接写v-slot
  • 缩写为#时不能不写参数,写成#default
  • 可以通过解构获取v-slot={user},还可以重命名v-slot="{user: newName}"和定义默认值v-slot="{user = '默认值'}"

10. Vue中key的原理是什么,说一说理解?

1.key 是什么? 两个实际的工作场景: 1.当我们使用v-for时,需要给单元加上key

<ul>
    <li v-for="item in items" :key="item.id">...</li>
</ul>

2.用+new Date()生成的时间戳作为key,手动强制触发重新渲染

<Comp :key="+new Date()" />

那么这背后的逻辑是什么,key的作用又是什么?

一句话来讲

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点

场景背后的逻辑: 当我们使用v-for时,需要给单元加上key

  • 如果不用key,Vue就会采用就地复用的原则:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或reuse.
  • 如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或destoryed

用`_new Date()`生成的时间戳作为`key`,手动强制触发 重新渲染 - 当拥有新值得rerender作为key 时,拥有了新的key的Comp出现了,那么旧的key Comp会被移除,新的key Comp触发渲染

2.设置key 与不设置key的区别? 举个例子:

创建一个实例,2秒后往items数组插入数据

<body>
  <div id="demo">
    <p v-for="item in items" :key="item">{{item}}</p>
  </div>
  <script src="../../dist/vue.js"></script>
  <script>
    // 创建实例
    const app = new Vue({
      el: '#demo',
      data: { items: ['a', 'b', 'c', 'd', 'e'] },
      mounted () {
        setTimeout(() => { 
          this.items.splice(2, 0, 'f')  // 
       }, 2000);
     },
   });
  </script>
</body>

在不使用key的情况,vue会进行这样的操作:

分析下整体流程:

  • 比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较C,F,相同类型的节点,进行patch,数据不同,发生dom操作
  • 比较D,C,相同类型的节点,进行patch,数据不同,发生dom操作
  • 比较E,D,相同类型的节点,进行patch,数据不同,发生dom操作
  • 循环结束,将E插入到DOM

一共发生了3次更新,1次插入操作

在使用key的情况:vue会进行这样的操作:

  • 比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作

  • 比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作

  • 比较C,F,不相同类型的节点

    • 比较E、E,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较D、D,相同类型的节点,进行patch,但数据相同,不发生dom操作

  • 比较C、C,相同类型的节点,进行patch,但数据相同,不发生dom操作

  • 循环结束,将F插入到C之前

一共发生了0次更新,1次插入操作

通过上面两个小例子,可见设置key能够大大减少对页面的DOM操作,提高了diff效率

设置key值一定能提高diff的效率吗?

其实不一定,文档中明确表示:

当Vue.js用v-for正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已经被渲染过的每个元素。

建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升

11.说说你对keep-alive的理解?

Keep-alive是什么

Keep-aliveVue的内置组件,能在组件切换的过程中将状态保留在内存中,防止重复渲染DOM
Keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是去销毁它们
Keep-alive可以设置以下props属性:

  • include--字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude--字符串或正则表达式。只有名称匹配的组件都不会被缓存
  • max--数字。最多可以缓存多少组件实例

关于`Keep-alive`的基本用法: ``` ``` 使用`includes`和`exclude`:
<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 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activateddeactivated):

  • 首次进入组件时:beforeRouteEnter > beforeCreate > createdmounted > activated > ... ... > beforeRouteLeave > deactivated
  • 再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated
使用场景

使用原则:当我们在某些场景下不需要让页面重新加载时可以使用Keep-alive
举个栗子:

当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive

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

在路由中设置keepAlive属性判断是否需要缓存

{
  path: 'list',
  name: 'itemList', // 列表页
  component (resolve) {
    require(['@/pages/item/list'], resolve)
 },
 meta: {
  keepAlive: true,
  title: '列表页'
 }
}

使用<keep-alive>

<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>
缓存后如何获取数据?

解决方案有两种:

  • beforeRouterEnter
  • actived

beforeRouterEnter
每次组件渲染的时候,都会执行`beforeRouterEnter` ``` beforeRouteEnter(to, from, next){ next(vm=>{ console.log(vm) // 每次进入路由执行 vm.getData() // 获取数据 }) }, ``` actived
在`keep-alive`缓存的组件被激活的时候,都会执行`actived`钩子
activated(){
   this.getData() // 获取数据
},

注意:服务器端渲染期间avtived不被调用

12.Vue常用的修饰符有哪些?有什么应用场景?

(1)是什么?

修饰符是用于限定类型以及类型成员的声明的一种符号。
Vue中,修饰符处理了许多关于DOM事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事,而能有更多的经历专注于程序的逻辑处理。
Vue中修饰符分为以下几种:

  • 表单修饰符
  • 事件修饰符
  • 鼠标按键修饰符
  • 键值修饰符
  • v-bind修饰符
(2)修饰符的作用

表单修饰符:
在填写表单的时候用的最多的就是input标签,指令用的最多的是v-model

  • lazy
  • trim
  • number
lazy

在填写完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息的同步

<input type="text"  v-modle.lazy= "value">
<p>{{value}}</p>
trim

自动过滤掉用户输入的首空格字符,而中间的空格不会过滤

<input type="text"  v-model.trim = "value">
number

自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值

<input v-model.number = "age" type="number">

事件修饰符:
事件修饰符是对事件捕获以及目标进行了处理,有如下修饰符:

  • stop
  • prevent
  • self
  • once
  • capture
  • passive
  • native
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="doTaht">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击

once

绑定事件以后,只能触发一下,第二次就不能触发了

<button @click.once = "shout(1)">ok</button>
capture

使事件触发从包含这个元素的顶层开始往下触发

<div @click.capture="shout(1)">
    obj1
<div @click.capture="shout(2)">
    obj2
<div @click="shout(3)">
    obj3
<div @click="shout(4)">
    obj4
</div>
</div>
</div>
</div>
// 输出结构: 1 2 4 3 
passive

在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
不要把 `.passive` 和 `.prevent` 一起使用,因为 `.prevent` 将会被忽略,同时浏览器可能会向你展示一个警告。
`passive` 会告诉浏览器你不想阻止事件的默认行为
native

让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用v-on只会监听自定义事件

<my-component v-on:click.native="doSomething"></my-component>
使用.native修饰符来操作普通HTML标签是会令事件失效的

鼠标按钮修饰符:
鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下:

  • left 左键点击
  • right 右键点击
  • middle 中键点击
<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>

键盘修饰符:
键盘修饰符是用来修饰键盘事件(onkeyuponkeydown)的,有如下:

keyCode存在很多,但vue为我们提供了别名,分为以下两种:

  • 普通键(enter、tab、delete、space、esc、up...)
  • 系统修饰键(ctrl、alt、meta、shift...)
// 只有按键为keyCode的时候才触发
<input type="text" @keyup.keyCode="shout()">

还可以通过以下方式自定义一些全局的键盘码别名

Vue.config.keyCodes.f2 = 113

v-bind修饰符:
v-bind修饰符主要是为属性进行操作,用来分别有如下:

  • async

  • prop

  • camel

async

能对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);
}

使用async需要注意以下两点:

  • 使用sync的时候,子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致
  • 注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用
  • 将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的
prop

设置自定义标签属性,避免暴露数据,防止污染HTML结构

<input id="uid" title = "title" value="1"  :index.prop="index">
camel

将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox

<svg :viewBox="viewBox"></svg>
(3)应用场景

根据每一个修饰符的功能,我们可以得到以下修饰符的应用场景:

  • .stop:阻止事件冒泡
  • .native:绑定原生事件
  • .once:事件只执行一次
  • .self :将事件绑定在自身身上,相当于阻止事件冒泡
  • .prevent:阻止默认事件
  • .caption:用于事件捕获
  • .once:只触发一次
  • .keyCode:监听特定键盘按下
  • .right:右键

13.你了解Vue的diff算法吗?

(1) 是什么?

diff算法是一种通过同层的树节点进行比较的高效算法

有两个特点:

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

diff算法有很多的应用场景,在vue中,作用于虚拟dom渲染成真实dom的新旧Vnode节点比较

(2)比较方式

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

  1. 比较只会在同层级进行, 不会跨层级比较

img

  1. 比较的过程中,循环从两边向中间收拢

img

下面举个vue通过diff算法更新的例子:

新旧VNode节点如下图所示:

第一次循环后,发现旧节点D与新节点D相同,直接复用旧节点D作为diff后的第一个真实节点,同时旧节点endIndex移动到C,新节点的 startIndex 移动到了 C

第二次循环后,同样是旧节点的末尾和新节点的开头(都是 C)相同,同理,diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 endIndex 移动到了 B,新节点的 startIndex 移动到了 E

第三次循环中,发现E没有找到,这时候只能直接创建新的真实节点 E,插入到第二次创建的 C 节点之后。同时新节点的 startIndex 移动到了 A。旧节点的 startIndex 和 endIndex 都保持不动

第四次循环中,发现了新旧节点的开头(都是 A)相同,于是 diff 后创建了 A 的真实节点,插入到前一次创建的 E 节点后面。同时旧节点的 startIndex 移动到了 B,新节点的startIndex 移动到了 B

第五次循环中,情形同第四次循环一样,因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 startIndex移动到了 C,新节点的 startIndex 移动到了 F

新节点的 startIndex 已经大于 endIndex 了,需要创建 newStartIdx 和 newEndIdx 之间的所有节点,也就是节点F,直接创建 F 节点对应的真实节点放到 B 节点后面

14.

15.