精华内容
下载资源
问答
  • 本文实例讲述了Vue 的双向绑定原理与用法。分享给大家供大家参考,具体如下: Vue 中需要输入什么内容的时候,自然会想到使用 <input v-model=xxx /> 的方式来实现双向绑定。下面是一个最简单的示例 <h2>...
  • proxy方法 ...可查看Vue 2.0双向绑定原理的实现 具体实现过程的代码如下: 1、定义构造函数 function Vue(option){ this.$el = document.querySelector(option.el); //获取挂载节点 this.$data =
  • 本篇文章主要介绍了浅谈vue,angular,react数据双向绑定原理分析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...
  • vue,mvvm双向绑定原理和实现 1.实现compile,进行模板的编译。指令和watcher更新函数; 2.实现Observe,监听所有的数据,并对变化数据发布通知; 3.实现watcher,作为一个中枢,接收到observe发来的通知,并执行...
  • 双向绑定原理 通过Object.defineproperty()重新定义对象属性的set方法、get方法来实现的,从这个属性中取值时会触发get方法,改变这个属性时会触发set方法,所以我们只要将一些需要更新view的方法放在这里面就...
  • 本篇文章主要介绍了浅谈AngularJs 双向绑定原理(数据绑定机制),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Vue数据双向绑定原理是通过数据劫持结合发布者-订阅者模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图 MVC模式 以往的...
  • 双向绑定原理

    千次阅读 2019-05-31 18:46:32
    希望了解双向绑定原理 从MVC、MVVM说起 参考阮一峰老师的文章:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html 流程:View根据Model展示页面,当页面发生操作时(commander),View传递指令到Controller...

    适合读者:
    了解 MV* 架构模式
    希望了解双向绑定原理

    从MVC、MVVM说起

    参考阮一峰老师的文章:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

    MVC
    流程:View根据Model展示页面,当页面发生操作时(commander),View传递指令到Controller层,Controller层根据commander对Model做出修改,Model发生变化后,通知View重新渲染。
    在这里插入图片描述
    流程:Model传递到ViewModel层进行对应逻辑运算后同步到View上,同时View与ViewModel绑定,View的任何变动都反映在ViewModel上,ViewModel变更时,会通知Model做出变更。View与Model层不发生通信。

    单向绑定与双向绑定

    从上边MVC与MVVM模型图可以看出,MVC模型根据Model渲染View,Model向View单向通信,理解为单向绑定;
    MVVM模型View与ViewModel绑定,双方的任何变更均能反映到另一方,View的任何变更由ViewModel向Model通报,同时Model的变更向ViewModel通报,ViewModel与View同步。

    双向绑定的应用场景

    一句话概括: 双向绑定试用于所有UI控件,对非UI控件,当组件间通信时,可采用双向绑定,也可采用单向绑定。
    参考文章:单、双向绑定分析对比 https://www.wang1314.com/doc/topic-20437069-1.html

    模仿双向绑定

    <template>
      <div class="hello">
        <p> 双向绑定</p>
        <h1>{{price}}</h1>
        <h1>{{ tax }}</h1>
        <h1>{{ total }}</h1>
        <input v-model="price"/>
        <button @click = "clickBtn()">改变价格</button>
      </div>
    </template>
    <script>
    	export default {
    	  name: 'HelloWorld',
    	  data: function (){
    	    return {
    	      price: 10,
    	      count: 10
    	    }
    	  },
    	  computed: {
    	    total: function(){
    	      return this.price * this.count
    	    },
    	    tax: function(){
    	    	return pirce * (1+0.05)
    	    }
    	  },
    	  methods: {
    	    clickBtn: function() {
    	      this.price +=1;
    	    }
    	  }
    	}
    </script>
    
    

    问题1、修改price的值,为什么会更改total的值? 如何把这两个变量(属性)关联起来?

    计算属性:total
    total: function(){
          return this.price * this.count
        }
    total本质上是函数,在该函数调用过程中,会访问this.price和this.count的值
    在访问this.price属性的时候,可以将该方法保存起来,当this.price变更时,我们调用该方法,刷新total的值	
    

    具体实现分为两步:
    1、存储访问器属性对应的方法
    2、修改data中对应属性(依赖属性)时,调用存储的方法,刷新其他属性值

    存储访问器属性
    let price = 10
    let count = 10
    let total = price * count
    let target = null //全局属性, 用来替换访问器属性
    //第一步前奏
    class memoProperty {
    	constructor(){
    		this.subscribes = []
    	}
    	bind (){
    		target !== null && typeof target == 'function' && !this.subscribes.includes(target) ? 
    			this.subscribes.push(target) : null 
    	}
    	notify (){
    		this.subscribes.forEach(target => target())
    	}
    }
    console.log(total) 				// 100
    const memo = new memoProperty()
    //taget 表示计算属性对应的方法
    target = function(){
    	total = price * count
    }
    //第一步执行
    memo.bind()
    //第二步执行
    price = 15
    memo.notify()
    console.log(total)				//150
    price = 20
    memo.notify()
    console.log(total)				//300
    

    // 可以这样写的前提是我们知道total依赖price和count,是我们主动把total与price和count联系起来
    //怎么样才能做到当我们在执行function (){total = price * count }这句代码的时候,自动的把total与price和count联系起来?

    解决方案 Object.defineProperty

    /*简单介绍一下Object.defineProperty
    该方法可以对象的属性进行数据属性和访问器属性进行定义,不了解数据属性和访问器属性的同学参考MDN文档。
    对其访问器属性进行设置时,如下例*/
    Object.defineProperty(obj, 'name',{
        get: function(){
            console.log("name")
        },
        set: function(value){
            console.log("newValue: ", value)
        }
    })
    obj.name			//name
    obj.name = "hahah"	//newValue: hahah
    当获取或者更改该对象的属性时,调用对应的回调函数
    

    那么问题的具体解决思路为:
    1、主动计算访问器属性,即调用访问器属性方法target
    2、调用时,会访问price 和count变量,在get方法中存储调用方法target
    3、当price属性变更时,出发访问器set属性,此时调用对应的target,更新依赖于price的属性
    具体实现如下:

    let price = 10
    let count = 10
    let data = {price, count}
    let total = price * count
    let target = null //全局属性, 用来替换访问器属性
    //第一步前奏
    class memoProperty {
    	constructor(){
    		this.subscribes = []
    	}
    	bind (){
    		target !== null && typeof target == 'function' && !this.subscribes.includes(target) ? 
    			this.subscribes.push(target) : null 
    	}
    	notify (){
    		this.subscribes.forEach(target => target())
    	}
    }
    Object.keys(data).forEach( key =>{
    	//实例化存储类
    	const memo = new memoProperty()
    	let initValue = data[key]
    	Object.defineProperty(data, key,{
    		get: function(){
    			memo.bind()
                return initValue
    		},
    		set:function(value){
                initValue = value
    			// initValue !== value ? memo.notify() : null
                memo.notify()
            }
    	})
    })
    function watch(func){
    	//将计算属性对应方法赋值给target
    	target = func
    	//调用计算属性方法,此时会访问price和count变量,触发该变量的get方法,将target存储到队列中
    	target()
    	//将target重新指向null
    	target = null
    }
    function totalFun (){
    	data.total = data.price * data.count
    }
    watch(totalFun)
    
    console.log(data.total) 			//100
    data.price = 15
    console.log(data.total)				//150
    
    
    展开全文
  • 本篇文章主要介绍了详解Vue双向数据绑定原理解析 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • vue双向绑定原理及实现

    千次阅读 2020-12-16 12:02:21
    vue双向绑定原理及实现一、MVC模式二、MVVM模式三、双向绑定原理1、实现一个Observer2、实现一个Watcher3、实现一个Compile4、实现一个MVVM四、最后写一个html测试一下我们的功能 一、MVC模式 MVC模式 以往的MVC模式...

    一、MVC模式

    MVC模式

    以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新

    二、MVVM模式

    MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。

    我对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。

    三、双向绑定原理

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

    我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。

    因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

    接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

    因此接下去我们执行以下3个步骤,实现数据的双向绑定:

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

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

    3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

    1、实现一个Observer

    Observer是一个数据监听器,其实现核心方法就是Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理
    如下代码实现了一个Observer。

    function Observer(data) {
        this.data = data;
        this.walk(data);
    }
     
    Observer.prototype = {
        walk: function(data) {
            var self = this;
            //这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听
            Object.keys(data).forEach(function(key) {
                self.defineReactive(data, key, data[key]);
            });
        },
        defineReactive: function(data, key, val) {
            var dep = new Dep();
          // 递归遍历所有子属性
            var childObj = observe(val);
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function getter () {
                    if (Dep.target) {
                      // 在这里添加一个订阅者
                      console.log(Dep.target)
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
               // setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(),通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。
                set: function setter (newVal) {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                  // 新的值是object的话,进行监听
                    childObj = observe(newVal);
                    dep.notify();
                }
            });
        }
    };
     
    function observe(value, vm) {
        if (!value || typeof value !== 'object') {
            return;
        }
        return new Observer(value);
    };
     
    // 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数
    function Dep () {
        this.subs = [];
    }
    Dep.prototype = {
      /**
       * [订阅器添加订阅者]
       * @param  {[Watcher]} sub [订阅者]
       */
        addSub: function(sub) {
            this.subs.push(sub);
        },
      // 通知订阅者数据变更
        notify: function() {
            this.subs.forEach(function(sub) {
                sub.update();
            });
        }
    };
    Dep.target = null;
    

    在Observer中,当初我看别人的源码时,我有一点不理解的地方就是Dep.target是从哪里来的,相信有些人和我会有同样的疑问。这里不着急,当写到Watcher的时候,你就会发现,这个Dep.target是来源于Watcher。

    2、实现一个Watcher

    Watcher就是一个订阅者。用于将Observer发来的update消息处理,执行Watcher绑定的更新函数。
    如下代码实现了一个Watcher

    function Watcher(vm, exp, cb) {
        this.cb = cb;
        this.vm = vm;
        this.exp = exp;
        this.value = this.get();  // 将自己添加到订阅器的操作
    }
     
    Watcher.prototype = {
        update: function() {
            this.run();
        },
        run: function() {
            var value = this.vm.data[this.exp];
            var oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            }
        },
        get: function() {
            Dep.target = this;  // 缓存自己
            var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
    };
    

    在我研究代码的过程中,我觉得最复杂的就是理解这些函数的参数,后来在我输出了这些参数之后,函数的这些功能也容易理解了。vm,就是之后要写的SelfValue对象,相当于Vue中的new Vue的一个对象。

    exp是node节点的v-model或v-on:click等指令的属性值。如v-model=“name”,exp就是"name"。cb,就是Watcher绑定的更新函数。

    上面的代码中就可以看出来,在Watcher的getter函数中,Dep.target指向了自己,也就是Watcher对象。在getter函数中

    var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数。
    

    这里获取vm.data[this.exp] 时,会调用Observer中Object.defineProperty中的get函数

    get: function getter () {
        if(Dep.target) {
          // 在这里添加一个订阅者
          console.log(Dep.target)
            dep.addSub(Dep.target);
        }
        return val;
    },
    

    从而把watcher添加到了订阅器中,也就解决了上面Dep.target是哪里来的这个问题。

    3、实现一个Compile

    new SelfVue 绑定的dom节点

    Compile主要的作用是把new SelfVue 绑定的dom节点,(也就是el标签绑定的id)遍历该节点的所有子节点,找出其中所有的v-指令和" {{}} ".

    1.如果子节点含有v-指令,即是元素节点,则对这个元素添加监听事件。(如果是v-on,则node.addEventListener(‘click’),如果是v-model,则node.addEventListener(‘input’))。接着初始化模板元素,创建一个Watcher绑定这个元素节点。

    2.如果子节点是文本节点,即" {{ data }} “,则用正则表达式取出” {{ data }} "中的data,然后var initText = this.vm[exp],用initText去替代其中的data。
    具体代码参见我的github: vue-MVVM
    里面有详细的注释。

    4、实现一个MVVM

    可以说MVVM是Observer,Compile以及Watcher的“boss”了,他需要安排给Observer,Compile以及Watche做的事情如下

    a、Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify
    b、Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数
    c、Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update。

    最后,把这个MVVM抽象出来,就是vue中Vue的构造函数了,可以构造出一个vue实例。

    四、最后写一个html测试一下我们的功能

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>self-vue</title>
    </head>
    <style>
        #app {
            text-align: center;
        }
    </style>
    <body>
        <div id="app">
            <h2>{{title}}</h2>
            <input v-model="name">
            <h1>{{name}}</h1>
            <button v-on:click="clickMe">click me!</button>
        </div>
    </body>
    <script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/compile.js"></script>
    <script src="js/mvvm.js"></script>
    <script type="text/javascript">
     
         var app = new SelfVue({
            el: '#app',
            data: {
                title: 'hello world',
                name: 'canfoo'
            },
            methods: {
                clickMe: function () {
                    this.title = 'hello world';
                }
            },
            mounted: function () {
                window.setTimeout(() => {
                    this.title = '你好';
                }, 1000);
            }
        });
    </script>
    </html>
    

    先执行mvvm中的new SelfVue(…),在mvvm.js中

    observe(this.data);
    new Compile(options.el, this);
    

    先初始化一个监听器Observer,用于监听该对象data属性的值。
    然后初始化一个解析器Compile,绑定这个节点,并解析其中的v-," {{}} "指令,(每一个指令对应一个Watcher)并初始化模板数据以及初始化相应的订阅者,并把订阅者添加到订阅器中(Dep)。这样就实现双向绑定了。
    如果v-model绑定的元素

    <input v-model="name">
    

    即输入框的值发生变化,就会触发Compile中的

    node.addEventListener('input', function(e) {
        var newValue = e.target.value;
        if (val === newValue) {
            return;
        }
        self.vm[exp] = newValue;
        val = newValue;
    });
    

    self.vm[exp] = newValue;这个语句会触发mvvm中SelfValue的setter,以及触发Observer对该对象name属性的监听,即Observer中的Object.defineProperty()中的setter。

    setter中有通知订阅者的函数dep.notify,Watcher收到通知后就会执行绑定的更新函数。
    最后的最后就是效果图啦:

    相关参考链接:http://www.cnblogs.com/canfoo/p/6891868.html

    原文链接:https://www.jianshu.com/p/f194619f6f26

    展开全文
  • vue 的双向绑定原理

    2020-08-12 16:42:00
    vue 的双向绑定原理,分三步: 第一步,“数据劫持”:vue 用 Object.defineProperty() 方法实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep; 第二步,“添加观察者”:在编译的时候在该属性的数组 dep...

    目录

    一、一句话描述 vue 的双向绑定原理

    二、细说 vue 的双向绑定原理

    1、vue 2.x 的双向绑定

    2、vue 3.x 的双向绑定

    3、一个完整的案例


    一、一句话描述 vue 的双向绑定原理(vue 的响应式原理)

    vue 在实例化的时候,使用 Object.definePropery() 方法Proxy 构造函数,对 data 进行 getter 和 setter 的处理。在组件渲染时,若用到 data 里的某个数据,这个数据就会被依赖收集进 watcher 里。当数据更新,如果这个数据在 watcher 里,就会收到通知并更新,否则不会更新——vue 采用“数据劫持”+“观察者模式(发布者-订阅者模式)”相结合的方式实现了双向绑定——vue 的响应式原理。

    【拓展】

    “数据劫持”+“观察者模式(发布者-订阅者模式)”:通过 Object.defineProperty() 方法(Vue 2.x)或 ES6 的 Proxy 构造函数(Vue 3.x)来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

     

    二、细说 vue 的双向绑定原理(vue 的响应式原理)

    • 第一步,“数据劫持”:vue 2.x 用 Object.defineProperty() 方法来实现数据劫持,为每个属性分配一个订阅者集合的管理数组 dep;vue 3.x 用 ES6 的 Proxy 构造函数来实现数据劫持。
    • 第二步,“添加订阅者”:在编译的时候在该属性的数组 dep 中添加订阅者,添加方式包括:v-model 会添加一个订阅者,{{}} 也会,v-bind 也会,只要用到该属性的指令理论上都会。
    • 第三步,“为 input 添加监听事件”:为 input 添加监听事件,修改值就会为该属性赋值,触发该属性的 set() 方法,在 set() 方法内通知订阅者数组 dep,订阅者数组循环调用各订阅者的 update() 方法更新视图。

    1、vue 2.x 的双向绑定

    --> 发布者-订阅者模式:

    // 订阅者(观察者)
    let uid = 0;
    class Dep {
      constructor(){
        this.id = uid++;
        this.subs = [];
      }
      addSub(sub){
        this.subs.push(sub);
      }
      removeSub(sub){
        const arr = this.subs,
              item = sub;
        if(arr.length){
          const index = arr.indexOf(item);
          if(index > -1){
            return arr.splice(index, 1);
          }
        }
      }
      depend(){
        if(window.target){
          window.target.addDep(this);
        }
      }
      notify(){
        const subs = this.subs.slice();
        for(let i = 0, len = subs.lengths; i < len; i++){
          subs[i].update();
        }
      }
    }
    
    // 发布者(被观察者)(不考虑深度监听)
    class Watcher {
      constructor(vm, expOrFn, cb) {
        this.vm = vm;
        this.deps = [];
        this.depIds = new Set();
        this.getter = expOrFn;
        this.cb = cb;
        this.value = this.get();
      }
      get() {
        window.target = this;
        var value = this.getter.call(this.vm, this.vm);
        window.target = undefined;
        return value;
      }
      addDep() {
        const id = dep.id;
        if(!this.depIds.has(id)){
          this.depIds.add(id);
          this.deps.push(dep);
          dep.addSub(this);
        }
      }
      update() {
        console.log("更新, value:", this.value);
      }
    }
    
    /* “发布者-订阅者模式”的使用示例 */
    // 创建 发布者 实例
    var watcher = new Watcher({ x: 1 }, (val) => val);
    watcher.get();
    
    // 创建 订阅者 实例
    var dep = new Dep();
    
    // 订阅者 监听 发布者 对象
    dep.depend();
    dep.notify();

    --> 数据劫持: 

    // 数据劫持
    function defineReactive(obj, key, val){
      let dep = new Dep();
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          dep.depend();
          return value;
        },
        set: function reactiveSetter(newVal) {
          if(val === newVal){
             return;
          }
          val = newVal;
          dep.notify();
        }
      });
    }

    2、vue 3.x 的双向绑定

    vue 3.x 的双向绑定与 vue 2.x 的双向绑定,都采用 发布者-订阅者模式,不同的是 数据劫持 的实现,vue 3.x 采用的是 ES6 的 Proxy 构造函数实现的。

    Proxy(data, {
      get(target, key) {
        return target[key];
      },
      set(target, key, value) {
        let val = Reflect.set(target, key, value);
          _that.$dep[key].forEach(item => item.update());
        return val;
      }
    })

    3、一个完整的案例

    <body>
    	<div id="demo"></div>
    	<input type="text" id="inp">
    </body>
    <script type="text/javascript">
    	var obj = {};
    	var demo = document.querySelector('#demo')
    	var inp = document.querySelector('#inp')
    	Object.defineProperty(obj, 'name', {
    		get: function() {
    			return val;
    		},
    		set: function(newVal) { //当该属性被赋值的时候触发
    			inp.value = newVal;
    			demo.innerHTML = newVal;
    		}
    	})
    	inp.addEventListener('input', function(e) {
    		// 给obj的name属性赋值,进而触发该属性的set方法
    		obj.name = e.target.value;
    	});
    	obj.name = 'fei'; //在给obj设置name属性的时候,触发了set这个方法
    </script>

     

     

     

    参考文件:

    使用Proxy实现Vue数据劫持:https://zhuanlan.zhihu.com/p/50547367

    用一句话说明 Vuex工作原理:https://zhuanlan.zhihu.com/p/106838529

    展开全文
  • ViewModel:通过*双向绑定*连接了View和Model; 2.原理 在MVVM的架构中 Vue.js 的原理是对数据(Model)进行劫持(Object.defineProperty( )),在数据发生变化时,数据会触发劫持时绑定的setter方法,对视图(View...

    MVVM框架概念

    1.概念

    在这里插入图片描述

    经典MVVM模型图,由 View、Model、ViewModel 三部分组成。

    View:视图模版,负责将Model转化为UI,并展示;

    Model:数据模型,根据业务逻辑操作数据;

    ViewModel:通过*双向绑定*连接了View和Model;

    2.原理

    在MVVM的架构中 Vue.js 的原理是对数据(Model)进行劫持(Object.defineProperty( )),在数据发生变化时,数据会触发劫持时绑定的setter方法,对视图(View)进行更新。

    3.对比

    jQuery 的原理,如果数据发生变化,需要先获取对应的DOM元素,然后才能更新UI。数据以及业务逻辑和页面形成了强耦合。

    MVVM 的原理则是监听数据,数据变化后只刷新对应的UI,只需要关心操作数据,不需要操作DOM。MVVM中核心就是数据双向绑定。

    Vue数据双向绑定原理

    1.原理

    Vue数据双向绑定通过数据劫持以及发布者-订阅者模式的方式实现。

    2.相关代码

    Object.defineProperty()
    

    通过defineProperty()劫持属性的getter/setter,结合发布者-订阅者的方式,发送消息给订阅者,触发对应的回调函数。通过指令(v-xxx)去对DOM进行封装。当数据发生变化,指令修改对应的DOM,数据驱动DOM的变化。反向,Vue也会监听操作,修改视图时,Vue监听到变化后,改变数据。数据的双向变化形成。

    双向绑定实现

    1.创建Observer

    本质上是一个数据属性监听器,核心方法就是Object.defineProperty()。注意:是对所有属性进行监听,那么就意味着如果数据是一个复杂对象,那么就要进行递归遍历深层属性。

    创建Observer:

    // 为了实现数组的响应式,需要重写数组的原型方法
    const originPrototype = Array.prototype; // 空数组
    const newArrPrototype = Object.create(originPrototype)
    const methods = [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ]
    methods.forEach(method => {
        // 重新准备数组原型方法,放在newArrPrototype上
        newArrPrototype[method] = function(){
            // 获取原来的对应的原型方法
            originPrototype[method].apply(this, arguments);
        }
    })
    
    // 模拟defineReactive
    let defineReactive = (obj, key, value) => {
        // 递归调用,判断value是否也是对象,用于把深层对象设置成响应式属性
        observer(value);
    
        // obj 传入对象; key监听的属性; value初始值;
        // 拦截传入的参数,进行判断,是否发生数据变化
        // defindReactive对数组的拦截无效,需要修改原型
        Object.defineProperty(obj, key, {
            get() {
                console.log('get: ' + key);
                return value;
            },
            set(newValue) {
                if (newValue !== value) {
                    // 为了防止传进来的也是一个对象,set的时候也要调用observer
                    observer(newValue);
                    console.log('set: ' + key + ', value: ' + newValue);
                    value = newValue;
                }
            }
        })
    }
    
    let observer = function(obj){
        // 数组相关的方法是需要单独处理的
        if(typeof obj === 'object' && obj != null){
            // 增加对数组的判断,数组需要特殊处理
            if(Array.isArray(obj)){
                // 重新指定对象原型
                obj.__proto__ = newArrPrototype;
                // 遍历数组元素,创建响应式
                for (let i = 0; i < obj.length; i++) {
                    // 如果是多维数组,递归调用
                    observer(obj[i])
                }
            }else{
                // 获取obj的所有key
                let allKey = Object.keys(obj);
                // 遍历所有key,让每个属性都变成响应式属性
                allKey.forEach(key => {
                    defineReactive(obj, key, obj[key]);
                })
            }
        }
    }
    
    

    2.创建Dep并植入Oberver

    在原理图中可以看出,对属性监听完成后,需要一个消息订阅器(Dep),Dep的作用就是收集所有的watcher。便于数据发生变化时,通知(notify)相关的watcher,去更新视图(View)。

    那么创建Dep后,就需要改造上边创建的Observer:

    // 植入Dep,管理订阅者(watcher)
    // 创建劫持监听
    class Observer {
        constructor(value) {
            this.value = value;
            // 判断value的类型:对象or数组
            if (typeof value === 'object' && value != null && !Array.isArray(value)) {
                this.walk(value)
            } else {
                // 判断value是数组类型
                // 为了实现数组的响应式,需要重写数组的原型方法
                const originPrototype = Array.prototype; // 空数组
                const newArrPrototype = Object.create(originPrototype)
                const methods = [
                    'push',
                    'pop',
                    'shift',
                    'unshift',
                    'splice',
                    'sort',
                    'reverse'
                ]
                methods.forEach(method => {
                    // 重新准备数组原型方法,放在newArrPrototype上
                    newArrPrototype[method] = function(){
                        // 获取原来的对应的原型方法
                        originPrototype[method].apply(this, arguments);
                    }
                })
               	// 重新指定对象原型
                obj.__proto__ = newArrPrototype;
                // 遍历数组元素,创建响应式
                for (let i = 0; i < obj.length; i++) {
                    // 如果是多维数组,递归调用
                    observer(obj[i])
                }
            }
        }
        // 对象
        walk(obj) {
            Object.keys(obj).forEach(key => {
                defineReactive(obj, key, obj[key]);
            })
        }
        // 数组
    }
    let observer = function (obj) {
        if (typeof obj === 'object' && obj != null) {
            // 创建Observer实例
            new Observer(obj);
        }
    }
    
    // 模拟defineReactive
    let defineReactive = (obj, key, value) => {
        // 递归调用,判断value是否也是对象,用于把深层对象设置成响应式属性
        observer(value);
    		
      	// ------------------------------------------------------
      	// 创建Dep,关联指定的key
        let dep = new Dep();
    		// ------------------------------------------------------
      
        // obj 传入对象; key监听的属性; value初始值;
        // 拦截传入的参数,进行判断,是否发生数据变化
        // defindReactive对数组的拦截无效,需要修改原型
        Object.defineProperty(obj, key, {
            get() {
                console.log('get: ' + key);
              	// ------------------------------------------------------
              	// !!!!!!!!!!注意:在触发getter的时候,开始收集依赖。
              	// 也就是说,所有的依赖收集过程都发生在getter
              	if(收集依赖的条件){
                  dep.addDep(订阅者)
                }
              	// ------------------------------------------------------
                return value;
            },
            set(newValue) {
                if (newValue !== value) {
                    // 为了防止传进来的也是一个对象,set的时候也要调用observer
                    observer(newValue);
                    console.log('set: ' + key + ', value: ' + newValue);
                    value = newValue;
                }
            }
        })
    }
    
    
    

    创建Dep:

    // 依赖收集
    // 管理某个key对应的所有的watcher
    class Dep {
        constructor(){
            this.deps = [];
        }
        addDep(dep){
            this.deps.push(dep)
        }
        notify(){
            this.deps.forEach(dep => {
                console.log(dep);
                dep.update();
            })
        }
    }
    

    总结:Dep收集watcher放在了getter中,是让Watcher在初始化的时候进行触发。

    3.创建Wacther

    创建watcher的时候,即初始化watcher的时候,主动触发getter,将自己添加到Dep中:

    class Watcher{
        constructor(vm, key, fn){
            this.vm = vm;
            this.key = key;
            this.updateFn = fn;
    
            // 当前watcher赋值给target
            Dep.target = this;
            // 因为依赖收集发生在getter阶段,所以主动触发getter,将对应key的watcher收集到dep中
            this.vm[this.key];
            // 当前key的依赖收集完成后,立即释放target
            Dep.target = null;
    
        }
        update(){
            this.updateFn.call(this.vm, this.vm[this.key]);
        }
    }
    

    Watcher创建完成后,那么需要对第二步中的Observer继续改造:

    // 植入Dep,管理订阅者(watcher)
    // 创建劫持监听
    class Observer {
        constructor(value) {
            this.value = value;
            // 判断value的类型:对象or数组
            if (typeof value === 'object' && value != null && !Array.isArray(value)) {
                this.walk(value)
            } else {
                // 判断value是数组类型
                // 为了实现数组的响应式,需要重写数组的原型方法
                const originPrototype = Array.prototype; // 空数组
                const newArrPrototype = Object.create(originPrototype)
                const methods = [
                    'push',
                    'pop',
                    'shift',
                    'unshift',
                    'splice',
                    'sort',
                    'reverse'
                ]
                methods.forEach(method => {
                    // 重新准备数组原型方法,放在newArrPrototype上
                    newArrPrototype[method] = function(){
                        // 获取原来的对应的原型方法
                        originPrototype[method].apply(this, arguments);
                    }
                })
               	// 重新指定对象原型
                obj.__proto__ = newArrPrototype;
                // 遍历数组元素,创建响应式
                for (let i = 0; i < obj.length; i++) {
                    // 如果是多维数组,递归调用
                    observer(obj[i])
                }
            }
        }
        // 对象
        walk(obj) {
            Object.keys(obj).forEach(key => {
                defineReactive(obj, key, obj[key]);
            })
        }
        // 数组
    }
    let observer = function (obj) {
        if (typeof obj === 'object' && obj != null) {
            // 创建Observer实例
            new Observer(obj);
        }
    }
    
    // 模拟defineReactive
    let defineReactive = (obj, key, value) => {
        // 递归调用,判断value是否也是对象,用于把深层对象设置成响应式属性
        observer(value);
    		
      	// ------------------------------------------------------
      	// 创建Dep,关联指定的key
        let dep = new Dep();
    		// ------------------------------------------------------
      
        // obj 传入对象; key监听的属性; value初始值;
        // 拦截传入的参数,进行判断,是否发生数据变化
        // defindReactive对数组的拦截无效,需要修改原型
        Object.defineProperty(obj, key, {
            get() {
                console.log('get: ' + key);
              	// ------------------------------------------------------
              	// !!!!!!!!!!注意:在触发getter的时候,开始收集依赖。
              	// 也就是说,所有的依赖收集过程都发生在getter
              	Dep.target && dep.addDep(Dep.target);
              	// ------------------------------------------------------
                return value;
            },
            set(newValue) {
                if (newValue !== value) {
                    // 为了防止传进来的也是一个对象,set的时候也要调用observer
                    observer(newValue);
                    console.log('set: ' + key + ', value: ' + newValue);
                    value = newValue;
                  	
              			// ------------------------------------------------------
                  	// 在setter触发的时候,通知对应的watcher去更新view
                  	dep.notify()
              			// ------------------------------------------------------
                }
            }
        })
    }
    
    

    至此为止,只需要将Observer和Watcher关联起来,就可以实现数据的双向绑定。

    4.创建Compile

    Compile的作用就是编译模版,包括模版上的指令;

    在Compile的构造中需要两个参数,一个是el(new Vue的时候挂载到跟元素),一个是实例。

    开始编译时(compile()),开始遍历节点进行判断,判断是什么节点以及是否存在子节点,如果存在递归遍历。

    编译过程中,对于指定类型的节点开始进行初始化,此时,开始建立关联关系!

    // 创建编译器
    // 获取dom节点,遍历节点
    // 判断节点类型:dom节点/文本节点
    class Compiler {
        constructor(el, vm) {
            this.$el = document.querySelector(el);
            this.$vm = vm;
    
            if (this.$el) {
                this.compile(this.$el);
            }
        }
        compile(el) {
            // 获取子节点
            const childNodes = el.childNodes;
            // 遍历孩子节点,用于判断是dom节点还是文本节点
            Array.from(childNodes).forEach(node => {
                if (this.isElement(node)) {
                    // console.log('元素节点');
                    this.compileElement(node);
                } else if (this.isInter(node)) {
                    // console.log('文本节点');
                    this.compileText(node);
                }
                // 判断是否有子节点,递归调用
                if (node.childNodes && node.childNodes.length > 0) {
                    this.compile(node);
                }
            })
        }
        isElement(node) {
            return node.nodeType === 1;
        }
        isInter(node) {
            // 判断插值文本{{}}
            return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
        }
        compileText(node) {
            // 从实例中获取插值
            // 创建更新器后,会被抽取到公共方法中
            // node.textContent = this.$vm[RegExp.$1];
            // 创建更新器
            this.update(node, RegExp.$1, 'text');
        }
        compileElement(node) {
            // 遍历元素节点的属性
            const attrs = node.attributes;
            Array.from(attrs).forEach(attr => {
                // 获取指令名称
                const attrName = attr.name;
                // 判断是一般写法还是简写
                if (attrName.indexOf('-') > 0) {
                    if (attrName.indexOf('on') > 0) {
                        const exp = attrName.substring(2, 4);
                        this[exp] && this[exp](attr, node);
                    } else {
                        // 常规指令写法
                        const exp = attrName.substring(2);
                        this[exp] && this[exp](attr, node);
                    }
                }
            })
        }
        update(node, exp, dir) {
            // 初始化操作
            const fn = this[dir+'Updater'];
            fn && fn(node, this.$vm[exp])
    
            // 更新操作
            // 在更新的时候创建一个watcher
          	// ----------------------------------------------------
          	// 创建了Observer和Watcher的关系
            new Watcher(this.$vm, exp, function(value){
                fn && fn(node, value);
                //textUpdater
            })
        }
        textUpdater(node, value){
            node.textContent = value;
        }
        text(attribute, node) {
            this.update(node, attribute.value, 'text');
        }
        // 更新函数!!!
        htmlUpdater(node, value){
            node.innerHTML = value;
        }
        html(attribute, node) {
            this.update(node, attribute.value, 'html');
        }
        on(attribute, node) {
            node.addEventListener('click', this.$vm[attribute.value])
        }
    
    }
    

    5.何时调用Compile

    在new Vue的时候调用了Compile:

    const app = new kVue({
      el: '#app',
      data: {
        counter: 0,
        str: '<span style="color: red">我是html代码</span>'
      },
      methods: {
        aa() {
          alert(1)
        }
      },
    })
    

    创建kVue类:

    // 创建kVue的构造函数
    class kVue {
        constructor(options) {
            // 保存options
            this.$options = options;
            this.$data = options.data;
            this.$methods = options.methods;
            // 实例化的时候只有data中的数据需要实现响应化
            // 处理响应化的时候需要判断传入的参数是对象还是数组
            observer(this.$data);
    
            // 代理this中的$data
            proxy(this, '$data');
            proxy(this, '$methods')
    
            // 编译
            new Compile(this.$options.el, this)
        }
    }
    

    总结

    以上就实现了一个简单的数据的双向绑定,对于各种指令以及事件,可以自行扩展。

    代码奉上

    展开全文
  • 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,即vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据...
  • Vue 3.0双向绑定原理的实现

    千次阅读 2019-10-21 17:19:01
    proxy方法 vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的...可查看Vue 2.0双向绑定原理的实现 具...
  • vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的.本文重点给大家介绍Vue数据双向绑定原理及简单实现方法,感兴趣的朋友跟随脚本之家小编一起学习吧
  • 主要介绍了Vue自定义组件双向绑定实现原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Object.defineProperty方法 vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。...
  • vue-双向绑定原理

    2021-03-28 20:55:16
    1.双向绑定 [1]定义 在视图上的数据发生了变化,data中的数据也要对应改变; data中的数据发生了变化,视图上的数据也要对应改变; [2]原理 vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,...
  • 1.Vue2.X的数据双向绑定原理:采用的是Object.defineProperty()方法 <!-- 1.Vue2.x采用的数据双向绑定原理 --> <input type="text" v-model> <p v-bind></p> <script> // 1....
  • vue 双向绑定原理

    2020-10-30 14:01:14
    <!... <... <head>...meta charset="UTF-8">...meta name="viewport" content="width=device-width, initial-scale=1.0">...v-model双向绑定原理</title> <script src="https://lib.baomit
  • vue双向绑定原理

    2020-10-19 23:16:28
    vue双向绑定原理 当一个 vue 实例被创建时,他向 Vue 的响应式系统中加入了其 data 对象中能找到的所有属性.【proxy和defineProperty的区别(下面)】 再利用 es5 特性 Object.defineProperty实现数据劫持,通过遍历 ...
  • 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化。这也算是vue的精髓之处了。单项数据绑定是使用状态管理工具(如redux...
  • vue2与vue3的双向绑定写法不同 vue使用Object.defineProperty来劫持对象属性 而vue3使用Proxy代理来实现双向绑定。 vue2: <div class="data"></div> <label>vue2:</label> <input ...
  • 最近我参加了一次来自合肥的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理。 情景再现: 当我手机铃声响起...
  • Vue2 双向绑定原理(数据响应式)

    千次阅读 2020-04-20 12:55:50
    2. Vue2 双向绑定原理(数据响应式) 原理概述 数据劫持 发布与订阅 (一)MVVM 1. 定义: MVVM 是 Model-View-ViewModel(模型-视图-视图模型)的缩写;是一种软件架构设计模式。 其中: Model:是数据模型,既...
  • 下面小编就为大家带来一篇mvvm双向绑定机制的原理和实现代码(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Vue数据双向绑定原理(vue2向vue3的过渡)

    万次阅读 多人点赞 2019-11-13 15:13:40
    接下来我们浅析数据双向绑定原理 一、vue2 1、认识defineProperty vue2中的双向绑定是基于defineProperty的get操作与set操作,那么我们简单认识下defineProperty, 作用: 就是直接在一个对象上定义一个新属性,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,594
精华内容 17,837
关键字:

双向绑定原理

友情链接: FX2N PLC交通灯程序.rar