>웹 프론트엔드 >View.js >vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

青灯夜游
青灯夜游앞으로
2022-03-03 19:41:263419검색

vue에서 양방향 바인딩을 구현하는 방법은 무엇입니까? 이 글은 vue 양방향 바인딩을 작성하는 방법을 단계별로 가르쳐서 모든 사람이 양방향 바인딩의 논리적 방향을 더 잘 이해할 수 있도록 돕겠습니다.

vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

이 글은 주로 함정을 작성하고 함정을 채우는 과정으로 청중이 양방향 바인딩 구현 방법과 양방향 바인딩의 논리적 방향을 더 잘 이해할 수 있도록 합니다. -단계별로 바인딩하는 방법. 이것은 수업 튜토리얼 기사입니다. 기사를 따르고 각 수업을 주의 깊게 관찰하면 구현하는 것이 어렵지 않습니다.

Start

메이지를 시작하시나요? 강아지를 데려오세요! !

잘못된 것 같습니다

사진으로 시작

vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

사진을 보시면 new Vue()가 두 단계로 나누어져 있는 것을 보실 수 있습니다new Vue()分为了两步走

  • 代理监听所有数据,并与Dep进行关联,通过Dep通知订阅者进行视图更新。【相关推荐:vuejs视频教程

  • 解析所有模板,并将模板中所用到的数据进行订阅,并绑定一个更新函数,数据发生改变时Dep通知订阅者执行更新函数。

接下里就是分析如何去实现,并且都需要写什么,先看一段vue的基础代码,我们从头开始分析

<div id="app">
  <input v-model="message" />
  <p>{{message}}</p>
</div>
let app = new Vue({
    el:"#app",
    data:{
      message:"测试这是一个内容"
    }
})

从上面代码我们可以看到new Vue的操作,里面携带了eldata属性,这算是最基础的属性,而在html代码中我们知道<div id="app">是vue渲染的模板根节点,所以vue要渲染页面就要去实现一个模板解析的方法<code>Compile类,解析方法中还需要去处理{{ }}v-model两个指令,除了解析模板之后我们还需要去实现数据代理也就是实现Observer

实现 Vue 类

如下代码所示,这就写完了Vue类,够简单吧,如果对class关键字不熟悉的,建议先去学习一下,从下面我们可能看到,这里实例化了两个类,一个是代理数据的类,一个是解析模板的类。

class Vue {
  constructor(options) {
    // 代理数据
    new Observer(options.data)
    // 绑定数据
    this.data = options.data
    // 解析模板
    new Compile(options.el, this)
  }
}

接着往下我们先写一个Compile类用于解析模板,我们再来分析一波,解析模板要做什么事

  • 我们要解析模板不可能直接对dom继续操作,所以我们要创建一个文档片段(虚拟dom),然后将模板DOM节点复制一份到虚拟DOM节点中,对虚拟DOM节点解析完成之后,再将虚拟DOM节点替换掉原来的DOM节点

  • 虚拟节点复制出来之后,我们要遍历整个节点树进行解析,解析过程中会对DOM的atrr属性进行遍历找到Vue相关的指令,除此之外还要对 textContent节点内容进行解析,判断是否存在双花括号

  • 将解析出来所用到的属性进行一个订阅

实现模板解析 Compile 类

下面我们将逐步实现

  • 构建Compile类,先把静态节点和Vue实例获取出来,再定义一个虚拟dom的属性用来存储虚拟dom

class Compile {
  constructor(el, vm) {
    // 获取静态节点
    this.el = document.querySelector(el);
    // vue实例
    this.vm = vm 
    // 虚拟dom
    this.fragment = null 
    // 初始化方法
    this.init()
  }
}
  • 实现初始化方法init(),该方法主要是用于创建虚拟dom和调用解析模板的方法,解析完成之后再将DOM节点替换到页面中

class Compile { 
  //...省略其他代码

  init() {
    // 创建一个新的空白的文档片段(虚拟dom)
    this.fragment = document.createDocumentFragment()
  	// 遍历所有子节点加入到虚拟dom中
    Array.from(this.el.children).forEach(child => {
      this.fragment.appendChild(child)
    })
    // 解析模板
    this.parseTemplate(this.fragment)
    // 解析完成添加到页面
    this.el.appendChild(this.fragment);
  }
}
  • 实现解析模板方法parseTemplate,主要是遍历虚拟DOM中的所有子节点并进行解析,根据子节点类型进行不同的处理。

class Compile { 
  //...省略其他代码

  // 解析模板 
  parseTemplate(fragment) {
    // 获取虚拟DOM的子节点
    let childNodes = fragment.childNodes || []
    // 遍历节点
    childNodes.forEach((node) => {
      // 匹配大括号正则表达式 
      var reg = /\{\{(.*)\}\}/;
      // 获取节点文本
      var text = node.textContent;
      if (this.isElementNode(node)) { // 判断是否是html元素
        // 解析html元素
        this.parseHtml(node)
      } else if (this.isTextNode(node) && reg.test(text)) { //判断是否文本节点并带有双花括号
        // 解析文本
        this.parseText(node, reg.exec(text)[1])
      }

      // 递归解析,如果还有子元素则继续解析
      if (node.childNodes && node.childNodes.length != 0) {
        this.parseTemplate(node)
      }
    });
  }
}
  • 根据上面的代码我们得出需要实现两个简单的判断,也就是判断是否是html元素和文字元素,这里通过获取nodeType的值来进行区分,不了解的可以直接看一下 传送门:Node.nodeType,这里还扩展了一个isVueTag方法,用于后面的代码中使用

class Compile { 
  //...省略其他代码

	// 判断是否携带 v-
  isVueTag(attrName) {
    return attrName.indexOf("v-") == 0
  }
  // 判断是否是html元素
  isElementNode(node) {
    return node.nodeType == 1;
  }
  // 判断是否是文字元素
  isTextNode(node) {
    return node.nodeType == 3;
  }
}
  • 实现parseHtml

    • 에이전트는 모든 데이터를 수신하고 Dep과 연결하며 Dep을 통해 구독자에게 뷰 업데이트를 알립니다. [관련 권장사항: vuejs 비디오 튜토리얼🎜]🎜
    • 🎜모든 템플릿을 구문 분석하고, 템플릿에 사용된 데이터를 구독하고, 업데이트 기능을 바인딩합니다. 데이터가 변경되면 Dep은 구독자에게 업데이트 기능을 실행하라고 알립니다. 🎜
    🎜다음 단계는 구현 방법과 작성해야 할 사항을 분석하는 것입니다. 먼저 vue의 기본 코드를 처음부터 분석해 보겠습니다. 우리가 볼 수 있는 코드 새로운 Vue의 작업은 가장 기본적인 속성인 eldata 속성을 ​​전달하며, html 코드에서는 는 Vue에서 렌더링한 템플릿 루트 노드이므로 Vue가 페이지를 렌더링하려면 템플릿 구문 분석 메서드 Compile 클래스와 구문 분석 방법도 필요합니다. 템플릿을 구문 분석하는 것 외에도 <code>{{ }}v-model 두 명령을 처리하려면 다음 작업도 수행해야 합니다. Observer 클래스를 구현하는 데이터 에이전트를 구현해야 합니다. 🎜🎜Vue 클래스 구현🎜🎜다음 코드와 같이 Vue가 완성됩니다. 클래스입니다. class 키워드가 익숙하지 않은 경우 먼저 학습하는 것이 좋습니다. 다음에서 두 클래스가 인스턴스화되는 것을 볼 수 있습니다. 여기서 하나는 프록시 데이터 클래스이고 다른 하나는 구문 분석 템플릿 클래스입니다. 🎜
    class Compile {
      //...省略其他代码
    
      // 解析html
      parseHtml(node) {
        // 获取元素属性集合
        let nodeAttrs = node.attributes || []
        // 元素属性集合不是数组,所以这里要转成数组之后再遍历
        Array.from(nodeAttrs).forEach((attr) => {
          // 获取属性名称
          let arrtName = attr.name;
          // 判断名称是否带有 v- 
          if (this.isVueTag(arrtName)) {
            // 获取属性值
            let exp = attr.value;
            //切割 v- 之后的字符串
            let tag = arrtName.substring(2);
            if (tag == "model") {
              // v-model 指令处理方法
              this.modelCommand(node, exp, tag)
            }
          }
        });
      }
    }
    🎜 다음으로 템플릿을 구문 분석하기 위한 Compile 클래스를 작성해 보겠습니다. 다시 분석하여 템플릿을 구문 분석하기 위해 수행해야 할 작업을 살펴보겠습니다.🎜
    • 🎜템플릿을 파싱하려면 DOM에서 직접 작업을 계속할 수 없으므로 문서 조각(가상 DOM)을 만든 다음 템플릿 DOM 노드를 가상 DOM 노드에 복사해야 합니다. , 그리고 가상 DOM 노드의 구문 분석을 완료합니다. 그 후 원본 DOM 노드를 가상 DOM 노드로 교체합니다🎜
    • 🎜가상 노드가 복사된 후 구문 분석을 위해 전체 노드 트리를 순회해야 합니다. 구문 분석 프로세스 중에 DOM의 atrr 속성을 탐색하여 Vue 관련 지침을 찾고, textContent 노드의 콘텐츠를 구문 분석하여 이중 중괄호🎜
    • 가 있는지 확인합니다. >
    • 🎜파싱된 속성 구독🎜
    🎜템플릿 파싱 Compile 클래스 구현🎜🎜아래에서 단계별로 구현해 보겠습니다🎜
    • 🎜 컴파일 클래스를 빌드하고 먼저 정적 노드와 Vue 인스턴스를 얻은 다음 가상 dom 속성을 정의하여 가상 dom을 저장하세요🎜
    class Compile {
    	//...省略其他代码
      
       // 处理model指令
      modelCommand(node, exp) {
        // 获取数据
        let val = this.vm.data[exp]
        // 解析时绑定数据
        node.value = val || ""
    
        // 监听input事件
        node.addEventListener("input", (event) => {
          let newVlaue = event.target.value;
          if (val != newVlaue) {
            // 更新data数据
            this.vm.data[exp] = newVlaue
            // 更新闭包数据,避免双向绑定失效
            val = newVlaue
          }
        })
      }
    }
      🎜주로 가상 DOM을 생성하고 구문 분석이 완료된 후 구문 분석 템플릿 메서드를 호출하는 데 사용되는 초기화 메서드 init()를 구현합니다. , DOM 노드가 페이지로 대체됩니다🎜
  • class Compile {
    	//...省略其他代码
      
      //解析文本
      parseText(node, exp) {
        let val = this.vm.data[exp]
        // 解析更新文本
        node.textContent = val || ""
      }
    }
    • 🎜분석 템플릿 메소드 parseTemplate를 구현합니다. > 주로 가상 DOM의 모든 하위 노드를 순회하여 구문 분석합니다. 하위 노드 유형에 따라 다른 처리가 수행됩니다. 🎜
    // 监听者
    class Observer {
      constructor(data) {
        this.observe(data)
      }
      // 递归方法
      observe(data) {
        //判断数据如果为空并且不是object类型则返回空字符串
        if (!data || typeof data != "object") {
          return ""
        } else {
          //遍历data进行数据代理
          Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key])
          })
        }
      }
    
      // 代理方法
      defineReactive(data, key, val) {
        // 递归子属性
        this.observe(data[key])
        Object.defineProperty(data, key, {
          configurable: true,  //可配置的属性
          enumerable: true, //可遍历的属性
          get() {
            return val
          },
          set(newValue) {
            val = newValue
          }
        })
      }
    }
    class Vue {
      constructor(options) {
        // 代理数据
        new Observer(options.data)
        console.log(options.data)
        // 绑定数据
        this.data = options.data
        // 解析模板
        new Compile(options.el, this)
      }
    }
    • 🎜 parseHtml 메소드를 구현합니다. html 코드를 주로 트래버스합니다. html 요소의 속성 🎜
    class Compile {
      //...省略其他代码
    
      // 解析html
      parseHtml(node) {
        // 获取元素属性集合
        let nodeAttrs = node.attributes || []
        // 元素属性集合不是数组,所以这里要转成数组之后再遍历
        Array.from(nodeAttrs).forEach((attr) => {
          // 获取属性名称
          let arrtName = attr.name;
          // 判断名称是否带有 v- 
          if (this.isVueTag(arrtName)) {
            // 获取属性值
            let exp = attr.value;
            //切割 v- 之后的字符串
            let tag = arrtName.substring(2);
            if (tag == "model") {
              // v-model 指令处理方法
              this.modelCommand(node, exp, tag)
            }
          }
        });
      }
    }
    • 实现modelCommand方法,在模板解析阶段来说,我们只要把 vue实例中data的值绑定到元素上,并实现监听input方法更新数据即可。

    class Compile {
    	//...省略其他代码
      
       // 处理model指令
      modelCommand(node, exp) {
        // 获取数据
        let val = this.vm.data[exp]
        // 解析时绑定数据
        node.value = val || ""
    
        // 监听input事件
        node.addEventListener("input", (event) => {
          let newVlaue = event.target.value;
          if (val != newVlaue) {
            // 更新data数据
            this.vm.data[exp] = newVlaue
            // 更新闭包数据,避免双向绑定失效
            val = newVlaue
          }
        })
      }
    }
    • 处理Text元素就相对简单了,主要是将元素中的textContent内容替换成数据即可

    class Compile {
    	//...省略其他代码
      
      //解析文本
      parseText(node, exp) {
        let val = this.vm.data[exp]
        // 解析更新文本
        node.textContent = val || ""
      }
    }

    至此已经完成了Compile类的初步编写,测试结果如下,已经能够正常解析模板

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    下面就是我们目前所实现的流程图部分

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    坑点一:

    • 在第6点modelCommand方法中并没有实现双向绑定,只是单向绑定,后续要双向绑定时还需要继续处理

    坑点二:

    • 第7点parseText方法上面的代码中并没有去订阅数据的改变,所以这里只会在模板解析时绑定一次数据

    实现数据代理 Observer 类

    这里主要是用于代理data中的所有数据,这里会用到一个Object.defineProperty方法,如果不了解这个方法的先去看一下文档传送门:

    文档:

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    Observer类主要是一个递归遍历所有data中的属性然后进行数据代理的的一个方法

    defineReactive中传入三个参数data, key, val

    datakey都是Object.defineProperty的参数,而val将其作为一个闭包变量供Object.defineProperty使用

    // 监听者
    class Observer {
      constructor(data) {
        this.observe(data)
      }
      // 递归方法
      observe(data) {
        //判断数据如果为空并且不是object类型则返回空字符串
        if (!data || typeof data != "object") {
          return ""
        } else {
          //遍历data进行数据代理
          Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key])
          })
        }
      }
    
      // 代理方法
      defineReactive(data, key, val) {
        // 递归子属性
        this.observe(data[key])
        Object.defineProperty(data, key, {
          configurable: true,  //可配置的属性
          enumerable: true, //可遍历的属性
          get() {
            return val
          },
          set(newValue) {
            val = newValue
          }
        })
      }
    }

    下面我们来测试一下是否成功实现了数据代理,在Vue的构造函数输出一下数据

    class Vue {
      constructor(options) {
        // 代理数据
        new Observer(options.data)
        console.log(options.data)
        // 绑定数据
        this.data = options.data
        // 解析模板
        new Compile(options.el, this)
      }
    }

    结果如下,我们可以看出已经实现了数据代理。

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    对应的流程图如下所示

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    坑点三:

    • 这里虽然实现了数据代理,但是按照图上来说,还需要引入管理器,在数据发生变化时通知管理器数据发生了变化,然后管理器再通知订阅者更新视图,这个会在后续的填坑过程过讲到。

    实现管理器 Dep 类

    上面我们已经实现了模板解析到初始化视图,还有数据代理。而下面要实现的Dep类主要是用于管理订阅者和通知订阅者,这里会用一个数组来记录每个订阅者,而类中也会给出一个notify方法去调用订阅者的update方法,实现通知订阅者更新功能。这里还定义了一个target属性用来存储临时的订阅者,用于加入管理器时使用。

    class Dep {
      constructor() {
        // 记录订阅者
        this.subList = []
      }
      // 添加订阅者
      addSub(sub) {
        // 先判断是否存在,防止重复添加订阅者
        if (this.subList.indexOf(sub) == -1) {
          this.subList.push(sub)
        }
      }
      // 通知订阅者
      notify() {
        this.subList.forEach(item => {
          item.update() //订阅者执行更新,这里的item就是一个订阅者,update就是订阅者提供的方法
        })
      }
    }
    // Dep全局属性,用来临时存储订阅者
    Dep.target = null

    管理器实现完成之后我们也就实现了流程图中的以下部分。要注意下面几点

    • Observer通知Dep主要是通过调用notify方法
    • Dep通知Watcher主要是是调用了Watcher类中的update方法

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.


    实现订阅者 Watcher 类

    订阅者代码相对少,但是理解起来还是有点难度的,在Watcher类中实现了两个方法,一个是update更新视图方法,一个putIn方法(我看了好几篇文章都是定义成 get 方法,可能是因为我理解的不够好吧)。

    • update:主要是调用传入的cb方法体,用于更新页面数据
    • putIn:主要是用来手动加入到Dep管理器中。
    // 订阅者
    class Watcher {
      // vm:vue实例本身
      // exp:代理数据的属性名称
      // cb:更新时需要做的事情
      constructor(vm, exp, cb) {
        this.vm = vm
        this.exp = exp
        this.cb = cb
        this.putIn()
      }
      update() {
        // 调用cb方法体,改变this指向并传入最新的数据作为参数
        this.cb.call(this.vm, this.vm.data[this.exp])
      }
      putIn() {
        // 把订阅者本身绑定到Dep的target全局属性上
        Dep.target = this
        // 调用获取数据的方法将订阅者加入到管理器中
        let val = this.vm.data[this.exp]
        // 清空全局属性
        Dep.target = null
      }
    }

    坑点四:

    • Watcher类中的putIn方法再构造函数调用后并没有加入到管理器中,而是将订阅者本身绑定到target全局属性上而已

    埋坑

    通过上面的代码我们已经完成了每一个类的构建,如下图所示,但是还是有几个流程是有问题的,也就是上面的坑点。所以下面要填坑

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    埋坑 1 和 2

    完成坑点一和坑点二,在modelCommandparseText方法中增加实例化订阅者代码,并自定义要更新时执行的方法,其实就是更新时去更新页面中的值即可

    modelCommand(node, exp) {
      
      // ...省略其他代码
      
      // 实例化订阅者,更新时直接更新node的值
      new Watcher(this.vm, exp, (value) => {
        node.value = value
      })
    }
    
    
    parseText(node, exp) {
      
      //  ...省略其他代码
      
      // 实例化订阅者,更新时直接更新文本内容
      new Watcher(this.vm, exp, (value) => {
        node.textContent = value
      })
    }

    埋坑 3

    完成坑点三,主要是为了引入管理器,通知管理器发生改变,主要是在Object.defineProperty set方法中调用dep.notify()方法

    // 监听方法
    defineReactive(data, key, val) {
      // 实例化管理器--------------增加这一行
      let dep = new Dep()
      
      // ...省略其他代码
      
        set(newValue) {
          val = newValue
          // 通知管理器改变--------------增加这一行
          dep.notify()
        }
    
    }

    埋坑 4

    完成坑点四,主要四将订阅者加入到管理器中

    defineReactive(data, key, val) {
      // ...省略其他代码
        get() {
          // 将订阅者加入到管理器中--------------增加这一段
          if (Dep.target) {
            dep.addSub(Dep.target)
          }
          return val
        },
      // ...省略其他代码
    }

    完成了坑点四可能就会有靓仔疑惑了,这里是怎么加入的呢Dep.target又是什么呢,我们不妨从头看看代码并结合下面这张图

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    至此我们已经实现了一个简单的双向绑定,下面测试一下

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    完结撒花

    总结

    本文解释的并不多,所以才是类教程文章,如果读者有不懂的地方可以在评论去留言讨论

    vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.

    (学习视频分享:vuejs教程web前端

위 내용은 vue 양방향 바인딩을 구현하는 방법을 단계별로 가르쳐주세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
이전 기사:구성요소란 무엇입니까? Vue.js 구성 요소에 대한 깊은 이해를 도와드립니다!다음 기사:구성요소란 무엇입니까? Vue.js 구성 요소에 대한 깊은 이해를 도와드립니다!

관련 기사

더보기