优雅的响应式数据

70 阅读3分钟

defineProperty

这个obj上的方法可以用来修改指定的对象属性的值

语法:Object.defineProperty(obj,property,descriptor)

参数一:obj

绑定属性的目标对象

参数二:property

绑定的属性名

参数三:descriptor

属性描述(配置),且此参数本身为一个对象

属性值1:value

设置属性默认值

属性值2:writable

设置属性是否能够修改

属性值3:enumerable

设置属性是否可以枚举,即是否允许遍历

属性值4:configurable

设置属性是否可以删除或编辑

属性值5:get

获取属性的值

属性值6:set

设置属性的值

先定义一个这个玩意

		var obj = {
			count : 0,
			list : [1,2,4]
		}
		var target = {};

通过value设置属性值

由于Object.defineProperty可以给对象定义属性,我们通过value,可以动态地将obj对象的属性动态地添加给target中。

	   for (let key in obj){
			Object.defineProperty(target,key,{
				value: obj[key]
			})
		}
		console.log(target);

通过get方法获取属性的值

【注意!】当设置get方法时,不能有value和writable方法,否则会报错
get方法的值是一个函数,此函数不需要参数

		   for (let key in obj){
				Object.defineProperty(target,key,{
					//value: obj[key],
					//writable:true,
					enumerable: false,
					configurable: true,
					get: function(){
						return obj[key]
					}
				})
			}
			console.log(target.count);

通过set方法设置属性的值

set方法的值也是一个函数,定义时会自动注入一个参数,此参数会设置属性的值

		   for (let key in obj){
				Object.defineProperty(target,key,{
					//value: obj[key],
					//writable:true,
					enumerable: false,
					configurable: true,
					get: function(){
						return obj[key]
					},
					set: function(val){
						console.log(val);//打印设置好的值
					}
				})
			}
			target.count = 10;//修改count属性的值为10

vue2.x的响应式原理

Vue2.X 响应式中使用到了 defineProperty 进行数据劫持,所以我们对它必须有一定的了解,那么我们先来了解它的使用方法把, 这里我们来使用 defineProperty来模拟 Vue 中的 data

<body>
  <div id="app"></div>
  <script>
    // 模拟 Vue的data
    let data = {
      msg: '',
    }
    // 模拟 Vue 实例
    let vm = {}
    // 对 vm 的 msg 进行数据劫持
    Object.defineProperty(vm, 'msg', {
      // 获取数据
      get() {
        return data.msg
      },
      // 设置 msg
      set(newValue) {
        // 如果传入的值相等就不用修改
        if (newValue === data.msg) return
        // 修改数据
        data.msg = newValue
        document.querySelector('#app').textContent = data.msg
      },
    })
    // 这样子就调用了 defineProperty vm.msg 的 set
    vm.msg = '1234'
  </script>
</body>

看了上面的方法只能修改一个属性,实际上我们 data 中数据不可能只有一个,我们何不定义一个方法把data中的数据进行遍历都修改成响应式

<body>
    <div id="app"></div>
	<script>
        // 模拟 Vue的data
        let data = {
            msg: '哈哈',
            age: '18',
        }
        // 模拟 Vue 实例
        let vm = {}
        // 把多个属性转化 响应式
        function proxyData() {
            // 把data 中每一项都[msg,age] 拿出来操作
            Object.keys(data).forEach((key) => {
                // 对 vm 的 属性 进行数据劫持
                Object.defineProperty(vm, key, {
                    // 可枚举
                    enumerable: true,
                    // 可配置
                    configurable: true,
                    // 获取数据
                    get() {
                        return data[key]
                    },
                    // 设置 属性值
                    set(newValue) {
                        // 如果传入的值相等就不用修改
                        if (newValue === data[key]) return
                        // 修改数据
                        data[key] = newValue
                        document.querySelector('#app').textContent = data[key]
                    },
                })
            })
        }
        // 调用方法
        proxyData(data)

	</script>
</body>

发布订阅模式

首先来说简单介绍下 一共有三个角色

发布者订阅者信号中心

在Vue 中的例子 就是EventBus onon emit

<body>
  <div id="app"></div>
  <script>
    class Vue {
      constructor() {
        // 用来存储事件
        // 存储的 例子 this.subs = { 'myclick': [fn1, fn2, fn3] ,'inputchange': [fn1, fn2] }
        this.subs = {}
      }
      // 实现 $on 方法 type是任务队列的类型 ,fn是方法
      $on(type, fn) {
        // 判断在 subs是否有当前类型的 方法队列存在
        if (!this.subs[type]) {
          // 没有就新增一个 默认为空数组
          this.subs[type] = []
        }
        // 把方法加到该类型中
        this.subs[type].push(fn)
      }
      // 实现 $emit 方法
      $emit(type) {
        // 首先得判断该方法是否存在
        if (this.subs[type]) {
          // 获取到参数
          const args = Array.prototype.slice.call(arguments, 1)
          // 循环队列调用 fn
          this.subs[type].forEach((fn) => fn(...args))
        }
      }
    }

    // 使用
    const eventHub = new Vue()
    // 使用 $on 添加一个 sum 类型的 方法到 subs['sum']中
    eventHub.$on('sum', function () {
      let count = [...arguments].reduce((x, y) => x + y)
      console.log(count)
    })
    // 触发 sum 方法
    eventHub.$emit('sum', 1, 2, 4, 5, 6, 7, 8, 9, 10)
  </script>
</body>

观察者模式

与发布订阅者不同 观察者中 发布者和订阅者(观察者)是相互依赖的 必须要求观察者订阅内容改变事件 ,而发布订阅者是由调度中心进行调度,那么观察者模式是相互依赖,下面就举个简单例子

<body>
  <div id="app"></div>
  <script>
    // 目标
    class Subject {
      constructor() {
        this.observerLists = []
      }
      // 添加观察者
      addObs(obs) {
        // 判断观察者是否有 和 存在更新订阅的方法
        if (obs && obs.update) {
          // 添加到观察者列表中
          this.observerLists.push(obs)
        }
      }
      // 通知观察者
      notify() {
        this.observerLists.forEach((obs) => {
          // 每个观察者收到通知后 会更新事件
          obs.update()
        })
      }
      // 清空观察者
      empty() {
        this.subs = []
      }
    }

    class Observer {
      // 定义观察者内容更新事件
      update() {
        // 在更新事件要处理的逻辑
        console.log('目标更新了')
      }
    }

    // 使用
    // 创建目标
    let sub = new Subject()
    // 创建观察者
    let obs1 = new Observer()
    let obs2 = new Observer()
    // 把观察者添加到列表中
    sub.addObs(obs1)
    sub.addObs(obs2)
    // 目标开启了通知 每个观察者者都会自己触发 update 更新事件
    sub.notify()
  </script>
</body>

鞭尸supershop

sendShopCar() {
				let that = this
				let arr = []
				this.tabbar.forEach(item => {
					console.log(item);
					item.list.forEach(item1 => {
						if (item1.num > 0) {
							arr.push(item1)
						}
					})
				})

  
				this.$store.state.Cartlist = arr
	
  
  			console.log(arr);
				uni.request({
					url: `https://mall.wuhanzhuangxiu01.cn/apip/cart/`,
					method: "POST",
					data: {
						"data": JSON.stringify(arr),
						"appid": that.appId,
						"openid": that.openid,
						"shopid": that.shop_info.uid
					},
					success: (res) => {
						console.log(res);
					}
				})
			},

关于为什么在shopCarts页面没有实现响应式的原因

新加入的Cartlist属性是直接显示在控制台上的,并没有被匹配一对getter和setter,这就是视图不能立即响应的原因。

直接给state里的数据使用最简单粗暴的赋值的方式添加属性,并没有通过Vue对数据进行操作,所以这样并不会是响应式。

我们需要使用Vue.set(target,propertyName,value)这个方法来实现修改响应式数据

  • target:要更改的数据(一般是响应式对象) 例:state.data
  • property:这个对象中的子对象或者属性
  • value:你想要赋的值
Vue.set(this.$store.state.Cartlist,"Cartlist",arr)