精华内容
下载资源
问答
  • 到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。...

    在线使用-线上测试-源码

    //代码:
    <div id="app">
        <input v-model="name" type="text">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    const vm = new Mvue({
        el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    });
    </script>

    数据绑定

    在正式开始之前我们先来说说数据绑定的事情,数据绑定我的理解就是让数据M(model)展示到 视图V(view)上。我们常见的架构模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是采用 MVVM 模式实现双向绑定,Vue 自然也不例外。但是各个框架实现双向绑定的方法略有所不同,目前大概有三种实现方式。

    • 发布订阅模式
    • Angular 的脏查机制
    • 数据劫持

    而 Vue2.0 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定,数据劫持主要通过 Object.defineProperty 来实现。

    Object.defineProperty

    这篇文章我们不详细讨论 Object.defineProperty 的用法,我们主要看看它的存储属性 get 与 set。我们来看看通过它设置的对象属性之后有何变化。

    var people = {
        name: "Modeng",
        age: 18
    }
    people.age; //18
    people.age = 20;

    上述代码就是普通的获取/设置对象的属性,看不到什么奇怪的变化。

    var modeng = {}
    var age;
    Object.defineProperty(modeng, 'age', {
      get: function () {
        console.log("获取年龄");
        return age;
      },
      set: function (newVal) {
        console.log("设置年龄");
        age = newVal;
      }
    });
    modeng.age = 18;
    console.log(modeng.age);

    你会发现通过上述操作之后,我们访问 age 属性时会自动执行 get 函数,设置 age 属性时,会自动执行 set 函数,这就给我们的双向绑定提供了非常大的方便。

    剖析

    我们知道 MVVM 模式在于数据与视图的保持同步,意思是说数据改变时会自动更新视图,视图发生变化时会更新数据。

    所以我们需要做的就是如何检测到数据的变化然后通知我们去更新视图,如何检测到视图的变化然后去更新数据。检测视图这个比较简单,无非就是我们利用事件的监听即可。

    那么如何才能知道数据属性发生变化呢?这个就是利用我们上面说到的 Object.defineProperty 当我们的属性发生变化时,它会自动触发 set 函数从而能够通知我们去更新视图。

    实现

    通过上面的描述与分析我们知道 Vue 是通过数据劫持结合发布订阅模式来实现双向绑定的。我们也知道数据劫持是通过 Object.defineProperty 方法,当我们知道这些之后,我们就需要一个监听器 Observer 来监听属性的变化。得知属性发生变化之后我们需要一个 Watcher 订阅者来更新视图,我们还需要一个 compile 指令解析器,用于解析我们的节点元素的指令与初始化视图。所以我们需要如下:

    • Observer 监听器:用来监听属性的变化通知订阅者
    • Watcher 订阅者:收到属性的变化,然后更新视图
    • Compile 解析器:解析指令,初始化模版,绑定订阅者

    监听器 Observer

    监听器的作用就是去监听数据的每一个属性,我们上面也说了使用 Object.defineProperty 方法,当我们监听到属性发生变化之后我们需要通知 Watcher 订阅者执行更新函数去更新视图,在这个过程中我们可能会有很多个订阅者 Watcher 所以我们要创建一个容器 Dep 去做一个统一的管理。

    function defineReactive(data, key, value) {
      //递归调用,监听所有属性
      observer(value);
      var dep = new Dep();
      Object.defineProperty(data, key, {
        get: function () {
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        set: function (newVal) {
          if (value !== newVal) {
            value = newVal;
            dep.notify(); //通知订阅器
          }
        }
      });
    }
    
    function observer(data) {
      if (!data || typeof data !== "object") {
        return;
      }
      Object.keys(data).forEach(key => {
        defineReactive(data, key, data[key]);
      });
    }
    
    function Dep() {
      this.subs = [];
    }
    Dep.prototype.addSub = function (sub) {
      this.subs.push(sub);
    }
    Dep.prototype.notify = function () {
      console.log('属性变化通知 Watcher 执行更新视图函数');
      this.subs.forEach(sub => {
        sub.update();
      })
    }
    Dep.target = null;

    以上我们就创建了一个监听器 Observer,我们现在可以尝试一下给一个对象添加监听然后改变属性会有何变化。

    var modeng = {
      age: 18
    }
    observer(modeng);
    modeng.age = 20;

    我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。

    订阅者 Watcher

    Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:

    1. 把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数
    2. 接收到通知,执行更新函数。
    function Watcher(vm, prop, callback) {
      this.vm = vm;
      this.prop = prop;
      this.callback = callback;
      this.value = this.get();
    }
    Watcher.prototype = {
      update: function () {
        const value = this.vm.$data[this.prop];
        const oldVal = this.value;
        if (value !== oldVal) {
          this.value = value;
          this.callback(value);
        }
      },
      get: function () {
        Dep.target = this; //储存订阅器
        const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法
        Dep.target = null;
        return value;
      }
    }

    这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。

    function Mvue(options, prop) {
    	this.$options = options;
    	this.$data = options.data;
    	this.$prop = prop;
    	this.$el = document.querySelector(options.el);
    	this.init();
    }
    Mvue.prototype.init = function () {
        observer(this.$data);
        this.$el.textContent = this.$data[this.$prop];
        new Watcher(this, this.$prop, value => {
    	    this.$el.textContent = value;
    	});
    }

    这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。

    <div id="app">{{ name }}</div>
    const vm = new Mvue({
    	el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    }, "name");

    我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。

    到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。

    Compile 解析器

    Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。

    因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。

    function Compile(vm) {
      this.vm = vm;
      this.el = vm.$el;
      this.fragment = null;
      this.init();
    }
    Compile.prototype = {
      init: function () {
        this.fragment = this.nodeFragment(this.el);
      },
      nodeFragment: function (el) {
        const fragment = document.createDocumentFragment();
        let child = el.firstChild;
        //将子节点,全部移动文档片段里
        while (child) {
          fragment.appendChild(child);
          child = el.firstChild;
        }
        return fragment;
      }
    }

    然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。

    Compile.prototype = {
      compileNode: function (fragment) {
        let childNodes = fragment.childNodes;
        [...childNodes].forEach(node => {
          let reg = /\{\{(.*)\}\}/;
          let text = node.textContent;
          if (this.isElementNode(node)) {
            this.compile(node); //渲染指令模板
          } else if (this.isTextNode(node) && reg.test(text)) {
            let prop = RegExp.$1;
            this.compileText(node, prop); //渲染 {{ }}模板
          }
    
          //递归编译子节点
          if (node.childNodes && node.childNodes.length) {
            this.compileNode(node);
          }
        });
      },
      compile: function (node) {
        let nodeAttrs = node.attributes;
        [...nodeAttrs].forEach(attr => {
          let name = attr.name;
          if (this.isDirective(name)) {
            let value = attr.value;
            if (name === "v-model") {
              this.compileModel(node, value);
            }
            node.removeAttribute(name);
          }
        });
      },
      //略
    }

    因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码

    到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。

    现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。

    创建 Mvue

    这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }

    然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。

    <div id="app">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    	const vm = new Mvue({
    		el: "#app",
    		data: {
    			name: "完全没问题,看起来是不是很酷!"
    		}
    	});
    </script>

    我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。

    数据代理

    我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty 方法进行一步中间的转换操作,间接的去访问。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      //数据代理
      Object.keys(this.$data).forEach(key => {
        this.proxyData(key);
      });
    
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }
    Mvue.prototype.proxyData = function (key) {
      Object.defineProperty(this, key, {
        get: function () {
          return this.$data[key]
        },
        set: function (value) {
          this.$data[key] = value;
        }
      });
    }

    到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美

     

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

    2021-06-10 16:34:20
    当前台显示的view发生变化了,它会实时反应到viewModel上,如果有需要,viewModel 会通过ajax等方法将改变的数据 传递给后台model同时从后台model获取过来的数据,通过vm将值响应到前台UI上双向绑定原理v...

    d3199a94016d993763e3dcbdae82285a.png

    简析mvvm框架

    目前angular,reat和vue都是mvvm类型的框架

    以vue为例

    aaded1f18b1529f23f329df1e0787265.png

    这里的vm 就是vue框架,它相当于中间枢纽的作用,连接着model 和view.当前台显示的view发生变化了,它会实时反应到viewModel上,如果有需要,viewModel 会通过ajax等方法将改变的数据 传递给后台model

    同时从后台model获取过来的数据,通过vm将值响应到前台UI上

    双向绑定原理

    dc9936a42e84a602be2c7c9ccc3a1e7f.png

    vm的核心是view 和 data当data 有变化的时候它通过Object.defineProperty()方法中的set方法进行监控,并调用在此之前已经定义好data 和view的关系了的回调函数,来通知view进行数据的改变

    而view 发生改变则是通过底层的input 事件来进行data的响应更改

    vue是通过Object.defineProperty()来实现数据劫持的。

    Object.defineProperty( )是用来做什么的?它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举,这里我们主要先来研究下它对应的两个描述属性get和setvarBook= {}

    varname= '';

    Object.defineProperty(Book, 'name', {

    set:function(value) {

    name= value;

    console.log('你取了一个书名叫做'+ value);

    },

    get:function() {

    return'《'+ name+ '》'

    }

    })

    console.log(Book)

    Book.name= 'vue权威指南'; // 你取了一个书名叫做vue权威指南

    console.log(Book.name); // 《vue权威指南》

    // get 是在读取那么属性的时候触发的

    // set 是在设置属性值的时候触发的

    实现方法: 观察者模式

    2e60365ad12626e643906fffb6856c6c.png

    Observer(Objec.defineProperty中的set)监听data的变化,当data有变化的时候通知观察者列表Dep(里面有与data变化对应的update函数),watcher负责向观察者列表里添加(订阅)对应的更新函数,Dep里的更新函数执行完了之后将最新的值更新到view上。

    具体的代码实现可参考:https://www.cnblogs.com/libin-1/p/6893712.html

    想要了解更多相关知识,可访问 前端学习网站!!

    展开全文
  • Android中的双向绑定是指:将Model设置给View之后,当Model中的数据发生改变时,View显示的内容也会随之改变;当View发生变化时,Model中的数据也会随之改变。双向绑定可以让开发者使用数据驱动视图,并且降低了程序...

      Android中的双向绑定是指:将Model设置给View之后,当Model中的数据发生改变时,View显示的内容也会随之改变;当View发生变化时,Model中的数据也会随之改变。双向绑定可以让开发者使用数据驱动视图,并且降低了程序中的耦合度。双向绑定本质是基于观察者模式实现的。在代码层面,主要表现为:当Model中的数据发生变化时,通过回调接口,通知数据绑定器重新设置View中的数据;当View中的属性发生变化时,通过监听器捕获发生的变化,并将此变化传递给Model。这样就实现了双向绑定。整体框架如下图所示:

    双向绑定举例:

      将ObservableField<String>与EditView进行双向绑定,并使用一个TextView展示。代码如下:

      ViewModel代码:

    class UserViewModel:ViewModel() {
        var name:ObservableField<String> = ObservableField("")
    }

      XML文件代码:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:MyView="http://schemas.android.com/apk/res-auto">
    
        <data>
            <variable
                name="userVM"
                type="com.example.jetpacklearn.viewModel.UserViewModel" />
        </data>
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
            <EditText
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:id="@+id/userName"
                android:text="@={userVM.name}"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/showName"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/showName"
                android:text="@{userVM.name}"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toEndOf="@+id/userName"
                app:layout_constraintHorizontal_chainStyle="spread"
                app:layout_constraintEnd_toEndOf="parent"/>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

      MainActivity文件:

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            var layoutInflater = LayoutInflater.from(this)
            var dataBinding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(dataBinding.root)
    
            var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
            dataBinding.userVM = userViewModel
        }
    }

      运行结果如下:

        

    接下来,以上述例子介绍双向绑定的实现流程。

    双向绑定流程代码分析:

      在MainActivity中我们可以使用以下方法获取ViewDataBinding,并设置ViewModel。

    var dataBinding = ActivityMainBinding.inflate(layoutInflater)
    var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
    dataBinding.userVM = userViewModel

      ActivityMainBinding是自动生成的类,其inflate代码如下:

      public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
        return inflate(inflater, DataBindingUtil.getDefaultComponent());
      }
      public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
          @Nullable Object component) {
        return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
      }

      接下来,会去调用ViewDataBinding的inflateInternal方法。注意,在这里DataBindingUtil.getDefaultCompinent获取的结果为null,即component对象为null。

        protected static <T extends ViewDataBinding> T inflateInternal(
                @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
                boolean attachToParent, @Nullable Object bindingComponent) {
            return DataBindingUtil.inflate(
                    inflater,
                    layoutId,
                    parent,
                    attachToParent,
                    checkAndCastToBindingComponent(bindingComponent)
            );
        }

      接下来,调用DataBindingUtil的inflate方法。注意,这里parent为null,attachToParent为false。

        public static <T extends ViewDataBinding> T inflate(
                @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
                boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
            final boolean useChildren = parent != null && attachToParent;//userCHildren为false
            final int startChildren = useChildren ? parent.getChildCount() : 0;
            //生成Root View
            final View view = inflater.inflate(layoutId, parent, attachToParent);
            if (useChildren) {
                return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
            } else {
                //走这里
                return bind(bindingComponent, view, layoutId);
            }
        }

      接下来调用bind方法。注意bindingComponent为null

        static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
                int layoutId) {
            return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
        }

      sMapper的值如下:

    public class DataBindingUtil {
        private static DataBinderMapper sMapper = new DataBinderMapperImpl();
        private static DataBindingComponent sDefaultComponent = null;
        ......
    }
    
    public class DataBinderMapperImpl extends MergedDataBinderMapper {
      DataBinderMapperImpl() {
        addMapper(new com.example.jetpacklearn.DataBinderMapperImpl());
      }
    }

      DataBinderMapperImpl继承了MergedDataBinderMapper,MergedDataBinderMapper包含了一组DataBinderMapper对象。DataBinderMapper类是用来获取ViewDataBinding的,它会根据View和layoutId返回一个数据绑定类(xxxxxBinding)的实例。在DataBinderMapperImpl初始化,其会添加一个项目中自动生成的jetpacklearn.DataBinderMapperImpl类的实例。此类的代码为:

    public class DataBinderMapperImpl extends DataBinderMapper {
      private static final int LAYOUT_ACTIVITYMAIN = 1;
    
      private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
    
      static {
        INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.jetpacklearn.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
      }
    
      @Override
      public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
        int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
        if(localizedLayoutId > 0) {
          //获取视图的Tag
          final Object tag = view.getTag();
          if(tag == null) {
            throw new RuntimeException("view must have a tag");
          }
          //根据视图的Tag生成不同的ViewDataBinding实例
          switch(localizedLayoutId) {
            case  LAYOUT_ACTIVITYMAIN: {
              if ("layout/activity_main_0".equals(tag)) {
                return new ActivityMainBindingImpl(component, view);
              }
              throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
            }
          }
        }
        return null;
      }
      ......
    }
    

       而sMapper.getDataBinder方法最终会调用上面类中的getDataBinder方法,以生成数据绑定类的实例。在getDataBinder方法,会根据根View的tag来生成不同的ViewDataBinding对象。但是,我们在xml文件中并没有设置tag,那么这里的tag是哪里来的呢?实际上,我们在写完xml文件后,在编译时,数据绑定系统会根据我们的xml文件再生成一个XML文件,并在此XML文件中设置tag。生成的XML文件如下所示:

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity" 
            android:tag="layout/activity_main_0" 
        xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:app="http://schemas.android.com/apk/res-auto" 
        xmlns:tools="http://schemas.android.com/tools" xmlns:MyView="http://schemas.android.com/apk/res-auto">
    
            <EditText
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:id="@+id/userName"
                android:tag="binding_1"       
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/showName"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/showName"
                android:tag="binding_2"      
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toEndOf="@+id/userName"
                app:layout_constraintHorizontal_chainStyle="spread"
                app:layout_constraintEnd_toEndOf="parent"/>
        </androidx.constraintlayout.widget.ConstraintLayout>

      在编译器生成的文件中,不但会给根View设置tag,而且还会给里面使用数据绑定的View设置tag。

      接下来,看一下在实例化ActivityMainBindingImpl时,都会做些什么。

    public class ActivityMainBindingImpl extends ActivityMainBinding  {
    
        @Nullable
        private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
        @Nullable
        private static final android.util.SparseIntArray sViewsWithIds;
        static {
            sIncludes = null;
            sViewsWithIds = null;
        }
        // views
        @NonNull
        private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
        ......
    
        public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
            this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
        }
        private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
            super(bindingComponent, root, 1
                , (android.widget.TextView) bindings[2]
                , (android.widget.EditText) bindings[1]
                );
            this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
            this.mboundView0.setTag(null);
            this.showName.setTag(null);
            this.userName.setTag(null);
            setRootTag(root);
            // listeners
            invalidateAll();
        }
        ......
    }

       在实例化ActivityMainBindingImpl时,其做的工作如下:首先,调用mapBindings方法生成一个视图数组bindings,此视图数组中即含有根View(下标为0),亦有使用数据绑定的视图(下标参考生成的XML中的tag)。然后,调用super方法,去执行父类ViewDataBinding的构造方法。在父类的构造方法中,会构建一个进行数据绑定的Runnable对象,以在收到绑定通知时,进行数据绑定。最后,将ActivityMainBindingIMpl实例放到根View的tag里,并发送一个数据绑定请求。接下来,依次对上面3个工作进行详细分析。

       1、mapBindings方法的代码如下:

        protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
                int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
            //创建视图数组
            Object[] bindings = new Object[numBindings];
            //添加视图元素
            mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
            return bindings;
        }
    
        private static void mapBindings(DataBindingComponent bindingComponent, View view,
                Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
                boolean isRoot) {
            final int indexInIncludes;
            //还没将数据绑定实例放到view的Tag中,此时getBinding的返回值为null
            final ViewDataBinding existingBinding = getBinding(view);
            if (existingBinding != null) {
                return;
            }
            //获取视图的TAG,参考生成的XML文件
            Object objTag = view.getTag();
            final String tag = (objTag instanceof String) ? (String) objTag : null;
            boolean isBound = false;
            if (isRoot && tag != null && tag.startsWith("layout")) {
                final int underscoreIndex = tag.lastIndexOf('_');
                if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                    //从生成的XML文件中知,index为0
                    final int index = parseTagInt(tag, underscoreIndex + 1);
                    if (bindings[index] == null) {
                        //添加根视图
                        bindings[index] = view;
                    }
                    //在案例中,未使用include标签,此值为-1
                    indexInIncludes = includes == null ? -1 : index;
                    isBound = true;
                } else {
                    indexInIncludes = -1;
                }
            } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
                //将子View放到bindings数组中
                //生成的XML文件中,子View的tag格式为binding_{x},此时tagIndex的值就是x
                int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
                if (bindings[tagIndex] == null) {
                    bindings[tagIndex] = view;
                }
                isBound = true;
                indexInIncludes = includes == null ? -1 : tagIndex;
            } else {
                // Not a bound view
                indexInIncludes = -1;
            }
            ......
            //将xml文件中的子View放到bindings数组中
            if (view instanceof  ViewGroup) {
                final ViewGroup viewGroup = (ViewGroup) view;
                final int count = viewGroup.getChildCount();
                int minInclude = 0;
                for (int i = 0; i < count; i++) {
                    final View child = viewGroup.getChildAt(i);
                    boolean isInclude = false;
                    //未使用include标签,此处先略过
                    if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                        ......
                    }
                    if (!isInclude) {
                        //递归调用当前方法,传入child,且isRoot为false以绑定子视图
                        mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                    }
                }
            }
        }

      2、super方法对应的父类ViewDataBinding的构造方法如下:

        protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
            mBindingComponent = bindingComponent;
            //此数组用于Observable数据变化时的回调
            mLocalFieldObservers = new WeakListener[localFieldCount];
            this.mRoot = root;
            if (Looper.myLooper() == null) {
                throw new IllegalStateException("DataBinding must be created in view's UI Thread");
            }
            
            if (USE_CHOREOGRAPHER) {
                //SDK>16时走这里
                mChoreographer = Choreographer.getInstance();
                mFrameCallback = new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                        mRebindRunnable.run();
                    }
                };
            } else {
                mFrameCallback = null;
                mUIThreadHandler = new Handler(Looper.myLooper());
            }
        }
    
        private final Runnable mRebindRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mPendingRebind = false;
                }
                processReferenceQueue();
    
                if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
                    // Nested so that we don't get a lint warning in IntelliJ
                    if (!mRoot.isAttachedToWindow()) {
                        // Don't execute the pending bindings until the View
                        // is attached again.
                        mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                        mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                        return;
                    }
                }
                //调用此方法,进行数据绑定
                executePendingBindings();
            }
        };

     3、将绑定类的实例放到根View的Tag,并发送绑定请求

            setRootTag(root);
            // listeners
            invalidateAll();

      setRootTag代码如下:

        protected void setRootTag(View view) {
            view.setTag(R.id.dataBinding, this);
        }

      invalidateAll方法代码如下:

        public void invalidateAll() {
            synchronized(this) {
                    mDirtyFlags = 0x4L;
            }
            requestRebind();
        }
        protected void requestRebind() {
            if (mContainingBinding != null) {
                mContainingBinding.requestRebind();
            } else {
                final LifecycleOwner owner = this.mLifecycleOwner;
    
                ......
    
                synchronized (this) {
                    if (mPendingRebind) {
                        return;
                    }
                    mPendingRebind = true;
                }
                if (USE_CHOREOGRAPHER) {
                    //SDK>16走这里
                    //发送数据绑定请求,最终会调用mRebindRunnable的executePendingBindings方法
                    mChoreographer.postFrameCallback(mFrameCallback);
                } else {
                    mUIThreadHandler.post(mRebindRunnable);
                }
            }
        }

    调用的executePendingBindings方法的代码如下:

        public void executePendingBindings() {
            if (mContainingBinding == null) {
                //走这里
                executeBindingsInternal();
            } else {
                //include标签下的View走这里
                mContainingBinding.executePendingBindings();
            }
        }
    
        private void executeBindingsInternal() {
            if (mIsExecutingPendingBindings) {
                requestRebind();
                return;
            }
            if (!hasPendingBindings()) {
                return;
            }
            mIsExecutingPendingBindings = true;
            mRebindHalted = false;
    
            ......
    
            if (!mRebindHalted) {
                //调用此方法进行数据绑定,此方法的真正实现在生成的xxxxBindingImpl类中(如ActivityMainBindingImpl)
                executeBindings();
                if (mRebindCallbacks != null) {
                    mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
                }
            }
            mIsExecutingPendingBindings = false;
        }

      executeBindings方法的代码如下:

        protected void executeBindings() {
            long dirtyFlags = 0;
            synchronized(this) {
                dirtyFlags = mDirtyFlags;
                mDirtyFlags = 0;
            }
            com.example.jetpacklearn.viewModel.UserViewModel userVM = mUserVM;
            androidx.databinding.ObservableField<java.lang.String> userVMName = null;
            java.lang.String userVMNameGet = null;
    
            if ((dirtyFlags & 0x7L) != 0) {
    
    
    
                    if (userVM != null) {
                        // read userVM.name
                        userVMName = userVM.getName();
                    }
                    //给userVMName添加一个数据变化的监听器;实现双向绑定的步骤1
                    updateRegistration(0, userVMName);
    
    
                    if (userVMName != null) {
                        // read userVM.name.get()
                        userVMNameGet = userVMName.get();
                    }
            }
            // batch finished
            if ((dirtyFlags & 0x7L) != 0) {
                // api target 1
    
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.showName, userVMNameGet);
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.userName, userVMNameGet);
            }
            if ((dirtyFlags & 0x4L) != 0) {
                // api target 1
    
               //给userName(EditView)添加文本监听器;实现双向绑定的步骤2 
    androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.userName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, userNameandroidTextAttrChanged);
            }
        }

      为了实现双向绑定,既需要为数据设置监听器,监听数据变化;也需要为View设置监听器,以监听View的变化。在此,通过updateRegistration方法对数据设置监听器;通过最后一行的setTextWatcher方法对View设置监听器。因此,主要介绍中updateRegistration方法和setTextWatcher中的参数userNameAndroidTextAttrChanged。这两部分是实现双向绑定的关键。先介绍updateRegistration方法。

      3.1 给Modle设置监听器

      updateRegistration方法代码如下:

        protected boolean updateRegistration(int localFieldId, Observable observable) {
            return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
        }

      CREATE_PROPERTY_LISTENER的作用是 为Observable类添加一个回调接口,此接口会在数据变化时,通知观察者。其代码如下:

        private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
            @Override
            public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
                return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
            }
        };
    private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
                implements ObservableReference<Observable> {
            final WeakListener<Observable> mListener;
    
            public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
                //创建一个WeakListener对象,此对象在给Observable类设置回调接口时,起到承接作用。
                //WeakListener中包含了ViewDataBinding实例,回调接口的引用(当前类)
                mListener = new WeakListener<Observable>(binder, localFieldId, this);
            }
    
            @Override
            public WeakListener<Observable> getListener() {
                return mListener;
            }
    
            @Override
            public void addListener(Observable target) {
                //给Observable添加回调接口
                target.addOnPropertyChangedCallback(this);
            }
    
            ......
    
            //Observable类的子类会在其set方法中调用此方法
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                ViewDataBinding binder = mListener.getBinder();
                if (binder == null) {
                    return;
                }
                Observable obj = mListener.getTarget();
                if (obj != sender) {
                    return; // notification from the wrong object?
                }
                //数据变化时,会调用此方法,通知绑定器重新绑定数据
                binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
            }
        }

       WeakListener类的代码如下:

    private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
            private final ObservableReference<T> mObservable;
            protected final int mLocalFieldId;
            private T mTarget;
    
            public WeakListener(ViewDataBinding binder, int localFieldId,
                    ObservableReference<T> observable) {
                //binder为ViewDataBinding实例
                super(binder, sReferenceQueue);
                //标识ViewDataBinding中的Observable字段的ID
                mLocalFieldId = localFieldId;
                //实现了回调接口OnPropertyChangedCallback
                mObservable = observable;
            }
            ......
    }

       handleFieldChange代码如下:

        private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
            ......
    
            //调用ActivityMainBinding的onFieldChange方法,标注哪些数据发送了变化
            boolean result = onFieldChange(mLocalFieldId, object, fieldId);
            if (result) {
                //请求重新进行数据绑定
                requestRebind();
            }
        }

      updateRegistration(localFIeldId,observable,CREATE_PROPERTY_LISTENER)的代码如下:

        private boolean updateRegistration(int localFieldId, Object observable,
                CreateWeakListener listenerCreator) {
            if (observable == null) {
                return unregisterFrom(localFieldId);
            }
            WeakListener listener = mLocalFieldObservers[localFieldId];
            if (listener == null) {
                //给observable设置回调接口OnPropertyChangedCallback
                registerTo(localFieldId, observable, listenerCreator);
                return true;
            }
            if (listener.getTarget() == observable) {
                return false;//nothing to do, same object
            }
            unregisterFrom(localFieldId);
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }

      registerTo方法代码如下:

        protected void registerTo(int localFieldId, Object observable,
                CreateWeakListener listenerCreator) {
            if (observable == null) {
                return;
            }
            WeakListener listener = mLocalFieldObservers[localFieldId];
            if (listener == null) {
                //生成一个WeakListener对象,此对象包含了OnPropertyChangedCallback的引用
                listener = listenerCreator.create(this, localFieldId);
                mLocalFieldObservers[localFieldId] = listener;
                if (mLifecycleOwner != null) {
                    listener.setLifecycleOwner(mLifecycleOwner);
                }
            }
            //给observable添加回调接口(OnPropertyChangedCallback)
            listener.setTarget(observable);
        }

       setTarget方法的代码如下:

            public void setTarget(T object) {
                unregister();
                mTarget = object;
                if (mTarget != null) {
                    //mObservable为WeakPropertyListener类实例,其addListener代码在上面
                    //mTarget是一个observable对象,此方法作用是给此对象添加一个回调接口
                    mObservable.addListener(mTarget);
                }
            }

      题外话:在第一次看到此代码时,感觉很绕,回调很多,使人不容易看懂,觉着为什么不直接使用以下这样的写法:

    userVMName.addOnPropertyChangedCallback(new XXXXCallBack())

      这样的写法,可以让人一看就懂。一眼就能看出此行代码的作用是什么,添加了什么样的回调接口。而官方代码则看的绕来绕去的,不易理解。后来思考了以下,发现官方的写法其实挺有道理的。官方代码的写法使代码的耦合度降低了,符合“开闭原则”,“依赖倒置原则”。而我们的简单写方法则不符合这两个原则。比如,当我们要更换一个新的Callback时,简单写法需要修改addxxxCallback方法的参数,而官方的代码,则采用扩展的方法实现这种需求,不需要对原来代码进行修改。即只需要重新实现一个CreateListener的类,并添加一个新的updateRegistration方法就好。这样虽然多写了一些代码,但是保证了“开闭原则”,即对修改关闭,对扩展开放。

      在此,通过下面的类图再回顾一下给Observable添加PropertyChangedCallback的过程。首先,创建一个WeakListener对象。然后,调用此对象的setTarget(Observable)方法,给Observable添加回调接口。此过程的核心是WeakListener类,此类是一个“虚假”的监听器类,其并没有实现监听功能。而是持有了回调接口的引用,并负责给Observable对象设置回调接口。官方将“给Observable对象设置回调接口”这个功能抽象为了一个接口ObservableReference,此举符合“依赖倒置”原则,方便日后对代码进行扩展。

       3.2、给View设置监听器

      上面介绍了如何给Model设置监听器,接下来介绍如何给View设置监听器。以上面的案例代码为例,绑定系统通过setTextWatcher给EditView设置监听器,以在EditView的文字变化时,通知Model去同步数据,setTextWatcher方法如下:

    
        protected void executeBindings() {
    
            ......
    
            if ((dirtyFlags & 0x4L) != 0) {
                // api target 1
    
                androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.userName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, userNameandroidTextAttrChanged);
            }
        }

      userNameandroidTextAttrChanged对应的类的代码如下:

        private androidx.databinding.InverseBindingListener userNameandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
            @Override
            public void onChange() {
                //EditView的文本变化时会调用此方法
                java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(userName);
    
                boolean userVMNameJavaLangObjectNull = false;
                // userVM
                com.example.jetpacklearn.viewModel.UserViewModel userVM = mUserVM;
                // userVM != null
                boolean userVMJavaLangObjectNull = false;
                // userVM.name
                androidx.databinding.ObservableField<java.lang.String> userVMName = null;
                // userVM.name.get()
                java.lang.String userVMNameGet = null;
    
                userVMJavaLangObjectNull = (userVM) != (null);
                if (userVMJavaLangObjectNull) {
    
                    userVMName = userVM.getName();
    
                    userVMNameJavaLangObjectNull = (userVMName) != (null);
                    if (userVMNameJavaLangObjectNull) {
                        //将EditView的text赋值给userVMName
                        userVMName.set(((java.lang.String) (callbackArg_0)));
                    }
                }
            }
        };

        userVMName的set方法代码如下:

    public class ObservableField<T> extends BaseObservableField implements Serializable {
        static final long serialVersionUID = 1L;
        private T mValue;
    
        ......
    
        /**
         * Set the stored value.
         *
         * @param value The new value
         */
        public void set(T value) {
            if (value != mValue) {
                //避免无线循环,只有数据真正改变时,才通知数据绑定器进行数据绑定
                mValue = value;
                notifyChange();
            }
        }
    }

       至此,双向绑定原理的介绍基本结束。其本质可以看作分别给数据源Model和视图View设置监听器,以在数据变化时,通知彼此同步变化。

    展开全文
  • 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化。这也算是vue的精髓之处了。单项数据绑定是使用状态管理工具(如redux...

    前言:

    什么是数据双向绑定?

    vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化。这也算是vue的精髓之处了。单项数据绑定是使用状态管理工具(如redux)的前提。如果我们使用vuex,那么数据流也是单项的,这时就会和双向数据绑定有冲突。

    为什么要实现数据的双向绑定?

    在vue中,如果使用vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,vue的双向数据绑定用起来就特别舒服了。

    即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

    一 、vue双向绑定原理

    1. vue中的v-model可以实现双向绑定,采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图
    2. 具体步骤:
    • 需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergetter
      这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
      -compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
    • Watcher订阅者是ObserverCompile之间通信的桥梁,主要做的事情是:
      1、在自身实例化时往属性订阅器(dep)里面添加自己
      2、自身必须有一个update()方法
      3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
    • MVVM作为数据绑定的入口,整合ObserverCompileWatcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起ObserverCompile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

    名词解释:
    一、 什么是setter、getter?
    对象有两种属性:
    1 . 数据属性:就是我们经常使用的属性
    2 . 访问器属性:也称存取属性(存取器属性就是一组获取和设置值的函数)
    在这里插入图片描述
    控制台打印:
    在这里插入图片描述
    数据属性就是a和b;
    get和set就是关键字,他们后面各自对应一个函数,这个函数就是上面红字部分所讲的,存储器属性。
    get对应的方法称为getter,负责获取值,它不带任何参数。set对应的方法 是setter,负责设置值,在它的函数体中,一切的return都是无效的。
    二、什么是Object.defineProperty()?
    对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。
    定义对象可以使用构造函数或字面量的形式:
    在这里插入图片描述
    除了以上添加属性的方式,当然还可以使用Object.defineProperty定义新属性或修改原有的属性;
    语法:Object.defineProperty(obj, prop, descriptor)
    参数 :
    obj:必需,目标对象;
    prop :必需,需定义或修改的属性的名字;
    descriptor:必需,目标属性所拥有的特性;
    返回值:

    传入函数的对象,即第一个参数obj;

    使用:

    在这里插入图片描述
    在这里插入图片描述

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

    2021-03-28 20:55:16
    1.双向绑定 [1]定义 在视图上的数据发生了变化,data中的数据也要对应改变; data中的数据发生了变化,视图上的数据也要对应改变; [2]原理 vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,...
  • vue2与vue3的双向绑定写法不同 vue使用Object.defineProperty来劫持对象属性 而vue3使用Proxy代理来实现双向绑定。 vue2: <div class="data"></div> <label>vue2:</label> <input ...
  • V-model的双向绑定原理

    2021-09-17 21:58:39
    在学习v-model的过程中手贱自己用原生JS实现了一遍v-model的双向绑定。。。 首先,v-model只是个语法糖,实际上原理给表单元素用绑定值和input或change事件,举个例子: <input v-model="value" /> //实际上...
  • vue数据双向绑定原理

    2021-02-03 14:11:00
    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的 var vm = new Vue({ data: { obj: { a: 1 } }, created: function () { console.log(this.obj); } }); 结果 我们可以看到属性a有两...
  • vue源码之数据双向绑定原理一.双向绑定的原理介绍二.代码简单实现 这里什么说是vue2.0的双向绑定原理是因为和vue3.0的实现方式是有区别的。 一.双向绑定的原理介绍 vue的双向绑定是数据和视图的同步变化,即当数据...
  • 目录原理介绍DEMO实现双向绑定第一部分是Observer:第二部分是Watcher:第三部分是Dep:Vue中的双向绑定Vue中的Observer:Vue中的Dep:Vue中的Watcher:总结一下 原理介绍 Vue.js是通过数据劫持以及结合发布者-订阅...
  • JS 几个流行的框架 Vuejs、AngularJS 都使用 MVVM 模式,该模式叫做视图模型双向数据绑定,以达到数据和视图快速同步的目的。 主要体现就是表单元素值变,JS变量值改变,若页面上有输出,输出值也改变,典型的...
  • 文章目录前言一、源码分析的理解:二、双向绑定原理网上的说法:二、其他理解1.父子组件通信-->v-model总结 前言 双向绑定除了数据驱动 DOM 外, DOM 的变化反过来影响数据,是一个双向关系,在 Vue 中,我们...
  • v-model双向绑定原理

    2021-09-07 20:22:52
    数据双向绑定也就是说,模型数据变化更新视图,视图变化更新模型数据。 v-model 实现的原理是采用 数据劫持 + 发布者-订阅者模式 实现。 监听模型数据变化可以使用Object.defineProperty()。 监听视图变化(这里...
  • 双向数据绑定机制: 官方:vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。 第一步: 需要...
  • 开头 此文希望能给想跳槽和面试朋友一些参考。 金九银十已过,面试的狂热季也已结束,小编也正是选择了在金九十银跳槽,之前在腾讯做了五年Android开发工作,之后...HashMap1.7和1.8的实现原理 final关键字,为什么匿
  • v-model绑定在表单上 v-model绑定在表单上时,v-model就是v-bind绑定value和v-on监听input事件的结合 v-model = :value + @input 用v-bind和input事件模拟v-...原理:通过 :value 绑定 username变量,每次输...
  • 数据响应式: 数据模型是JavaScript 的普通对象,当我们修改数据时,视图会进行更新,...直接看案例 vue2.0 双向绑定原理 <!-- body --> <div id="app">app</div> <script> let data={ msg
  • Vue数据双向绑定原理是通过数据劫持结合“发布者-订阅者”模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图。Vue数据双向...
  • vue3.x双向绑定原理

    2021-01-27 10:48:20
    Vue3.x是通过proxy(代理)实现的数据的双向绑定。proxy跟Object.defineProperty一样也可以给对象的属性添加两个方法get&set。 区别:Object.defineProperty一次性只能给对象的一个属性添加get&set方法,而...
  • vue3 Proxy 双向绑定原理 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" ...
  • 习惯了用父子组件传值,也尝试下v-model吧~ 第一种:使用v-model // 父组件 <template> <div> <manage-dialog v-model="manageDialog"/> </div>... name: 'Manage',
  • v-model radio v-model checkbox v-model select 值绑定
  • VUE双向绑定原理

    2021-08-22 15:32:33
    VUE双向绑定原理 一 双向数据绑定 目前几种主流的mvc(mvvm)框架都实现了单向数据绑定,双向数据绑定无非就是在单向绑定的基础上给可输入元素(input, textarea等)添加了change(input)事件,来动态修改model和view。...
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...
  • Object.defineProperty( ) v-model 描述 仿vue使用Proxy实现双向绑定 双向绑定原理
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...
  • 双向绑定原理

    2021-06-16 22:00:03
    一、双向绑定原理 双向绑定的话用到了vue的技术栈 双向绑定在vue很常见,基本上没个页面都可以用到 vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,...

空空如也

空空如也

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

双向绑定的原理