当前位置:   article > 正文

02-详细介绍Vue中的数据代理(getter)和数据响应式(setter)

02-详细介绍Vue中的数据代理(getter)和数据响应式(setter)

数据代理

MVVM

原生的Javascript代码Model和View没有分离,如果数据发生任意的改动, 接下来我们需要编写大篇幅的JS代码操作DOM元素更新视图

MVVM是目前前端开发领域当中倡导Model和View进行分离的开发思想或者架构模式,大部分主流框架如Vue和React都借鉴了这个MVVM思想

  • Model和View分离之后出现了一个核心VM负责更新,当Model发生改变之后VM自动去更新View,同理当View发生改动之后VM自动去更新Model

Vue框架中对应MVVM中的角色

  • M(Model): 对应data配置项中的数据

  • V(View): 对应容器中的模板语句, Vue实例的所有属性及Vue原型上的所有属性在模板语句中都可以直接使用

  • VM(ViewModel): 对应Vue的实例对象也是MVVM中核心部分

在这里插入图片描述

<!-- 准备容器 -->
<!-- View V-->
<div id="app">
    姓名:<input type="text" v-model="name">
</div>

<!-- vue程序 -->
<script>
    // ViewModel  vm表示Vue实例
    const vm = new Vue({
        el : '#app',
        // Model  M
        data : {
            name : 'zhangsan'
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

代理机制的原理

Vue实例可以访问的属性有三种: 以$或以_开始的属性用来和数据代理的属性区分开, Vue实例对象的原型对象的属性,Vue实例对象代理的目标对象data上的属性

  • 以$开始的属性: 可以看做是公开的属性,这些属性是供程序员使用的
  • 以_开始的属性: 可以看做是私有的属性,这些属性是Vue框架底层使用的, 程序员很少使用

对象新增属性的方法: Object.defineProperty(新增属性的对象, '新增的属性名', {新增属性的相关配置项key:value})

  • 当配置项当中有setter和getter的时候,value和writable配置项存在会报错, enumerable和configurable可以存在
属性配置项作用
value设置新增属性的值
writable设置新增属性的值是否可以被修改, true表示可以修改 , 默认是false表示不能修改
enumerable设置新增属性是否可以遍历,true表示可以遍历的,默认是false表示不可遍历,Object.keys(对象)可以遍历对象的属性
configurable设置新增属性是否可以被删除,true表示可以被删除, ,默认是false表示不可删除,delete 对象.属性可以删除对象的属性
getter方法当读取新增属性值的时候,getter()方法被自动调用, 方法返回的值就是新增属性的值
setter方法当给新增属性赋值的时候,setter(val)方法被自动调用,val参数可以接收修改后的值
<script>
    // 这是一个普通的对象
    let phone = {}

    // 临时变量
    let temp

    // 给phone对象新增一个color属性并设置setter和getter方法
    Object.defineProperty(phone, 'color', {
        //value : '太空灰',
        //writable : true,
        enumerable : false,
        configurable : false
        
        // getter方法配置项
        get : function(){
            console.log('getter方法执行');
            return temp
            // 以下这种写法会造成死循环,一直读取新增属性值一直调用get方法
            //return this.color
        },
        // setter方法配置项
        set : function(val){
            console.log('setter方法执行',val);
            temp = val
            // 以下这种写法会造成死循环,一直给新增属性赋值一直调用set方法
            //this.color = val
        }
        
        // 在ES6对象中的函数/方法:function是可以省略的
         get(){
            console.log('getter方法执行');
            return temp
        },
        // setter方法配置项
        set(val){
            console.log('setter方法执行',val);
            temp = val
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

代理机制的实现

数据代理机制就是代理对象新增一个和目标对象相同属性名的属性,通过访问代理对象的属性来间接访问目标对象的属性,本质上是不同对象的属性

<script>
    // 目标对象
    let target = {
        name : 'zhangsan'
    }

    // 代理目标对象的name属性加给代理对象新增一个name属性(属性名要一致)
    let proxy = {}

    Object.defineProperty(proxy, 'name', {
        get(){
        	// 间接访问目标对象的属性
            return target.name
        },
        set(val){
            target.name = val
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在Vue框架中代理对象是Vue的实例对象vm,目标对象是构造函数的options参数中的data对象

  • 当给data对象中新增属性时,vm会新增同名属性去代理data对象中的属性
  • vm不会给data中以_和$开始的属性名做数据代理(vm就没有该属性),所以我们就无法通过vm间接访问data对象上以_和$开始的属性
  • 在给data对象的属性在命名的时候不能以_和$这两个符号开始,防止和Vue框架自身的属性名冲突发生属性值覆盖
<script>
    const vm = new Vue({
        el : '#app',
        data : {
            msg : 'Hello Vue!',
            // 以下这两个属性就是data对象的属性,但vm不会做数据代理
            _name : 'zhangsan',
            $age : 20
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

手动实现Vue框架的数据代理机制

需求: 代理对象vm负责代理目标对象data的msg属性

<script>
    const vm = new Vue({
     data : {
            msg : 'Hello Vue!',
            name : 'zhangsan',
            age : 20
        }
    })
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<script>
// 定义一个Vue类
class Vue {
    // 定义构造函数
    constructor(options){// options是一个简单的纯粹的JS对象{},有一个data配置项对象
        // 获取data对象的所有的属性名
        Object.keys(options.data).forEach((propertyName, index) => {
            // 如string msg 0 
            console.log(typeof propertyName, propertyName, index)
            // 如果是以_和$开始的属性名就不做数据代理
            let firstChar = propertyName.charAt(0)
            if(firstChar != '_' && firstChar != '$'){
                // this就是Vue的实例对象vm
                Object.defineProperty(this, propertyName, {
                    get(){
                        // propertyName是个字符串,通过对象["属性名"]的方式读取属性值
                        return options.data[propertyName]
                    },
                    set(val){
                        options.data[propertyName] = val
                    }
                })
            }
        })
    }
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

_data和$data

<script>
	// 调用
    function Vue(options){
        this._init(options);
        Vue.prototype._init = function (options){
            // 调用方法将options合并到$options,即$options含有options的所有属性
            
            // 程序执行到这里的时候vm上还没有_data属性
    		var data = vm.$options.data;
            
            // 程序执行完这个代码之后,vm对象上多了一个_data这样的属性
            // 如果data是函数,则调用getData(data, vm)来获取data对象
            // 如果data不是函数,则直接将data对象返回给data变量, 并且同时将data对象赋值给vm._data属性
            data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
            
            // 对于Vue实例vm来说,_data和$data都直接指向了底层真实的data对象,_data是私有的用于框架内部使用的, $data是公开的是给程序员使用
            // 如果我们程序员不想走代理的方式读取data(不走getter和setter方法),可以通过_data和$data属性直接读取data当中的数据
            
            // 判断字符串是否以_和$开始,true表示是, false表示否
            function isReserved(str) {
                var c = (str + '').charCodeAt(0);
                return c === 0x24 || c === 0x5f;
            }
            
            // 数据代理: 给vm新增属性,代理_data即data对象的所有属性
            while (i--) {
                var keys = Object.keys(data);
                // key是data对象的一个属性名
                var key = keys[i];
                
                // sharedPropertyDefinition是新增属性的配置项
                var sharedPropertyDefinition = {
                    enumerable: true,
                    configurable: true,
                    get: noop,
                    set: noop
              	};
                proxy(vm, "_data", key)
                function proxy(target, sourceKey, key) { 
                    sharedPropertyDefinition.get = function proxyGetter() {
                        return this[sourceKey][key];
                    };
                    sharedPropertyDefinition.set = function proxySetter(val) {
                        this[sourceKey][key] = val;
                    };
                    Object.defineProperty(vm, key, sharedPropertyDefinition);
                }   
        }   
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

数据响应式

当修改data配置项中的数据后,页面实现自动改变/刷新的响应式效果

数据劫持

Vue的响应式是通过数据劫持机制实现的: 底层使用了Object.defineProperty方法给新增的属性都配置了setter方法对数据进行劫持

  • 当给data对象中的属性赋值时setter方法则被自动调用(vm已经代理了该属性),setter方法内主要执行修改属性值和重新渲染页面的业务代码
  • Vue会给创建Vue实例时data中所有的属性(包括属性中的属性)都添加响应式

后期通过vm.$data.属性名的方式追加的属性默认没有添加响应式处理,需要通过调用Vue.set/$set(目标对象,‘属性名’, 值)给追加的属性添加响应式处理

  • 追加响应式属性时目标对象不能直接是vm或vm.$data,所以vm和data中的属性只能在声明时提前定义好

通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的(数组本身或者数组元素的内部属性都是有响应式处理的)

  • 第一种方案:vm.$set/set(数组对象, 下标, 值)
  • 第二种方案:push(),pop(),reverse(),splice(), shift(),unshift(),sort(),Vue对这些方法进行了重写增加了响应式处理的功能
<body>
    <div id="app">
        <h1>{{msg}}</h1>
        <div>姓名:{{name}}</div>
        <div>年龄:{{age}}岁</div>
        <div>数字:{{a.b.c.e}}</div>
        <div>邮箱:{{a.email}}</div>
        <!--数组中的元素没有响应式处理-->
         <ul>
            <li v-for="user in users">{{user}}</li>
        </ul>
        <!--如果数组元素是个对象,对象的属性是有响应式处理的-->
        <ul>
            <li v-for="vip in vips" :key="vip.id">{{vip.name}}</li>
        </ul>
        
    </div>
    <script>
        const vm = new Vue({
            el : '#app',
            // Vue会给创建Vue实例时data中所有的属性(包括属性中的属性)都添加响应式
            data : {
                msg : '响应式与数据劫持',
                name : 'jackson',
                age : 20,
                a : {
                    b : {
                        c : {
                            e : 1
                        }
                    }
                }
                users : ['jack', 'lucy', 'james'],
                vips : [
                    {id:'111', name:'zhangsan'},
                    {id:'222', name:'lisi'}
                ]
            }
        })
        // vm后期追加的属性并没有添加响应式处理
        vm.$data.a.email = 'jack@126.com'
        
        // 调用以下的两个方法给后期追加的属性添加响应式处理
        //Vue.set(vm.a, 'email', 'jack@123.com')
        vm.$set(vm.a, 'email', 'jack@456.com')
        
        // 不能直接给vm/vm.$data追加响应式属性,只能在声明时提前定义好
        //Vue.set(vm, 'x', '1')
        //Vue.set(vm.$data, 'x', '1')
        
        // 直接通过数组下标修改数组中的没有响应式效果
        vm.users[0] = "李四"
        vm.vips[0] = {id:'333',name:'wangwu'}
        
        // 数组中元素的属性有响应式效果
        vm.vips[0].name = "张三"
        
        
		// 操作数组元素并具有响应式效果
        Vue.$set(vm.users,0,"李四")
        Vue.$set(vm.vips,2,{id:'333',name:'wangwu'})
        vm.users.push('王五')
        // 修改数组从0位置开始的一个元素
        vm.users.splice(0,1'杰克')
    </script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/540738
推荐阅读
相关标签
  

闽ICP备14008679号