Home  >  Article  >  Web Front-end  >  Take you step by step to understand the principle of VUE responsiveness

Take you step by step to understand the principle of VUE responsiveness

青灯夜游
青灯夜游forward
2022-08-26 20:41:201985browse

In this article, we will learn about the Vue2. , if you are interested, you can watch it patiently, I hope it will be helpful to everyone!

Take you step by step to understand the principle of VUE responsivenessVue2.X Responsive Principle

1.Application of defineProperty

defineProperty

is used in

Vue2.X responsiveness for data hijacking, so we must have a certain understanding of it, so let’s first understand how to use it Well, here we use defineProperty to simulate data in Vue. (Learning video sharing: vue video tutorial)

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

You can see the above Take you step by step to understand the principle of VUE responsivenessvm.msg

data is

responsive

2.defineProperty Modify multiple parameters to be responsive

Modify multiple parameters

I saw that the above method can only modify one attribute. In fact, there cannot be only one data in our

data
. Why don’t we define a method to traverse the data in

data All modified to responsive

    <div></div>
	<script>
        // 模拟 Vue的data
        let data = {
            msg: &#39;哈哈&#39;,
            age: &#39;1Take you step by step to understand the principle of VUE responsiveness&#39;,
        }
        // 模拟 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(&#39;#app&#39;).textContent = data[key]
                    },
                })
            })
        }
        // 调用方法
        proxyData(data)

	</script>
3.Proxy

Used in

Vue3

Proxy to set responsive attributesLet’s first understand the two parameters of

Proxy

new Proxy(target,handler)

target
    : The target object to be wrapped using
  • Proxy (can be any type of object, including a native array, a function, or even another Agent)handler
  • : An object that usually has functions as attributes. The functions in each attribute respectively define the behavior of the agent
  • p when performing various operationsIn fact, the logic implemented by Vue2. # and
  • get
methods

    <div></div>
    <script>
            // 模拟 Vue data
            let data = {
                msg: &#39;&#39;,
                age: &#39;&#39;,
            }
            // 模拟 Vue 的一个实例
            // Proxy 第一个
            let vm = new Proxy(data, {
                // get() 获取值
                // target 表示需要代理的对象这里指的就是 data
                // key 就是对象的 键
                get(target, key) {
                    return target[key]
                },
                // 设置值
                // newValue 是设置的值
                set(target, key, newValue) {
                    // 也先判断下是否和之前的值一样 节省性能
                    if (target[key] === newValue) return
                    // 进行设置值
                    target[key] = newValue
                    document.querySelector(&#39;#app&#39;).textContent = target[key]
                },
            })
    </script>

4. Publish and subscribe mode

is applied in Vue responsiveness When it comes to the publish and subscribe model, let’s first understand it

First of all, let’s briefly introduce there are three rolesPublisher

,
Subscriber

,

Signal Center
For a real-life example, the author (publisher) writes an article and sends it to the Nuggets (Signal Center). The Nuggets can process the article and push it to the homepage, and then their respective bosses (subscribe (or) you can subscribe to the article

The example in Vue is

EventBus $on $emit

Then let’s simply imitate Vue’s event busThe previous code indentation of 4 units was a bit wide, here it is changed to 2 units

// 触发了set方法
vm.msg = 'haha'
// 触发了get方法
console.log(vm.msg)
5. Observer pattern

#Differences from publish-subscribe

Different from publish-subscriber The publisher and subscriber (observer) in the observer are Interdependence must require observers to subscribe to content change events, and publishing subscribers are scheduled by the dispatch center. Let's see how the observer pattern depends on each other. Here is a simple example

  <div></div>
  <script>
    class Vue {
      constructor() {
        // 用来存储事件
        // 存储的 例子 this.subs = { &#39;myclick&#39;: [fn1, fn2, fn3] ,&#39;inputchange&#39;: [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[&#39;sum&#39;]中
    eventHub.$on(&#39;sum&#39;, function () {
      let count = [...arguments].reduce((x, y) => x + y)
      console.log(count)
    })
    // 触发 sum 方法
    eventHub.$emit(&#39;sum&#39;, 1, 2, 4, 5, 6, Take you step by step to understand the principle of VUE responsiveness, Take you step by step to understand the principle of VUE responsiveness, 9, 10)
  </script>

6. Simulate the responsive principle of Vue

Here is a small and simple

Vue

that mainly implements the following functionsReceive initialization parameters, here are just a few simple examples

el

data options

    Through private methods
  • _proxyData Register data to Vue and convert it to getter
  • setterUse observer Convert the properties in data to responsiveness and add them to itself Use the observer
  • method to monitor all properties of
  • data Change to update the view through the observer patternUse compiler
  • to compile the difference expression between the above instructions on the element node and the text node
  • 1.vue. js
  • Get el here
  • data

Put data

through

_proxyData The properties are registered to Vue and converted into getter

setter

/* vue.js */

class Vue {
  constructor(options) {
    // 获取到传入的对象 没有默认为空对象
    this.$options = options || {}
    // 获取 el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    // 获取 data
    this.$data = options.data || {}
    // 调用 _proxyData 处理 data中的属性
    this._proxyData(this.$data)
  }
  // 把data 中的属性注册到 Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) => {
      // 进行数据劫持
      // 把每个data的属性 到添加到 Vue 转化为 getter setter方法
      Object.defineProperty(this, key, {
        // 设置可以枚举
        enumerable: true,
        // 设置可以配置
        configurable: true,
        // 获取数据
        get() {
          return data[key]
        },
        // 设置数据
        set(newValue) {
          // 判断新值和旧值是否相等
          if (newValue === data[key]) return
          // 设置新值
          data[key] = newValue
        },
      })
    })
  }
}

2.observer.js

在这里把 data 中的 属性变为响应式加在自身的身上,还有一个主要功能就是 观察者模式在 第 4.dep.js 会有详细的使用

/* observer.js */

class Observer {
  constructor(data) {
    // 用来遍历 data
    this.walk(data)
  }
  // 遍历 data 转为响应式
  walk(data) {
    // 判断 data是否为空 和 对象
    if (!data || typeof data !== 'object') return
    // 遍历 data
    Object.keys(data).forEach((key) => {
      // 转为响应式
      this.defineReactive(data, key, data[key])
    })
  }
  // 转为响应式
  // 要注意的 和vue.js 写的不同的是
  // vue.js中是将 属性给了 Vue 转为 getter setter
  // 这里是 将data中的属性转为getter setter
  defineReactive(obj, key, value) {
    // 如果是对象类型的 也调用walk 变成响应式,不是对象类型的直接在walk会被return
    this.walk(value)
    // 保存一下 this
    const self = this
    Object.defineProperty(obj, key, {
      // 设置可枚举
      enumerable: true,
      // 设置可配置
      configurable: true,
      // 获取值
      get() {
        return value
      },
      // 设置值
      set(newValue) {
        // 判断旧值和新值是否相等
        if (newValue === value) return
        // 设置新值
        value = newValue
        // 赋值的话如果是newValue是对象,对象里面的属性也应该设置为响应式的
        self.walk(newValue)
      },
    })
  }
}

在html中引入的话注意顺序

<script></script>
<script></script>

然后在vue.js 中使用 Observer

/* vue.js */

class Vue {
  constructor(options) {
    ...
    // 使用 Obsever 把data中的数据转为响应式
    new Observer(this.$data)
  }
  // 把data 中的属性注册到 Vue
  _proxyData(data) {
   ...
  }
}

看到这里为什么做了两个重复性的操作呢?重复性两次把 data的属性转为响应式

obsever.js 中是把 data 的所有属性 加到 data 自身 变为响应式 转成 getter setter方式

vue.js 中 也把 data的 的所有属性 加到 Vue 上,是为了以后方面操作可以用 Vue 的实例直接访问到 或者在 Vue 中使用 this 访问

使用例子:

    <div></div>
    <script></script>
    <script></script>
    <script>
      let vm = new Vue({
        el: &#39;#app&#39;,
        data: {
          msg: &#39;123&#39;,
          age: 21,
        },
      })
    </script>
  

image-20210Take you step by step to understand the principle of VUE responsiveness25162Take you step by step to understand the principle of VUE responsiveness44305

这样在Vue$data 中都存在了 所有的data 属性了 并且是响应式的

3.compiler.js

comilper.js在这个文件里实现对文本节点 和 元素节点指令编译 主要是为了举例子 当然这个写的很简单 指令主要实现 v-text v-model

/* compiler.js */

class Compiler {
  // vm 指 Vue 实例
  constructor(vm) {
    // 拿到 vm
    this.vm = vm
    // 拿到 el
    this.el = vm.$el
    // 编译模板
    this.compile(this.el)
  }
  // 编译模板
  compile(el) {
    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      // 根据不同的节点类型进行编译
      // 文本类型的节点
      if (this.isTextNode(node)) {
        // 编译文本节点
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        //元素节点
        this.compileElement(node)
      }
      // 判断是否还存在子节点考虑递归
      if (node.childNodes && node.childNodes.length) {
        // 继续递归编译模板
        this.compile(node)
      }
    })
  }
  // 编译文本节点(简单的实现)
  compileText(node) {
    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量
    // 再去Vue找这个变量赋值给node.textContent
    let reg = /\{\{(.+?)\}\}/
    // 获取节点的文本内容
    let val = node.textContent
    // 判断是否有 {{}}
    if (reg.test(val)) {
      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格
      let key = RegExp.$1.trim()
      // 进行替换再赋值给node
      node.textContent = val.replace(reg, this.vm[key])
    }
  }
  // 编译元素节点这里只处理指令
  compileElement(node) {
    // 获取到元素节点上面的所有属性进行遍历
    ![...node.attributes].forEach((attr) => {
      // 获取属性名
      let attrName = attr.name
      // 判断是否是 v- 开头的指令
      if (this.isDirective(attrName)) {
        // 除去 v- 方便操作
        attrName = attrName.substr(2)
        // 获取 指令的值就是  v-text = "msg"  中msg
        // msg 作为 key 去Vue 找这个变量
        let key = attr.value
        // 指令操作 执行指令方法
        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法
        this.update(node, key, attrName)
      }
    })
  }
  // 添加指令方法 并且执行
  update(node, key, attrName) {
    // 比如添加 textUpdater 就是用来处理 v-text 方法
    // 我们应该就内置一个 textUpdater 方法进行调用
    // 加个后缀加什么无所谓但是要定义相应的方法
    let updateFn = this[attrName + 'Updater']
    // 如果存在这个内置方法 就可以调用了
    updateFn && updateFn(node, key, this.vm[key])
  }
  // 提前写好 相应的指定方法比如这个 v-text
  // 使用的时候 和 Vue 的一样
  textUpdater(node, key, value) {
    node.textContent = value
  }
    
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
  }
    
  // 判断元素的属性是否是 vue 指令
  isDirective(attr) {
    return attr.startsWith('v-')
  }
  // 判断是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 判断是否是 文本 节点
  isTextNode(node) {
    return node.nodeType === 3
  }
}

4.dep.js

写一个Dep类 它相当于 观察者中的发布者  每个响应式属性都会创建这么一个 Dep 对象 ,负责收集该依赖属性的Watcher对象 (是在使用响应式数据的时候做的操作)

当我们对响应式属性在 setter 中进行更新的时候,会调用 Depnotify 方法发送更新通知

然后去调用 Watcher 中的 update 实现视图的更新操作(是当数据发生变化的时候去通知观察者调用观察者的update更新视图)

总的来说 在Dep(这里指发布者) 中负责收集依赖 添加观察者(这里指Wathcer),然后在 setter 数据更新的时候通知观察者

说的这么多重复的话,大家应该知道是在哪个阶段 收集依赖 哪个阶段 通知观察者了吧,下面就来实现一下吧

先写Dep

/* dep.js */

class Dep {
  constructor() {
    // 存储观察者
    this.subs = []
  }
  // 添加观察者
  addSub(sub) {
    // 判断观察者是否存在 和 是否拥有update方法
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 通知方法
  notify() {
    // 触发每个观察者的更新方法
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

obsever.js 中使用Dep

get 中添加 Dep.target (观察者)

set 中 触发 notify (通知)

/* observer.js */

class Observer {
  ...
  }
  // 遍历 data 转为响应式
  walk(data) {
   ...
  }
  // 这里是 将data中的属性转为getter setter
  defineReactive(obj, key, value) {
	...
    // 创建 Dep 对象
    let dep = new Dep()
    Object.defineProperty(obj, key, {
	  ...
      // 获取值
      get() {
        // 在这里添加观察者对象 Dep.target 表示观察者
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      // 设置值
      set(newValue) {
        if (newValue === value) return
        value = newValue
        self.walk(newValue)
        // 触发通知 更新视图
        dep.notify()
      },
    })
  }
}

5.watcher.js

**watcher **的作用 数据更新后 收到通知之后 调用 update 进行更新

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // vm 是 Vue 实例
    this.vm = vm
    // key 是 data 中的属性
    this.key = key
    // cb 回调函数 更新视图的具体方法
    this.cb = cb
    // 把观察者的存放在 Dep.target
    Dep.target = this
    // 旧数据 更新视图的时候要进行比较
    // 还有一点就是 vm[key] 这个时候就触发了 get 方法
    // 之前在 get 把 观察者 通过dep.addSub(Dep.target) 添加到了 dep.subs中
    this.oldValue = vm[key]
    // Dep.target 就不用存在了 因为上面的操作已经存好了
    Dep.target = null
  }
  // 观察者中的必备方法 用来更新视图
  update() {
    // 获取新值
    let newValue = this.vm[this.key]
    // 比较旧值和新值
    if (newValue === this.oldValue) return
    // 调用具体的更新方法
    this.cb(newValue)
  }
}

那么去哪里创建 Watcher 呢?还记得在 compiler.js中 对文本节点的编译操作吗

在编译完文本节点后 在这里添加一个 Watcher

还有 v-text v-model 指令 当编译的是元素节点 就添加一个 Watcher

/* compiler.js */

class Compiler {
  // vm 指 Vue 实例
  constructor(vm) {
    // 拿到 vm
    this.vm = vm
    // 拿到 el
    this.el = vm.$el
    // 编译模板
    this.compile(this.el)
  }
  // 编译模板
  compile(el) {
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      if (this.isTextNode(node)) {
        // 编译文本节点
        this.compileText(node)
      } 
       ...
  }
  // 编译文本节点(简单的实现)
  compileText(node) {
    let reg = /\{\{(.+)\}\}/
    let val = node.textContent
    if (reg.test(val)) {
      let key = RegExp.$1.trim()
      node.textContent = val.replace(reg, this.vm[key])
      // 创建观察者
      new Watcher(this.vm, key, newValue => {
        node.textContent = newValue
      })
    }
  }
  ...
  // v-text 
  textUpdater(node, key, value) {
    node.textContent = value
     // 创建观察者2
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // 创建观察者
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 这里实现双向绑定 监听input 事件修改 data中的属性
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
}

当 我们改变 响应式属性的时候 触发了 set() 方法 ,然后里面 发布者 dep.notify 方法启动了,拿到了 所有的 观察者 watcher 实例去执行 update 方法调用了回调函数 cb(newValue) 方法并把 新值传递到了 cb() 当中 cb方法是的具体更新视图的方法 去更新视图

比如上面的例子里的第三个参数 cb方法

new Watcher(this.vm, key, newValue => {
    node.textContent = newValue
})

还有一点要实现v-model的双向绑定

不仅要通过修改数据来触发更新视图,还得为node添加 input 事件 改变 data数据中的属性

来达到双向绑定的效果

Take you step by step to understand the principle of VUE responsiveness.测试下自己写的

到了目前为止 响应式 和 双向绑定 都基本实现了 那么来写个例子测试下

  <div>
    {{msg}} <br>
    {{age}} <br>
    <div></div>
    <input>
  </div>
  <script></script>
  <script></script>
  <script></script>
  <script></script>
  <script></script>
  <script>
    let vm = new Vue({
      el: &#39;#app&#39;,
      data: {
        msg: &#39;123&#39;,
        age: 21,
      },
    })
  </script>

Take you step by step to understand the principle of VUE responsiveness

OK 基本实现了 通过 观察者模式 来 实现 响应式原理

Take you step by step to understand the principle of VUE responsiveness.五个文件代码

这里直接把5个文件个代码贴出来 上面有的地方省略了,下面是完整的方便大家阅读

vue.js

/* vue.js */

class Vue {
  constructor(options) {
    // 获取到传入的对象 没有默认为空对象
    this.$options = options || {}
    // 获取 el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    // 获取 data
    this.$data = options.data || {}
    // 调用 _proxyData 处理 data中的属性
    this._proxyData(this.$data)
    // 使用 Obsever 把data中的数据转为响应式
    new Observer(this.$data)
    // 编译模板
    new Compiler(this)
  }
  // 把data 中的属性注册到 Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) => {
      // 进行数据劫持
      // 把每个data的属性 到添加到 Vue 转化为 getter setter方法
      Object.defineProperty(this, key, {
        // 设置可以枚举
        enumerable: true,
        // 设置可以配置
        configurable: true,
        // 获取数据
        get() {
          return data[key]
        },
        // 设置数据
        set(newValue) {
          // 判断新值和旧值是否相等
          if (newValue === data[key]) return
          // 设置新值
          data[key] = newValue
        },
      })
    })
  }
}

obsever.js

/* observer.js */

class Observer {
  constructor(data) {
    // 用来遍历 data
    this.walk(data)
  }
  // 遍历 data 转为响应式
  walk(data) {
    // 判断 data是否为空 和 对象
    if (!data || typeof data !== 'object') return
    // 遍历 data
    Object.keys(data).forEach((key) => {
      // 转为响应式
      this.defineReactive(data, key, data[key])
    })
  }
  // 转为响应式
  // 要注意的 和vue.js 写的不同的是
  // vue.js中是将 属性给了 Vue 转为 getter setter
  // 这里是 将data中的属性转为getter setter
  defineReactive(obj, key, value) {
    // 如果是对象类型的 也调用walk 变成响应式,不是对象类型的直接在walk会被return
    this.walk(value)
    // 保存一下 this
    const self = this
    // 创建 Dep 对象
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      // 设置可枚举
      enumerable: true,
      // 设置可配置
      configurable: true,

      // 获取值
      get() {
        // 在这里添加观察者对象 Dep.target 表示观察者
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      // 设置值
      set(newValue) {
        // 判断旧值和新值是否相等
        if (newValue === value) return
        // 设置新值
        value = newValue
        // 赋值的话如果是newValue是对象,对象里面的属性也应该设置为响应式的
        self.walk(newValue)
        // 触发通知 更新视图
        dep.notify()
      },
    })
  }
}

compiler.js

/* compiler.js */

class Compiler {
  // vm 指 Vue 实例
  constructor(vm) {
    // 拿到 vm
    this.vm = vm
    // 拿到 el
    this.el = vm.$el
    // 编译模板
    this.compile(this.el)
  }
  // 编译模板
  compile(el) {
    // 获取子节点 如果使用 forEach遍历就把伪数组转为真的数组
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      // 根据不同的节点类型进行编译
      // 文本类型的节点
      if (this.isTextNode(node)) {
        // 编译文本节点
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        //元素节点
        this.compileElement(node)
      }
      // 判断是否还存在子节点考虑递归
      if (node.childNodes && node.childNodes.length) {
        // 继续递归编译模板
        this.compile(node)
      }
    })
  }
  // 编译文本节点(简单的实现)
  compileText(node) {
    // 核心思想利用把正则表达式把{{}}去掉找到里面的变量
    // 再去Vue找这个变量赋值给node.textContent
    let reg = /\{\{(.+?)\}\}/
    // 获取节点的文本内容
    let val = node.textContent
    // 判断是否有 {{}}
    if (reg.test(val)) {
      // 获取分组一  也就是 {{}} 里面的内容 去除前后空格
      let key = RegExp.$1.trim()
      // 进行替换再赋值给node
      node.textContent = val.replace(reg, this.vm[key])
      // 创建观察者
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 编译元素节点这里只处理指令
  compileElement(node) {
    // 获取到元素节点上面的所有属性进行遍历
    ![...node.attributes].forEach((attr) => {
      // 获取属性名
      let attrName = attr.name
      // 判断是否是 v- 开头的指令
      if (this.isDirective(attrName)) {
        // 除去 v- 方便操作
        attrName = attrName.substr(2)
        // 获取 指令的值就是  v-text = "msg"  中msg
        // msg 作为 key 去Vue 找这个变量
        let key = attr.value
        // 指令操作 执行指令方法
        // vue指令很多为了避免大量个 if判断这里就写个 uapdate 方法
        this.update(node, key, attrName)
      }
    })
  }
  // 添加指令方法 并且执行
  update(node, key, attrName) {
    // 比如添加 textUpdater 就是用来处理 v-text 方法
    // 我们应该就内置一个 textUpdater 方法进行调用
    // 加个后缀加什么无所谓但是要定义相应的方法
    let updateFn = this[attrName + 'Updater']
    // 如果存在这个内置方法 就可以调用了
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }
  // 提前写好 相应的指定方法比如这个 v-text
  // 使用的时候 和 Vue 的一样
  textUpdater(node, key, value) {
    node.textContent = value
    // 创建观察者
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // 创建观察者
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 这里实现双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // 判断元素的属性是否是 vue 指令
  isDirective(attr) {
    return attr.startsWith('v-')
  }
  // 判断是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 判断是否是 文本 节点
  isTextNode(node) {
    return node.nodeType === 3
  }
}

dep.js

/* dep.js */

class Dep {
  constructor() {
    // 存储观察者
    this.subs = []
  }
  // 添加观察者
  addSub(sub) {
    // 判断观察者是否存在 和 是否拥有update方法
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 通知方法
  notify() {
    // 触发每个观察者的更新方法
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

watcher.js

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // vm 是 Vue 实例
    this.vm = vm
    // key 是 data 中的属性
    this.key = key
    // cb 回调函数 更新视图的具体方法
    this.cb = cb
    // 把观察者的存放在 Dep.target
    Dep.target = this
    // 旧数据 更新视图的时候要进行比较
    // 还有一点就是 vm[key] 这个时候就触发了 get 方法
    // 之前在 get 把 观察者 通过dep.addSub(Dep.target) 添加到了 dep.subs中
    this.oldValue = vm[key]
    // Dep.target 就不用存在了 因为上面的操作已经存好了
    Dep.target = null
  }
  // 观察者中的必备方法 用来更新视图
  update() {
    // 获取新值
    let newValue = this.vm[this.key]
    // 比较旧值和新值
    if (newValue === this.oldValue) return
    // 调用具体的更新方法
    this.cb(newValue)
  }
}

(学习视频分享:web前端开发编程基础视频

The above is the detailed content of Take you step by step to understand the principle of VUE responsiveness. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete