精华内容
下载资源
问答
  • vue双向绑定的实现原理
    2022-06-08 21:58:43

    一、什么是双向绑定

            单向绑定:Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新;

            双向绑定:在单向绑定的基础上,当更新View时,Model也跟着更新。

    二、双向绑定的流程是什么

    1、new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe中;

    2、同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中;

    3、同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数;

    4、由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher;

    5、将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数。

    3、实现原理

    // 构造vue实例,对其初始化
    class Vue {
      constructor(obj) {
        this.$data = obj.data
        // 对其中的数据进行响应式处理
        Observe(this.$data)
        // 执行编译
        Compile(obj.el, this)
      }
    
    
    }
    
    // 对data进行响应式处理
    function Observe(data) {
      if (!data || typeof data !== 'object') return
      const dependency = new Dependency()
      Object.keys(data).forEach((key) => {
        let val = data[key]
        Observe(val)
        Object.defineProperty(data, key, {
          enumerable: true,
          configurable: true,
          get() {
            console.log(`获取了当前的val为${val}`)
            Dependency.temp && dependency.addSub(Dependency.temp)
            return val
          },
          set(newVal) {
            console.log(`修改了当前的val为${newVal}`)
            val = newVal
            Observe(newVal)
            // 通知依赖
            dependency.notify()
          }
        })
      })
    }
    
    // 编译模板
    function Compile(element, vm) {
      vm.$el = document.querySelector(element)
      const fragment = document.createDocumentFragment()
      let child
      while (child = vm.$el.firstChild) {
        fragment.append(child)
      }
      fragment_compile(fragment)
      function fragment_compile(node) {
        const pattern = /\{\{\s*(\S+)\s*\}\}/
        if (node.nodeType == 3) {
          const temp = node.nodeValue
          const result_reg = pattern.exec(node.nodeValue)
          if (result_reg) {
            const arr = result_reg[1].split('.')
            const val = arr.reduce((total, current) => total[current], vm.$data)
            node.nodeValue = temp.replace(pattern, val)
            new Watcher(vm, result_reg[1], (newVal) => {
              node.nodeValue = temp.replace(pattern, newVal)
            })
    
          }
          return
        }
        if (node.nodeType == 1 && node.nodeName == 'INPUT') {
          const attr = Array.from(node.attributes)
          attr.forEach(i => {
            if (i.nodeName === 'v-model') {
              const val = i.nodeValue.split('.').reduce((total, current) => total[current], vm.$data)
              node.value = val
              new Watcher(vm, i.nodeValue, (newVal) => {
                node.value = newVal
              })
              node.addEventListener('input', e => {
                const arr1 = i.nodeValue.split('.')
                const arr2 = arr1.slice(0, arr1.length - 1)
                const final = arr2.reduce((total, current) => total[current], vm.$data)
    
                final[arr1[arr1.length - 1]] = e.target.value
              })
            }
    
          })
        }
        node.childNodes.forEach((child) => { fragment_compile(child) })
      }
      // 挂载到页面上
      vm.$el.appendChild(fragment)
    }
    
    class Dependency {
      constructor() {
        // 依赖管理
        this.subscriber = []
      }
      // 添加依赖
      addSub(sub) {
        this.subscriber.push(sub)
      }
      // 派发依赖
      notify() {
        this.subscriber.forEach((sub) => { sub.update() })
      }
    }
    
    // 收集依赖,多个Watcher由一个Dep管理,要更新时由Dep统⼀通知
    class Watcher {
      constructor(vm, key, callback) {
        this.vm = vm
        this.key = key
        this.callback = callback
        // 临时触发getter,保存依赖
        // 创建实例时,把当前实例指定到Dep.target静态属性上 
        Dependency.temp = this
        // 读一下key,触发get  
        vm[key]
        // 置空 
        Dependency.temp = null
      }
      // 未来执行dom更新函数,由dep调用的
      update() {
        const val = this.key.split('.').reduce((total, current) => total[current], vm.$data)
        this.callback(val)
      }
    
    }

    更多相关内容
  • 主要介绍了Vue双向绑定实现原理与方法,结合实例形式详细分析了发布者-订阅者模式、脏值检查、数据劫持与双向绑定相关实现技巧,需要的朋友可以参考下
  • 本篇文章主要介绍了详解Vue双向数据绑定原理解析 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Vue双向绑定实现原理及代码实现 原理: Vue采用数据劫持结合发布者-订阅者模式的方法, 通过Object.defineProperty()来劫持各个属性的setter,getter属性 在数据变动的时候,通知订阅者,触发更新回调函数,重新渲染...

    Vue双向绑定实现原理及代码实现

    原理:

    • Vue采用数据劫持结合发布者-订阅者模式的方法,
    • 通过Object.defineProperty()来劫持各个属性的setter,getter属性
    • 在数据变动的时候,通知订阅者,触发更新回调函数,重新渲染视图

    数据劫持(Object.defineProperty)

    • 当Vue实例上的 data 中的数据改变时,对应的视图所用到的 data中数据也会在页面改变。
    • 所以我们需要给 data 中的所有的数据设置一个监听器,监听 data 的改变和获取
    • 一但数据改变,监听器就会触发,通知页面,要改变数据了
    Object.defineProperty(obj,key, {
        get() {
            return value;
        },
        set: newValue => {
            console.log('更新视图')
        }
    })
    

    数据劫持的实现就是给每一个 data 绑定Object.defineProprety()

    补充:Object.defineProperty()的用法

    Object.defineProperty(obj, prop, descriptor)

    该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

    参数:

    • obj : 要定义属性的对象
    • prop:要定义或修改的属性名或Symbol
    • descriptor:要定义或修改的属性描述符

    descriptor 描述符

    • 数据描述符:是一个具有值的属性,该值可以是可写的,也可以是不可写的。
    • 存取描述符:是由getter函数和setter函数所描述的属性。

    一个描述符只能是这两者其中之一,不能同时是两者

    在这里插入图片描述

    这两种描述符都是对象,他们共享以下可选键值:

    • configurable:

      当且仅当该属性的configurable键值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上删除,默认为false

    • enumerable

      当且仅当该属性的enumerable键值为true时,该属性才会出现在对象的枚举属性中,默认为false

    数据描述符还具有以下可选键值:

    • value

      该属性对象的值。可以说是任何有效的JavaScript值(数值,对象,函数等)。默认为undefined

    • writable

      当且仅当该属性的writable键值为true时,属性的值,也就是上面的value,才有可能被赋值运算符改变,默认为false

    存取描述符还具有以下可选键值:

    • get

      属性的getter函数,如果没有getter, 则为undefined。 当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 该函数的返回值会被用作属性的值。默认为 undefined

    • set

      属性的setter函数,如果没有setter,则为undefined。当属性值被修改时,会调用此函数。该方法接收一个参数(也就是被赋予的新值), 会传入赋值时的this对象。 默认 undefined

    返回值:

    被传递给函数的对象obj

    发布者-订阅者模式

    其实24种基本的设计模式中并没有发布者-订阅者模式,它是观察者模式的一个别称,但是经过时间的沉淀,它已经强大了起来,独立于观察者模式。

    什么是发布者-订阅者模式?

    • 它定义了对象间的一对多的依赖关系:当一个对象的状态发生改变时,所有依赖于它的对象都将会得到通知。

    • 发布者和订阅者是完全解耦的:发布者并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互相不认识。

    • 通过第三者(调度中心)交流:订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,也就是该事件触发时,由调度中心统一调度订阅者注册到调度中心的处理代码

    在这里插入图片描述

    vue双向绑定实现思路

    • 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

    • 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

    • 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

    在这里插入图片描述

    步骤:

    • 设置一个监听器Observer, 对属性进行劫持监听

      class Observer {
        constructor(data) {
          this.data = data;
          Object.keys(data).forEach(key=> {
            this.defineReactive(this.data, key, data[key])
          })
        }
          
        // 自定义defineReactive 方法,通过Object.defineProperty实现数据劫持  
        defineReactive(data, key, val) {
          // 一个属性key, 对应一个Dep对象, get中收集订阅者,set中通知订阅者,更新数据
          const dep = new Dep()
          // 数据劫持
          Object.defineProperty(data, key, {
            enumerabel: true,
            configurable: true,
            get() {
              // Dep.target: 当前订阅者
              if(Dep.target) { 
                // 将订阅者放到Dep消息订阅器 进行统一管理
                dep.addSub(Dep.target)
              }
              return val
            },
            set(newValue) {
                if(newValue === val) {
                    return
                }
                val = newValue
                // 由dep实例 通知变化,更新数据
                dep.notify()
            }
          })
        }
      }
      
    • 因为订阅者有多个,所以需要有一个消息订阅器Dep来专门收集这些订阅者,在监听器Observer和订阅者Watcher之间进行统一管理

      class Dep {
          constructor() {
              this.subs = []
          }
          // 收集订阅者
          addSub(sub) {
              this.subs.push(sub)
          }
          // 通知
          notify() {
              this.subs.forEach(sub => {
                  sub.update()
              })
          }
      }
      
    • 需要一个指令解析器Compiler, 对每个节点元素进行扫描和解析,对视图进行初始化

      const reg = /\{\{(.+)\}\}/   // 匹配{{}}
      class Compiler {
          constructor(el, vm) {
              this.el = document.querySelector(el)
              this.vm = vm
              this.frag = this._createFragment()
              this.el.appendChild(this.frag)
          }
          // 
          _createFragment() {
              const frag = document.createDocumentFragment()
              let child;
              while (child = this.el.firstChild) {
                  this._compile(child)
                  frag.appendChild(child)
              }
              return frag
          }
          _compile(node) {
              if(node.nodeType === 1) { // 标签节点
                  const attrs = node.attributes
                  if(attrs.hasOwnProperty('v-model')) {
                      const name = attrs['v-model'].nodeValue
                      node.addEventListener('input', e => {
                          this.vm[name] = e.target.value
                      })
                  }
              }
              if(node.nodeType === 3) { // 文本节点
                  if(reg.test(node.nodeValue)) {
                      const name = RegExp.$1.trim()
                      new Watcher(node, name, this.vm)
                  }
              }
          }
      }
      
    • 当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

      class Watcher {
          /**
           * 参数:
           * node: 节点名,例如标签节点:<input />; 文本节点:{{message}}
           * name: 要监听的data属性名
           * vm: 当前实例:vue实例
          */
          constructor(node, name, vm) {
              this.node = node;
              this.name = name;
              this.vm = vm;
              Dep.target = this
              this.update();
              Dep.target = null;
          }
          update() {
              if(this.node.nodeName === 'INPUT') {
                  // 为input标签节点更新值
                  this.node.value = this.vm[this.name]
              } else {
                  // 为文本节点更新值
                  this.node.nodeValue = this.vm[this.name]
              }
          }
      }
      

      完整代码

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <input type="text" v-model="message">
          {{message}}
        </div>
        <script>
          class Vue {
            constructor(options) {
              // 1. 保存数据
              this.$options = options;
              this.$data = options.data;
              this.$el = options.el;
      
              // 2. 将data添加到响应式系统中
              new Observer(this.$data)
              // 3. 代理this.$data的数据
              Object.keys(this.$data).forEach(key => {
                this._proxy(key)
              })
              // 4. 处理el
              new Compiler(this.$el, this)
            }
            // 设置代理
            _proxy(key) {
              Object.defineProperty(this, key, {
                configurable: true,
                enumerable: true,
                set(newValue) {
                  this.$data[key] = newValue
                },
                get() {
                  return this.$data[key]
                }
              })
            }
          }
          // 定义Observer
          class Observer {
            constructor(data) {
              this.data = data;
              Object.keys(data).forEach(key => {
                this.defineReactive(this.data, key, data[key])
              })
            }
            defineReactive(data, key, val) {
              // 一个属性key, 对应一个Dep对象
              const dep = new Dep()
              Object.defineProperty(data, key, {
                enumerable: true,
                configurable:true,
                get() {
                  // Dep.target:当前订阅者
                  if(Dep.target) {
                    // 将订阅者放到Dep消息订阅器 进行统一管理
                    dep.addSub(Dep.target)
                  }
                  return val
                },
                set(newValue) {
                  if(newValue === val) {
                    return
                  }
                  val = newValue
                  // 通知变化
                  dep.notify()
                }
              })
            }
          }
      
          // 定义 Dep
          class Dep {
            constructor() {
              this.subs = []
            }
            addSub(sub) {
              this.subs.push(sub)
            }
            notify() {
              this.subs.forEach(sub => {
                sub.update()
              })
            }
          }
      
          // 定义Watcher
          class Watcher {
            /**
             * 参数:
             * node: 节点名,例如标签节点:<input />; 文本节点:{{message}}
             * name: 要监听的data属性名
             * vm: vue实例
            */
            constructor(node, name, vm) {
              this.node = node;
              this.name = name;
              this.vm = vm;
              Dep.target = this;
              this.update();
              Dep.target = null;
            }
            update() {
              console.log(this.node.nodeName);
              if(this.node.nodeName === 'INPUT') {
              // 为input标签节点更新值
                this.node.value = this.vm[this.name]
              } else {
                // 为文本节点更新值
                this.node.nodeValue = this.vm[this.name]
              }
            }
          }
      
          const reg = /\{\{(.+)\}\}/   // 匹配{{}}
          class Compiler {
            constructor(el, vm) {
              this.el = document.querySelector(el)
              this.vm = vm
              this.frag = this._createFragment()
              this.el.appendChild(this.frag)
            }
            _createFragment() {
              const frag = document.createDocumentFragment()
              let child;
              while (child = this.el.firstChild) {
                this._compile(child)
                frag.appendChild(child)
              }
              return frag
            }
            _compile(node) {
              // console.log(node);
              if(node.nodeType === 1) { // 标签节点
                const attrs = node.attributes
                if(attrs.hasOwnProperty('v-model')) {
                  const name = attrs['v-model'].nodeValue
                  new Watcher(node, name, this.vm)
                  node.addEventListener('input', e=> {
                    this.vm[name] = e.target.value;
                  })
                }
              }
              if(node.nodeType === 3) { // 文本节点
                // console.log(reg.test(node.nodeValue));
                if(reg.test(node.nodeValue)) {
                  const name = RegExp.$1.trim()
                  // console.log(name)
                  new Watcher(node, name, this.vm)
                  // console.log(this.vm);
                }
              }
            }
          }
        </script>
        <script>
          const app = new Vue({
            el: '#app',
            data: {
              message: '你好'
            }
          })
        </script>
      </body>
      </html>
      
    展开全文
  • 主要介绍了Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定),非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
  • vue双向绑定原理

    千次阅读 2022-03-07 10:36:18
    Vue双向绑定原理 再讲双向双向绑定之前我们需要来了解下MVVM模式 MVVM(Model-View-ViewModel)是对 MVC(Model-View-Control)和 MVP(Model-View-Presenter)的进一步改进。 View:视图层(UI 用户界面) ...

    **

    Vue双向绑定的原理

    一、在讲vue双向绑定之前我们需要来了解下MVVM模式

    MVVM(Model-View-ViewModel)是对 MVC(Model-View-Control)和 MVP(Model-View-Presenter)的进一步改进。

    • View:视图层(UI 用户界面)
    • ViewModel:业务逻辑层(一切 js 可视为业务逻辑,也就是前端的日常工作)
    • Model:数据层(存储数据及对数据的处理如增删改查)
    • MVVM 将数据双向绑定(data-binding)作为核心思想,View 和 Model 之间没有联系,它们通过 ViewModel
      这个桥梁进行交互

    • Model 和 ViewModel 之间的交互是双向的,因此 View 的变化会自动同步到 Model,而 Model
      的变化也会立即反映到 View 上显示

    • 当用户操作 View,ViewModel 感知到变化,然后通知 Model 发生相应改变;反之当 Model 发生改变,ViewModel
      也能感知到变化,使 View 作出相应更新
      MVVM的核心就是ModelView-即双向绑定
      MVVM框架的的核心就是双向绑定, 其原理是通过数据劫持+发布订阅模式相结合的方式来是实现的,简单来说就是数据层发生变化的时候,可同布更新视图层,当视图层发生变化的时候,同步更新数据层

    双向绑定的核心: Object.defineProperty()

    Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

    • obj:要定义属性的对象
    • prop:要定义或修改的属性的名称或 Symbol
    • descriptor:要定义或修改的属性描述符
    • 返回值:被传递给函数的对象

    我们通过Object.defineProperty的get方法用来获取值 set方法用来拦截设置值

      var obj = {};  //定义一个空对象
        Object.defineProperty(obj, 'val', {//定义要修改对象的属性
            get: function () {
                console.log('获取对象的值')
            },
            set: function (newVal) { 
                console.log('设置对象的值:最新的值是'+newVal);
            }
        });
        obj.hello = 'hello world'
    

    js通过Object.defineProperty方法简单的实现双向绑定

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <input type="text" id="app">
        <span id="childSpan"></span>
    </body>
    <script>
        var obj = {}
        var initValue='初始值'
        Object.defineProperty(obj,'initValue',{
            get(){
                console.log('获取obj最新的值');
                return initValue
            },
            set(newVal){
                initValue = newVal
                console.log('设置最新的值');
                // 获取到最新的值  然后将最新的值赋值给我们的span
                document.getElementById('childSpan').innerHTML = initValue
                console.log(obj.initValue);
            }
        })
        document.addEventListener('keyup', function (e) {
            obj.initValue = e.target.value; //监听文本框里面的值 获取最新的值 然后赋值给obj 
        })
        
    </script>
    </html>
    

    实现双向绑定的过程

    在这里插入图片描述
    任务拆分:

    1. 将vue实例中的数据渲染到页面上
    2. 将页面上的数据变更同步到vue实例中
    3. vue实例中data数据变更 页面上数据同步变更

    传统的js来操作dom是非常繁琐的 性能及低的 比如我们要操作 dom 10次 操作第一次的时候 浏览器并不知道后面还有9次操作 所以浏览器会进行10次的重绘重排 但有的时候我们进行下一次的操作的时候 前一次的操作结果已经不准确了 那前一次的操作结果就是无用功了 白白浪费了性能。 DocuemntFragment(碎片化文档)可以把其看成一个容器 把浏览器的10次操作都扔到这个容器里 最终把最后一次的结果输出到浏览器上 这样我们页面只渲染了一次 ,并且DocuemntFragment是在内存中执行的 效率非常高。

    DocuemntFragment 拦截数据:

     function nodeToFragment(node){
            var fragment = document.createDocumentFragment();
            var child = null;
            while(child = node.firstChild){
                fragment.appendChild(child)
                
            }
            return fragment
        }
    

    实现思路:

    1. 如何将vue data 中的数据对应的绑定到文本上
    2. 如果将input中的数据 更新到vue实例的data中

    nodeToFragment方法中 我们会拦截到所有的dom 然后对dom节点的属性进行分析 比如找到v-model中的对应的变量跟vue data中的变量进行匹配,匹配到对应项,然后进行更新数据

    vue构造函数

    	// 构造函数
        function Vue(options){
            this.data = options.data
            var id = options.el
            var dom = nodeToFragment(document.getElementById(id),this)
            document.getElementById(id).appendChild(dom)
        }
        // 实例
        var vm = new Vue({
             el: 'app',
             data: {
                 text: '赵刚',
                 test:'测试',
                 name:'hbb'
             }
         })
         // dom结构
         <div id="app">
             测试双向绑定demo
             <input type="text" v-model="text" /> {{text}}
         </div>
    

    拦截dom并找到vue实例中data对应的数据,然后渲染到页面上

            //编译函数
            function compile(node, vm) {
                var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
                //如果是元素节点
                if(node.nodeType === 1) {
                    var attr = node.attributes;
                    //解析元素节点的所有属性
                    for(let i = 0; i < attr.length; i++) {
                        if(attr[i].nodeName == 'v-model') {
                            var name = attr[i].nodeValue //看看是与哪一个数据相关
                            node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
                                vm[name] = e.target.value
                            })
                            node.value = vm.data[name]; //将data中的值赋予给该node
                        }
                    }
                }
    
                //如果是文本节点
                if(node.nodeType === 3) {
                    if(reg.test(node.nodeValue)) {
                        var name = RegExp.$1; //获取到匹配的字符串
                        name = name.trim();
                        node.nodeValue = vm[name]; //将data中的值赋予给该node
                    }
                }
            }
    

    将获取到的data中的数据更新到文档碎片中

    	   function nodeToFragment(node, vm) {
                var fragment = document.createDocumentFragment();
                var child;
                while(child = node.firstChild) {
                    compile(child, vm);// 将从data中获取到的数据的dom更新到文档碎片中  这样页面上的dom就有值啦
                    fragment.appendChild(child);
                }
                return fragment
            }
    

    如此我们就完成了第一步 将data中数据渲染到页面上啦

    第二步是当页面上的数据发生变化的时候 将最新的数据更新到data中
    首先我们需要拦截data对象中的所有属性 这样当页面数据发生改变 我们会在setter函数中监听到数据变化并拿到最新的数据

    		function Vue(options) {
                this.data = options.data;
                observe(this.data, this) //观察整个data对象
                var id = options.el;
                var dom = nodeToFragment(document.getElementById(id), this)
                //处理完所有节点后,重新把内容添加回去
                document.getElementById(id).appendChild(dom)
            }
            // 拦截data中的所有属性
    		function observe(obj, vm) {
                for(let key of Object.keys(obj)) {
                    defineReactive(vm, key, obj[key]);
                }
            }
            function defineReactive(obj, key, val) {
                Object.defineProperty(obj, key, {
                    get: function() {
                    	// 获取对象的值
                        return val
                    },
                    set: function(newVal) {
                        val = newVal;
                        // 当对象属性值变更 拦截数据
                        console.log('新值' + val);
                    }
                })
            }
    

    第三步 就是当我们已经拿到最新的变更后的数据了 那么怎么通知dom 让其对应的更新成最新的数据呢 我们都知道一个页面就是一个组件 一个vue实例对象 那么我们就需要一个中间桥梁 当数据发生变化 这个中间桥梁拿到最新数据 然后告诉页面 数据更新了 你需要重新渲染了

    发布订阅模式:
    发布订阅者模式就是一种一对多的依赖关系。多个订阅者(一般是注册的函数)同时监听同一个数据对象,当这个数据对象发生变化的时候会执行一个发布事件,通过这个发布事件会通知到所有的订阅者,使它们能够自己改变对数据对象依赖的部分状态。
    一个完整的订阅发布模式,由发布者、订阅者、消息管理器三部分组成
    在这里插入图片描述
    在双向数据绑定中 每当有数据发生变化就要发布一个通知 让视图层更新 那么在set函数中就要发布订阅函数 而每一个对象属性都是订阅者

            //dep构造函数
            function Dep() {
                this.subs = [] // 观察主题添加订阅者
            }
            Dep.prototype = {
                // 添加订阅者
                addSub(sub) {
                    this.subs.push(sub)
                },
                // 发布通知
                notify() {
                    this.subs.forEach(function(sub) {
                        sub.update();
                    })
                }
            }
            function defineReactive(obj, key, val) {
                var dep = new Dep(); //观察者实例
                Object.defineProperty(obj, key, {
                    get: function() {
                        if(Dep.target) { //每一个观察着都是唯一的
                            dep.addSub(Dep.target)
                        }
                        return val
                    },
                    set: function(newVal) {
                        if(newVal === val) {
                            return
                        }
                        val = newVal;
                        console.log('新值' + val);
                        //一旦更新立马通知
                        dep.notify();
                    }
                })
            }
    

    以上我们已经完成了当数据发生变化的时候,通知所有的订阅者 数据更新了 快更新dom吧 那如何将发布者和订阅者关联起来呢?通过将每一个Watcher实例赋值给Dep.target 的全局变量,这样Watcher和Dep就有关系了,当操作完成了就需要将Dep.target 置为空 这样保证了 Watcher实例的唯一性

            // Watcher监听者
            function Watcher(vm, node, name) {
                Dep.target = this;
                this.vm = vm;
                this.node = node;
                this.name = name;
                this.update();
                Dep.target = null;
            }
    
            Watcher.prototype = {
                update() {
                    this.get();
                    this.node.nodeValue = this.value //更改节点内容的关键
                },
                get() {
                    this.value = this.vm[this.name] //触发相应的get
                }
            }
    

    如此 双向绑定原理已经介绍完成,虽然刚开始接触,还有那么一丢丢小困难,但是困难是暂时的,只要我们踏踏实实的一步一步的往下去,突然有一天你会豁然开朗的发现,原来也不过如此嘛

    最终,附上完整版的demo实例

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
        <body>
            <div id="app">
                测试双向绑定demo
                <input type="text" v-model="text" /> {{text}}
            </div>
        </body>
        <script type="text/javascript">
            //编译函数
            function compile(node, vm) {
                var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
                //如果是元素节点
                if(node.nodeType === 1) {
                    var attr = node.attributes;
                    //解析元素节点的所有属性
                    for(let i = 0; i < attr.length; i++) {
                        if(attr[i].nodeName == 'v-model') {
                            var name = attr[i].nodeValue //看看是与哪一个数据相关
                            node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
                                vm[name] = e.target.value
                            })
                            node.value = vm.data[name]; //将data中的值赋予给该node
                            node.removeAttribute('v-model')
                        }
                    }
                }
    
                //如果是文本节点
                if(node.nodeType === 3) {
                    if(reg.test(node.nodeValue)) {
                        var name = RegExp.$1; //获取到匹配的字符串
                        name = name.trim();
                        node.nodeValue = vm[name]; //将data中的值赋予给该node
                        new Watcher(vm, node, name) //绑定一个订阅者
                    }
                }
            }
    
            // 在向碎片化文档中添加节点时,每个节点都处理一下
            function nodeToFragment(node, vm) {
                var fragment = document.createDocumentFragment();
                var child;
                while(child = node.firstChild) {
                    compile(child, vm);
                    fragment.appendChild(child);
                }
                return fragment
            }
    
            //  Vue构造函数     
            //   观察data中的所有属性值,注意增添了observe
            function Vue(options) {
                this.data = options.data;
                observe(this.data, this)
                var id = options.el;
                var dom = nodeToFragment(document.getElementById(id), this)
                //处理完所有节点后,重新把内容添加回去
                document.getElementById(id).appendChild(dom)
            }
    
            //实现一个响应式监听属性的函数。一旦有赋新值就发生变化 
            function defineReactive(obj, key, val) {
                var dep = new Dep(); //观察者实例
                Object.defineProperty(obj, key, {
                    get: function() {
                        if(Dep.target) { //每一个观察着都是唯一的
                            dep.addSub(Dep.target)
                        }
                        return val
                    },
                    set: function(newVal) {
                        if(newVal === val) {
                            return
                        }
                        val = newVal;
                        console.log('新值' + val);
                        //一旦更新立马通知
                        dep.notify();
                    }
                })
            }
    
            //实现一个观察者,对于一个实例 每一个属性值都进行观察。
            function observe(obj, vm) {
                for(let key of Object.keys(obj)) {
                    defineReactive(vm, key, obj[key]);
                }
            }
    
            // Watcher监听者
            function Watcher(vm, node, name) {
                Dep.target = this;
                this.vm = vm;
                this.node = node;
                this.name = name;
                this.update();
                Dep.target = null;
            }
    
            Watcher.prototype = {
                update() {
                    this.get();
                    this.node.nodeValue = this.value //更改节点内容的关键
                },
                get() {
                    this.value = this.vm[this.name] //触发相应的get
                }
            }
    
            //dep构造函数
            function Dep() {
                this.subs = [] // 观察主题添加订阅者
            }
            Dep.prototype = {
                // 添加订阅者
                addSub(sub) {
                    this.subs.push(sub)
                },
                // 发布通知
                notify() {
                    this.subs.forEach(function(sub) {
                        sub.update();
                    })
                }
            }
    
            var vm = new Vue({
                el: 'app',
                data: {
                    text: '赵刚'
                }
            })
        </script>
    
    </html>
    
    展开全文
  • 主要为大家详细介绍了Vue.js双向绑定实现原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 具体实现过程的代码如下: 1、定义构造函数 function Vue(option){ this.$el = document.querySelector(option.el); //获取挂载节点 this.$data = option.data; this.$methods = option.methods; this.deps =
  • vue双向数据绑定实现原理

    千次阅读 2020-11-07 14:16:19
    vue双向数据绑定实现原理? vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听...

    vue双向数据绑定实现的原理?

    vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。
    当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
    vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

    #####2.Object.defineProperty()

    Object.defineProperty() 方法用于在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。注意:只能在Object 构造器对象上调用此方法,而不能在任意一个 Object 类型的实例上调用。

    Object.defineProperty(obj, prop, descriptor)
    参数

    obj:目标对象,就是要在这个对象上面定义属性
    prop:要定义或修改的属性的名称或 Symbol 。
    descriptor:要定义或修改的属性描述符。是一个对象
    通过赋值操作添加的普通属性是可枚举的,(for…in 或 Object.keys 方法),既可以改变这些属性的值,也可以删除这些属性。
    Object.defineProperty()方法可以通过对属性的配置,实现更精确的控制
    关于descriptor:对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
    数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
    存取描述符是由 getter 函数和 setter 函数所描述的属性。
    一个描述符只能是这两者其中之一;不能同时是两者。
    数据描述符和存取描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):
    configurable
    是否可以删除目标属性:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。(使用delete删除)
    默认为 false。
    enumerable
    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
    默认为 false。

    数据描述符还具有以下可选键值:
    value
    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
    默认为 undefined。
    writable
    该属性的值是否可以被重写:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
    默认为 false。
    示例:


    存取描述符还具有以下可选键值
    get 属性,是一个函数,是一种获得属性值的方法
    如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
    默认为 undefined。
    set 属性,是一个函数,是一种设置属性值的方法
    如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
    默认为 undefined。
    示例:

    <script>
    
        // 在对象中添加一个设置了存取描述符属性的示例
        var bValue = 38;
        Object.defineProperty(o, "b", {
            // 使用了方法名称缩写(ES2015 特性)
            // 下面两个缩写等价于:
            // get : function() { return bValue; },
            // set : function(newValue) { bValue = newValue; },
            get() {
                console.log('有人访问数据了');
                return bValue;
            },
            set(newValue) {
                console.log('设置了新值');
                bValue = newValue;
            },
            enumerable: true,
            configurable: true
        });
    
        o.b; // 38
        // 对象 o 拥有了属性 b,值为 38
        // 现在,除非重新定义 o.b,o.b 的值总是与 bValue 相同
    
        // 数据描述符和存取描述符不能混合使用
        Object.defineProperty(o, "conflict", {
            value: 0x9f91102,
            get() { return 0xdeadbeef; }
        });
        // 抛出错误 TypeError: value appears only in data descriptors, get appears only in accessor descriptors
    
    </script>
    

    说明:
    拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
    属性值和函数的键 value、get 和 set 字段的默认值为 undefined。

    实现vue的双向数据绑定:
    在这里插入图片描述

    展开全文
  • Vue2.0 双向绑定原理与缺陷
  • 一、实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向数据绑定的做法大致有如下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的...
  • 双向绑定,指的是vue实例中的data与其渲染的DOM元素的内容保持一致,无论谁被改变,另一方会相应的更新为相同的数据。
  • Vue双向绑定原理

    千次阅读 2022-04-27 20:29:54
    大家都知道,关于Vue双向绑定,核心是Object.defineProperty()方法,那接下来我们就简单介绍一下! Object.defineProperty() 语法:Object.defineProperty(obj,prop,descriptor) obj——要在其上定义属性的对象。...
  • vue3双向绑定实现原理

    千次阅读 2021-07-15 21:48:59
    一、vue3为什么要用proxy实现双向绑定? 1.object.defineProperty的缺点: 因为es5的object.defineProperty无法监听对象属性的删除和添加 不能监听数组的变化,除了push/pop/shift/unshift/splice/spObject.definert...
  • 一、什么是双向绑定 我们先从单向绑定切入 单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新 ...二、双向绑定原理是什么 我们都知道 Vue 是数
  • vue双向绑定实现解析

    2022-06-20 15:19:34
    通过带着读者手写简化版的vue双向绑定,了解vue双向绑定的核心原理; 从零开始实现vue的双向绑定,让大家可以更好的理解双向绑定的逻辑走向; 本项目依次实现了下面的功能 1、自定义的vue类 2、模板解析 Compile 类 3...
  • 主要为大家详细介绍了Vue 3.0双向绑定原理实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • <div id="demo"></div> <input type="text" id="inp">
  • 通过 Proxy(defineProperty)来实现对数据的监听,通过给数据和方法形成一种绑定
  • vue.js是MVVM结构的,同类的还有AngularJs;至于MVC、MVP、MVVM的比较网上已经有很多了,这样不再...这篇文章将给大家深入的介绍vue.js双向绑定实现原理,有需要的朋友们可以参考借鉴,下面跟着小编一起来看看吧。
  • Vue数据双向绑定原理解析

    千次阅读 2020-05-16 16:03:26
    Vue数据双向绑定原理解析 思路     所谓MVVM数据双向绑定,即主要是:数据变化更新视图,视图变化更新数据。如图  也就是说,输入框内容变化时,data 中的数据同步变化。即 view —> model 的变化。data ...
  • 主要为大家详细介绍了Vue数据双向绑定底层实现原理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 手写vue双向绑定实现原理

    千次阅读 2019-07-06 13:06:06
    烂大街原理:数据劫持+发布订阅者模式 (obect.defineProperty())........(此处省略8888个字节)。 话不多说上代码 HTML: <div id="app"> <div> <div v-text="myText"></div> <...
  • 主要介绍了Vue自定义组件双向绑定实现原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 知识总结:Vue实现数据双向绑定原理是什么?

    千次阅读 多人点赞 2020-07-23 17:28:31
    1、vue的数据双向绑定是通过数据劫持和发布-订阅者功能来实现实现步骤: 1.实现一个监听者Oberver来劫持并监听所有的属性,一旦有属性发生变化就通知订阅者 2.实现一个订阅者watcher来接受属性变化的通知并执行...
  • vue是一个mvvm框架,双向绑定vue的一个核心功能,所谓双向绑定就是当试图发生改变的时候传递给VM(ViewModel ),让数据得到更新,当数据发生改变的时候传给VM(ViewModel ),使得视图发生变化!概念都知道,但是vue...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,455
精华内容 8,182
关键字:

vue双向绑定的实现原理