vue相关知识汇总

有关Vue相关知识的汇总,从基础到原理

兄弟组件之间相互传递数据

首先创建一个vue的空白实例(兄弟间的桥梁)
两个兄弟之间建立一个js文件

import Vue from 'vue'
export default new Vue()

子组件 childa
发送方使用 $emit 自定义事件把数据带过去

<template>
    <div>
        <span>A组件->{{msg}}</span>
        <input type="button" value="把a组件数据传给b" @click ="send">
    </div>
</template>
<script>
import vmson from "../../../util/emptyVue"
export default {
    data(){
        return {
            msg:{
                a:'111',
                b:'222'
            }
        }
    },
    methods:{
        send:function(){
            vmson.$emit("aevent",this.msg)
        }
    }
}
</script>

自组件childb

<template>
 <div>
    <span>b组件,a传的的数据为->{{msg}}</span>
 </div>
</template>
<script>
import vmson from "../../../util/emptyVue"
export default {
    data(){
        return {
            msg:""
        }
    },
      mounted(){
        //绑定自定义事件
        vmson.$on("aevent",this.getEvnt)
    },
    methods:{
      getEvnt(val){
        console.log(val);
      }
    },
    beforeDestory(){
      //及时销毁,否则可能导致内存泄漏
      event.$off('aevent',this.getEvnt)
    }
  
}
</script>

父组件:

<template>
  <div>
  <childa></childa>    
  <br />
  <childb></childb>      
  </div>
</template>
<script>
import childa from './childa.vue';
import childb from './childb.vue';
export default {
  components:{
    childa,
    childb
  },
  data(){
      return {
          msg:""
      }
  },
  methods:{  
  }
}
</script>

父组件与自组件之间的生命周期

父组件--created
子组件--created
子组件--mounted
父组件--mounted

父组件--before update
子组件--before update
子组件--updated
父组件--updated

vue自定义组件使用 v-model 进行双向数据绑定

<input v-model="something"> 是我们常用的双向绑定方法,如果在自定义组件中如何使用v-model进行双向绑定呢?

首先我们必须要清除v-model绑定的原理如下:
其实v-model的语法糖是这样包装而成的:

<input
  :value="something"
  @:input="something = $event.target.value">

而一个组件上使用时则会简化成这样子:

<custom-input
  :value="something"
  @input="value => { something = value }">
</custom-input>

因此,对于一个带有 v-model 的组件(核心用法),它应该如下:

带有v-model的父组件通过绑定的value值(即v-model的绑定值)传给子组件,子组件通过 prop接收一个 value;
子组件利用 $emit 触发 input 事件,并传入新值value给父组件;
this.$emit('input', value);
废话不多说了,直接上栗子;

<div id="app">
  <my-component v-model="msg"></my-component>
  msg: {{msg}}
  <my-counter v-model="num"></my-counter>
  num: {{num}}
</div>

对应的JS

Vue.component('my-component', {
  template: `<div>
  <input type="text" :value="currentValue" @input="handleInput"/>
  </div>`,
  data: function () {
    return {
      currentValue: this.value //将prop属性绑定到data属性上,以便修改prop属性(Vue不允许直接修改prop属性的值)
    }
  },
  props: ['value'], //接收一个 value prop
  methods: {
    handleInput(event) {
      var value = event.target.value;
      this.$emit('input', value); //触发 input 事件,并传入新值
    }
  }
});
Vue.component("my-counter", {
  template: `<div>
  <h1>{{value}}</h1>
  <button @click="plus">+</button>
  <button @click="minu">-</button>
  </div>`,
  props: {
    value: Number //接收一个 value prop
  },
  data: function() {
    return {
      val: this.value
    }
  },
  methods: {
    plus() {
      this.val = this.val + 1
      this.$emit('input', this.val) //触发 input 事件,并传入新值
    },
    minu() {
      if(this.val>0){
        this.val = this.val-1
        this.$emit('input', this.val) //触发 input 事件,并传入新值
      }
    }
  }
});
new Vue({
    el: '#app',
  data: {
    msg: 'hello world',
    num: 0
  }
})

slot 作用域插槽

也就是父组件中用子组件中slot的参数值
1.父组件:

<template>
    <div class="wrapper">
        <Box>
            <template v-slot="slotProps">{{slotProps.slotData.name}}</template>
        </Box>
    </div>
</template>
<script>
import Box from './box.vue'
export default {
    data(){
        return {
            name:'xiao'
        }
    },
    components:{
        Box
    },
}
</script>

2.子组件

<template>
    <div>
        <slot :slotData="info">{{info.age}}</slot>
    </div>
</template>
<script>
export default {
    data() {
        return {
            info:{
                name:'xiaohua',
                age:21
            }
        }
    }
}
</script>

动态组件

比如渲染多个动态楼层:

<template>
    <div>
        <div v-for="value in myComponent" :key="value.id">
            <component :is="value.id"></component>
        </div>
    </div>
</template>
<script>
import Mytext from './mytext.vue'
import Box from './box.vue'
export default {
    data(){
       return{
           myComponent:[
                {
                    id:'Mytext'
                },
                {
                    id:'Box'
                }
            ]
       }
    },
    components: {
        Mytext,
        Box
    }
}
</script>

异步组件

<template>
    <div>
        <button @click="show = true">Load Tooltip</button>
        <div v-if="show">
            <Tooltip />
        </div>
    </div>
</template>
<script>
export default {
    data: () => ({
        show: false
    }),
    components: {
        Tooltip: () => import('./components/Tooltip')
    }
}
</script>

keep-live

例如tab组件,如果不加上 keep-alive 包含的组件就会每次重新挂载渲染【优化性能】

<template>
    <div>
        <button @click="clickMe('A')">A</button>
        <button @click="clickMe('B')">B</button>
        <keep-alive>
            <Mytext v-if="status === 'A'"/>
            <Box v-if="status === 'B'"/>
        </keep-alive>
    </div>
</template>
<script>
import Mytext from './mytext.vue'
import Box from './box.vue'
export default {
    data(){
       return{
           status:'A'
       }
    },
    components: {
        Mytext,
        Box
    },
    methods:{
        clickMe(parmas){
            this.status = parmas;
        }
    }
}
</script>

mixin

多个组件有相同的逻辑,抽离出来

<template>
    <div>
        <button @click="showCity">显示</button>
        我的城市是{{city}}
    </div>
</template>
<script>
import myMixin from './myMixin.js'
export default {
    mixins:[myMixin],
    data() {
        return {
            name:'xiaohua'
        }
    }
}
</script>

对应的mixin.js文件

export default{
  data(){
    return {
      city:'beijing'
    }
  },
  methods:{
    showCity(){
      console.log(this.name);
    }
  }
}

mixin的缺点
1.变量来源不明确,不利于阅读;
2.多个mixin可能会造成命名冲突
3.mixin和组件可能出现多对多的关系,复杂度高

VueX

1 vuex的基本概念:state、getters、action、mutation
2 用于Vue组件API: dispation、commit、mapState、mapGetters、mapActions、mapMutations

Vue-router 路由模式

  1. hash 模式:如 http://aaa.com/#/user/10
  2. H5的history模式:如 http://aaa.com/user/20;[该方式需要server支持,因此无特殊要求可以选择前者]
    server要配置所有访问页面返回 index.html 主页面;
    但是这样的话服务器将不再返回404页面,所以前端要设置好:
const router = new VueRouter({
  mode:'history',
  routes:[
    {path:'*',component:NotFoundComponent}
  ]
})

3、动态路由

const User = {
  //获取参数如 10 20
  template:'<div>user is {{$route.parmas.id}}</div>'
}
const router = new VueRouter({
  routes:[
    //动态路径参数 以冒号开头 能命中 ‘/user/10’ '/user/20' 等格式的路由
    {path:'/user/:id',component:User}
  ]
})

4、路由配置懒加载

export default new VueRouter({
  routes:[
    {
      path:'/',
      component:()=>import(/*webpackChunkName:"navigator"*/'./../components/Navgator')
    }
  ]
})

===

Vue MVVM

View--ViewModel--Model
DOM-----Vue------JS Object
V-------VM--------M

例如,
V---表示template中的html
M---表示data中的数据
VM--表示template中用到的 方法、以及js中定义的方法,是一个连接层

<template>
  <p @click="changeName">{{name}}</p>
</template>
export default{
  data(){
    return {
      name:'vue'
    }
  },
  methods:{
    changeName(){
      this.name = 'react'
    }
  }
}

Vue的响应式原理

核心API:Object.defineProperty

1.用defineProperty如何监听复杂数据

function updateView(){
  console.log('更新视图')
}
function defineReactive(target,key,value){
  /*
    监听复杂数据
    例如data中的数据:
    data(){
      return {
        info:{address:'北京'}
      }
    }
  */
  observer(value);//深度监听数据变化
  Object.defineProperty(target,key,{
    get(){
      return value
    },
    set(newValue){
      observer(newValue);//深度监听数据变化
      /*
        比如设置这样的数据
        data.age = {num:21}
        然后改变数据
        data.age.num = 22;
      */
      if(newValue !== value){
        //注意,value一直在闭包中,此处设置完之后,再get时也是
         value = newValue;
         updateView();
      }
    }
  })
}
function observer(target){
  if(typeof target !== 'object' || targe === null){
    //不是数组或者对象
    return target
  }
  //重新遍历定义各个属性
  for(let key in target){
    defineReactive(target,key,target[key])
  }
}
const data = {
  name:'zhangsan',
  info:{
    address:'bejing'
  },
  nums:[10,20,30]
}

//监听数据
observer(data)
data.name = 'lisa'
data.x = '100' //新增属性,监听不到--所以有 Vue.set
delete dta.name //删除属性,监听不到--所以有 Vue.delete
data.nums.push(40);//defineProperty无法监听到数据变化

Object.defineProperty的一些缺点(Vue3.0 启动 Proxy)

1.由上面的例子可以看出:需要深度监听,需要递归到底,一次性递归计算量大
2.无法监听到新增属性/删除属性(Vue.set;Vue.delete)
3.无法监听到数组发生变化,需要特殊处理【其实defineProperty本身可以通过数组索引值,监听到数组发生变化,但是考虑到性能问题,vue没有做这个处理】

Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push\pop\shift\unshift等等,原理如下:

先看一段代码示例:

const oldArrayProperty = Array.prototype;
//创建新对象,原型指向 oldArrayProperty,再扩展新的方法不会影响原型,这样避免污染全局的原型
const arrProto = Object.create(oldArrayProperty)
arrProto.push = function(){ //相当于Object.create(Array.prototype).push = function(){}
  console.log(100);
}
arrProto.push()
let aa = [];
aa.push(10);
console.log(aa);
const arrProto = Array.prototype;
arrProto.push = function(){//相当于Array.protptype.push = function(){}
  console.log(100);
}
arrProto.push()
let aa = [];
aa.push(10);
console.log(aa);//直接挂载到prototype,影响了array方法

所以使用类似的方法,重新定义push等方法:

//重新定义数组原型
const oldArrayProperty = Array.prototype;
const arrProto = Object.create(oldArrayProperty);
['push','pop','shift','unshifr','splice'].forEach((methodName)=>{
  arrProto[methodName] = function(){
    updateView();//触发更新视图
    oldArrayProperty[methodName].call(this,...arguments)//相当于call了数组Array原型链上的方法
  }
})
//修改observer方法:
function observer(target){
  if(typeof target !== 'object' || targe === null){
    //不是数组或者对象
    return target
  }
  if(Array.isArray(target)){
    target.__proto__ = arrProto;//将data中的数组的隐式原型赋值给新改造的 arrProto
  }
  //重新遍历定义各个属性
  for(let key in target){
    defineReactive(target,key,target[key])
  }
}

由于 JavaScript 的限制,Vue 不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue,
当你修改数组的长度时,例如:vm.items.length = newLength

以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

Proxy 有兼容性问题

Proxy兼容性不好,且无法polyfill

VDom

因为直接操作 DOM 元素非常耗费性能,所以使用js来模拟dom,待diff后,只改变变化的元素。所以是用js来模拟的虚拟dom。
原html:

<div id="div1" class="container">
  <p>vdom</p>
  <ul style="font-size:12px">
    <li>a</li>
  </ul>
</div>

转成js的DOM

{
  tag:'div',
  props:{
    className:'container',
    id:'div1'
  },
  children:[
    {
      tag:'p',
      children:'vdom'
    },
    {
      tag:'ul',
      props:{style:'font-size:12px'},
      children:{
        tag:'li',
        chidren:'a'
      }
    }
  ]
}

diff 算法

如果按照两个tree比较,时间复杂度是 O(n^3)
优化时间复杂度到O(n):
1.只比较同一层级,不做跨级比较;
2.tag不相同,则直接删掉重建,不再深度比较;
3.tag和key,两者都相同,则认为是相同节点,不再深度比较;

编译模板

  • 模板不是html,有指令、插值、JS表达式,能实现判断、循环
  • html是标签语言,只有JS才能实现判断、循环等逻辑
  • 因此,模板一定是转成某种JS代码,即编译模版
const template = `<p>{{message}}</p>`
// with(this){return _c('p',[_v(_s(message))])}
// with(this){return createELement('p',[createTextVNode(toString(message))])}
// createELement返回的是vnode
const template2 = `<p>{{flag?message:'no message found'}}</p>`;
// 所以转成了js代码
// with(this){return _c('p',[_v(_s(flag?message:'no message found'))])}

上面的转换,在使用 webpack vue-loader,会在开发环境下编译模板;如果是引用的 vue.js 的cdn方式;则是在浏览器内部编译的(所以cdn方式引入的vue.js 不支持html中 驼峰式命名)

Vue-router 的原理

1.hash变化会触发网页跳转,即浏览器的前进后退;
2.hash变化不会刷新页面,SPA必须的特点【SPA单页面应用】
3.hash永远不会提交到server端

Vue-Router核心实现原理

hash 方式

window.onhashchange = (event) => {
    console.log('old url', event.oldURL)
    console.log('new url', event.newURL)
    console.log('hash:', location.hash)
}

// 页面初次加载,获取 hash
document.addEventListener('DOMContentLoaded', () => {
    console.log('hash:', location.hash)
})

// JS 修改 url
document.getElementById('btn1').addEventListener('click', () => {
    location.href = '#/user'
})

history 方式

// 页面初次加载,获取 path
document.addEventListener('DOMContentLoaded', () => {
    console.log('load', location.pathname)
})

// 打开一个新的路由
// 【注意】用 pushState 方式,浏览器不会刷新页面
document.getElementById('btn1').addEventListener('click', () => {
    const state = { name: 'page1' }
    console.log('切换路由到', 'page1')
    history.pushState(state, '', 'page1') // 重要!!
})

// 监听浏览器前进、后退
window.onpopstate = (event) => { // 重要!!
    console.log('onpopstate', event.state, location.pathname)
}

总结

hash---window.onhashchange函数
H5 history---history.pushState 和 window.onpopstate


Vue 面试真题演练

1. 为何在 v-for 中用 key

    1. 必须用 key,且不能是 index 和 random
    1. diff算法中通过 tag 和 key 来判断,是否是 sameNode;
    1. 减少渲染次数,提升渲染性能

2.为何组件的data必须是一个函数

最后生成的代码,vue是一个类,实例化一个vue的类,如果是函数,则实例化的时候就会针对该对象返回一个函数,也就是闭包,这样对应的data在每个对象中都不同。

3. Vuex中action和mutation有何区别

    1. action 中处理异步,mutation不可以
    1. mutaion 做原子操作,也就是最小最简单的操作;
    1. action 可以整合多个 mutation

4. Vue常见性能优化方式

    1. 合理使用 v-show,v-if
    1. 合理使用 computed 【可以缓存数据,data不变,对应的计算属性不变】
    1. v-for 时加 key,以避免和 v-if 同时使用;
    1. 自定义事件、DOM事件及时销毁,导致内存泄漏,页面会越来约卡【vue定义的事件不用管,因为vue可以自动销毁】
    1. 合理使用异步组件
    1. 合理使用 keep-alive
    1. data层级不要太深,避免响应式监听数据时,递归太深。

Vue3--Proxy实现响应式

    1. Proxy和Reflect是相对应的,Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法;
const proxyData = new Proxy(data,{
  get(targe,key,receiver){//receiver 是 proxyData
    const result = Reflect.get(targe,key,receiver)
    console.log('get',key);
    return result;//返回结果
  },
  set(target,key,val,receiver){
    const result = Reflect.set(target,key,val,receiver)
    return result;//返回是否设置成功
  },
  deleteProperty(target,key){
    const result = Refleect.deleteProperty(target,key);
    return result;//是否删除成功
  }
})
    1. 规范化、标准化、函数式;
      例如,判断对象中是否有某个key
const obj = {a:100,b:200};
'a' in obj;//true
Reflect.has(obj,'a');//true
//再如删除某个元素
delete obj.a;
Reflect.deleteProperty(obj,'b')
const data = {
  name:'zhangsan',
  age:20
}
    1. 替换掉 Object 上的工具函数;
//获取key值
const obj = {a:10,b:20};
//原来的方法。因为Object是个对象,不应该集成很多方法;
Object.getOwnPropertyNames(obj);//['a','b']
//现在改到 Reflect,逐渐替换Object上的方法
Reflect.ownKeys(obj);//['a','b']

proxy响应式:

// 创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        // 不是对象或数组,则返回
        return target
    }
    // 代理配置
    const proxyConf = {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('get', key) // 监听
            }
            const result = Reflect.get(target, key, receiver)
            // 深度监听,get到那一层级,才reactive到那一层级,不像defineReactive,在函数顶部递归循环
            // 性能如何提升的?
            return reactive(result)
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true
            }
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('已有的 key', key)
            } else {
                console.log('新增的 key', key)
            }
            const result = Reflect.set(target, key, val, receiver)
            console.log('set', key, val)
            // console.log('result', result) // true
            return result // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key)
            console.log('delete property', key)
            // console.log('result', result) // true
            return result // 是否删除成功
        }
    }
    // 生成代理对象
    const observed = new Proxy(target, proxyConf)
    return observed
}
// 测试数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        city: 'beijing',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
}
const proxyData = reactive(data)
  1. Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。

由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作。

  1. Object.defineProperty对新增属性需要手动进行Observe。

由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。

也正是因为这个原因,使用vue给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
如果采用 proxy 实现, Proxy 通过 set(target, propKey, value, receiver) 拦截对象属性的设置,是可以拦截到对象的新增属性的。
不止如此, Proxy 对数组的方法也可以监测到,不需要像上面vue2.x源码中那样进行 hack

总结:

Object.defineProperty 对数组和对象的表现一直,并非不能监控数组下标的变化,vue2.x中无法通过数组索引来实现响应式数据的自动更新是vue本身的设计导致的,不是 defineProperty 的锅。
Object.defineProperty 和 Proxy 本质差别是,defineProperty 只能对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动 Observe 的问题。
Proxy 作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的polifill方案。

posted @ 2020-04-16 22:20  小猪冒泡  阅读(471)  评论(0编辑  收藏  举报