当前位置: 首页>编程语言>正文

jq 监控 input 改变 js监控数据变化

引用官方的一段话:

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。

这里的非侵入式与侵入式都是框架功能的设计模式。

什么是变化侦测?

Vue.js会自动通过状态生成DOM,并将其输出到页面上显示,这个过程叫渲染。整个渲染过程是声明式的,我们可以通过模板来描述状态(数据)与DOM之间的映射关系。但是,在运行时应用内部状态不断发生变化,对应页面就需要重新渲染。变化侦测就是来解决如何确定状态中发生了什么变化的问题。当状态发生变化时,vue.js立刻就知道了,而且在一定程度上知道哪些状态变了。因此它知道的信息更多,也就可以进行更细粒度的更新(假如有一个状态绑定着好多个依赖, 每个依赖表示一个具体的DOM节点,那么当这个状态发生变化时,向这个状态的所有依赖发送通知,让它们进行DOM更新操作)。

如何追踪数据变化?

我们先回答这个问题:在JavaScript中,如何侦测一个对象的变化?其实学过JS的人都知道,有两种方法可以侦测数据的变化:(1)使用Object.defineProperty (2)ES6的Proxy

由于ES6在浏览器中的支持度并不理想,Vue2.x采用Object.defineProperty来实现的。而到Vue3中,尤雨溪使用Proxy重写了这部分代码。 

两者同宗同源,原理和思想是不会变的。

数据劫持

指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。

const obj = {}

let val = 1
Object.defineProperty(obj, 'a', {
  get() {       // getter
    console.log('get property a')
    return val
  },
  set(newVal) { // setter
    if (val === newVal) return
    console.log(`set property a -> ${newVal}`)
    val = newVal
  }
})

console.log(obj.a); // 1
obj.a = 999
console.log(obj.a); // 999

当我们访问obj.a时,打印get property a并返回1,obj.a = 999设置新值时,打印set property a -> 999。这相当于我们自定义了obj.a取值和赋值的行为,使用自定义的gettersetter来重写了原有的行为,这也就是数据劫持的含义。但不足的是需要一个全局变量来保存这个属性a的值,所以我们可以写一个封装函数:

// value使用了参数默认值
function defineReactive(data, key, value = data[key]) {
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter() {
        return value
      },
      set: function reactiveSetter(newValue) {
        if (newValue === value) return
        value = newValue
      }
    })
  }

defineReactive(obj, a, 1)

属性描述符的两种主要形式:数据描述符和存取描述符。一个描述符只能是这两者其中之一;不能同时是两者。

 上面的defineReactive函数作用是定义一个响应式数据,进行变化追踪。只需传入data,key就行,每当从data的key中读取数据时,get函数被触发;每当往data的key中设置数据时,set函数被触发。

如何收集依赖?

如果只是对Object.defineProperty进行封装,那其实并没有实际用处。我们思考一下,之所以有观察数据的需要,目的就是当数据的属性发生变化时,能够及时通知那些曾经使用了该数据的地方。

举个例子:

<template>
 <h1>{{ name }}</h1>
</template>

该模板中使用了数据name,所以当它发生变化时,要向使用它的地方发消息。

在Vue2.0中,模板等同于组件,所以当数据发生变化时,会通知相应的组件,然后组件内部再通过虚拟DOM重新渲染。 

针对这个问题,最重要的是先收集依赖,即把用到数据name的地方收集起来,然后等数据发生变化时,把之前收集好的依赖循环触发一遍就OK了。一句话就是,在getter中收集依赖,在setter中触发依赖。

依赖收集在哪里?

既然收集地点是getter中,那么该用什么器皿去盛呢?先分析一波,我们自然想到的是用一个数组,去存储当前对象key的依赖。假设依赖是一个函数,保存在window.target上,现在把defineReactive函数稍微加工一下:

function defineReactive(data, key, value = data[key]) {
    let dep = []  // 新增
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter() {
        dep.push(window.target) // 新增
        return value
      },
      set: function reactiveSetter(newValue) {
        if (newValue === value) return
        // 新增
        for(let i = 0; i < dep.length; i++) {
            dep[i](newValue, val)
        }
        value = newValue
      }
    })
  }


这里我们新增了数组 dep ,用来存储被收集的依赖。 然后在set 被触发时,循环 dep 以触发收集到的依赖。但是这样写有点耦合,我们把依赖收集的代码封装成一个 Dep 类,它


 未完待续。。。

 参考:

           Object.defineProperty() - JavaScript | MDN


https://www.xamrdz.com/lan/5zk1938448.html

相关文章: