>웹 프론트엔드 >View.js >VUE 응답성의 원리를 단계별로 이해해 보세요.

VUE 응답성의 원리를 단계별로 이해해 보세요.

青灯夜游
青灯夜游앞으로
2022-08-26 20:41:202019검색

이 기사에서는 Vue2에 대해 알아봅니다.

VUE 응답성의 원리를 단계별로 이해해 보세요.Vue2. 어느 정도 이해가 되었다면 먼저

defineProperty

를 사용하여 Vue에서

data

를 시뮬레이션해 보겠습니다. (학습 비디오 공유 : shar vue 비디오 자습서)

    <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>

당신은 위의

vm.msg 를 볼 수 있습니다. 데이터가 responsive 2.defineproperty 여러 매개 변수를 수정합니다. 여러 매개변수 수정위의 방법은 하나의 속성만 수정할 수 있습니다. 실제로 data

에 하나의 데이터만 가질 수는 없습니다. 응답 Risrreee

3.ProxyVUE 응답성의 원리를 단계별로 이해해 보세요.

스타일은 먼저 Proxy Proxy(target,handler)의 두 매개변수를 이해하기 위해 response -type 속성

을 설정하기 위해 Vue3Proxy

을 사용합니다
  • target: 프록시를 사용하여 래핑할 대상 개체(네이티브 배열, 함수 또는 심지어 모든 유형의 개체일 수 있음) 다른 에이전트)
  • handler: 일반적으로 속성으로 함수를 갖는 객체입니다. 각 속성의 함수는 각각 다양한 작업을 수행할 때 에이전트를 정의합니다. p

사실 Vue2에서 구현한 로직입니다.

get
메소드

    <div></div>
	<script>
        // 模拟 Vue的data
        let data = {
            msg: &#39;哈哈&#39;,
            age: &#39;1VUE 응답성의 원리를 단계별로 이해해 보세요.&#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>
4. 게시 및 구독 모델

게시 및 구독 모델은 Vue 응답성에 적용됩니다. 먼저 알아보겠습니다

우선 세 가지 역할이 있다는 것을 간단히 소개하겠습니다

Publisher, Subscriber,

Signal Center
실제 사례를 들어보겠습니다. Nuggets(Signal Center)로 보내면 Nuggets에서 기사를 처리하여 홈페이지에 푸시할 수 있으며, 그러면 각 상사(구독자)가 기사를 구독할 수 있습니다

Vue의 예는EventBus

$on <code>$emit

new Proxy(target,handler)

  • target :要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

其实 和 Vue2.X实现的逻辑差不多,不过实现的方法不一样

那么就放上代码了

    <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>

触发setget 的方法

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

4.发布订阅模式

在Vue 响应式中应用到了 发布订阅模式 我们先来了解下

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

发布者订阅者、  信号中心  举个现实中例子 作者(发布者)写一篇文章 发到了掘金(信号中心) ,掘金可以处理文章然后推送到了首页,然后各自大佬(订阅者)就可以订阅文章

在Vue 中的例子 就是EventBus $on $emit

那么我们就简单模仿一下 Vue 的事件总线吧

之前代码缩进4个单位有点宽,这里改成2个

  <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, VUE 응답성의 원리를 단계별로 이해해 보세요., VUE 응답성의 원리를 단계별로 이해해 보세요., 9, 10)
  </script>

5.观察者模式

与 发布订阅 的差异

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

  <div></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(&#39;目标更新了&#39;)
      }
    }

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

6.模拟Vue的响应式原理

这里来实现一个小型简单的 Vue 主要实现以下的功能

  • 接收初始化的参数,这里只举几个简单的例子 el data options
  • 通过私有方法 _proxyDatadata 注册到 Vue 中 转成getter setter
  • 使用 observerdata 中的属性转为 响应式 添加到 自身身上
  • 使用 observer 方法监听 data 的所有属性变化来 通过观察者模式 更新视图
  • 使用 compiler 编译元素节点上面指令 和 文本节点差值表达式

1.vue.js

在这里获取到 el data

그럼 간단히 Vue의 이벤트 버스를 흉내내자

이전 코드 들여쓰기 4개는 약간 넓었는데, 여기서는 2

/* 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
        },
      })
    })
  }
}
5로 변경되었습니다. 관찰자 모드

와 게시 및 구독🎜🎜🎜의 차이점은 게시 및 구독자 관찰과 다릅니다. 게시자와 구독자(관찰자)는 콘텐츠 변경 이벤트를 구독해야 하며 게시자와 구독자가 예약되어 있어야 합니다. 관찰자 패턴이 서로 어떻게 의존하는지 살펴보겠습니다. 간단한 예 🎜
/* 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)
      },
    })
  }
}
🎜🎜🎜6. Vue🎜🎜🎜🎜🎜여기에 작고 간단한 🎜Vue🎜가 있습니다. 주로 다음 기능을 구현합니다🎜🎜
  • 초기화 매개변수 수신, 여기에만 몇 가지 간단한 예를 제공합니다🎜el🎜 🎜data🎜 🎜options🎜
  • 를 통해 🎜data🎜를 🎜Vue🎜에 등록합니다. 개인 메소드 🎜_proxyData🎜를 🎜getter🎜 🎜setter🎜
  • 로 변환합니다.
  • 🎜observer🎜를 사용하여 🎜data🎜의 속성을 응답성으로 변환하고 자체에 추가합니다.
  • 사용 🎜data🎜의 모든 속성 변경을 모니터링하는 🎜observer🎜 메소드를 사용하여 관찰자 패턴을 통해 뷰를 업데이트합니다.
  • 🎜compiler🎜를 사용하여 요소 노드 위 명령어와 텍스트 노드 간의 차이 표현식을 컴파일합니다. /li>

🎜1.vue.js🎜🎜 el 데이터 가져오기🎜🎜 🎜_proxyData🎜를 통해 🎜data🎜의 속성을 🎜Vue🎜에 등록하고 이를 🎜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-20210VUE 응답성의 원리를 단계별로 이해해 보세요.25162VUE 응답성의 원리를 단계별로 이해해 보세요.44305

这样在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数据中的属性

来达到双向绑定的效果

VUE 응답성의 원리를 단계별로 이해해 보세요..测试下自己写的

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

  <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>

VUE 응답성의 원리를 단계별로 이해해 보세요.

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

VUE 응답성의 원리를 단계별로 이해해 보세요..五个文件代码

这里直接把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前端开发编程基础视频

위 내용은 VUE 응답성의 원리를 단계별로 이해해 보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제