zoukankan      html  css  js  c++  java
  • 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 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
    pushpopshiftunshift等等,原理如下:

    先看一段代码示例:

    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方案。

  • 相关阅读:
    [Oracle整理]synonym及其应用
    [Oracle整理]Oracle之Procedure参数类型
    [Oracle整理]Oracle之数组
    RDL之矩陣
    [Oracle整理]数据类型大全
    [Oracle整理]Oracle之ROWTYPE和RECORD
    [Oracle整理]Oracle游标(显示游标&隐式游标&动态游标&参数游标)
    报表rdl嵌入网页(ASP.NET)
    Linux物理机忘记root密码
    python ftplib下载文件封装
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/12716292.html
Copyright © 2011-2022 走看看