GithubHelp home page GithubHelp logo

第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的 about daily-interview-question HOT 15 OPEN

long-joan avatar long-joan commented on May 3, 2024 72
第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的

from daily-interview-question.

Comments (15)

Forever17s avatar Forever17s commented on May 3, 2024 44

核心是利用ES5的Object.defineProperty,这也是Vue.js为什么不能兼容IE8及以下浏览器的原因。

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

Object.defineProperty(
obj, // 定义属性的对象
prop, // 要定义或修改的属性的名称
descriptor, // 将被定义或修改属性的描述符【核心】

  • observe的功能就是用来监测数据的变化。实现方式是给非VNode的对象类型数据添加一个Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个Observer对象实例。

Observer是一个类,它的作用是给对象属性添加getter和setter,用于 依赖收集派发更新

依赖收集getter(重点关注以下两点)

  • *const dep = new Dep() // 实例化一个Dep实例
  • *在get函数中通过dep.depend做依赖收集

Dep是一个Class,它定义了一些属性和方法,它有一个静态属性target,这是一个全局唯一Watcher【同一时间内只能有一个全局的Watcher被计算】。Dep实际上就是对Watcher的一种管理,Dep脱离Watcher单独存在是没有意义的。Watcher和Dep就是典型的观察者设计模式。

Watcher是一个Class,在它的构造函数中定义了一些和Dep相关的属性:

this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()				

收集过程:当我们实例化一个渲染watcher的时候,首先进入watcher的构造函数逻辑,然后执行他的this.get()方法,进入get函数把Dep.target赋值为当前渲染watcher并压栈(为了恢复用)。接着执行vm._render()方法,生成渲染VNode,并且在这个过程对vm上的数据访问,这个时候就触发数据对象的getter(在此期间执行Dep.target.addDep(this)方法,将watcher订阅到这个数据持有的dep的subs中,为后续数据变化时通知到哪些subs做准备)。然后递归遍历添加所有子项的getter。

Watcher在构造函数中初始化两个Dep实例数组。newDeps代表新添加的Dep实例数组,deps代表上一次添加的Dep实例数组。
依赖清空:在执行清空依赖(cleanupDeps)函数时,会首先遍历deps,移除对dep的订阅,然后把newDepsIds和depIds交换,newDeps和deps交换,并把newDepIds和newDeps清空。考虑场景,在条件渲染时,及时对不需要渲染数据的订阅移除,减少性能浪费。

考虑到Vue是数据驱动的,所以每次数据变化都会重写Render,那么vm._render()方法会再次执行,并再次触发数据

收集依赖的目的是为了当这些响应式数据发生变化,触发它们的setter的时候,能知道应该通知哪些订阅者去做相应的逻辑处理【派发更新

派发更新setter(重点关注以下两点)

  • *childOb = !shallow && observe(newVal) // 如果shallow为false的情况,会对新设置的值变成一个响应式对象
  • *dep.notify() // 通知所有订阅者

派发过程:当我们组件中对响应的数据做了修改,就会触发setter的逻辑,最后调用dep.notify()方法,它是Dep的一个实例方法。具体做法是遍历依赖收集中建立的subs,也就是Watcher的实例数组【subs数组在依赖收集getter中被添加,期间通过一些逻辑处理判断保证同一数据不会被添加多次】,然后调用每一个watcher的update方法。

update函数中有个queueWatcher(this)方法引入了队列的概念,是vue在做派发更新时优化的一个点,它并不会每次数据改变都会触发watcher回调,而是把这些watcher先添加到一个队列中,然后在nextTick后执行watcher的run函数

队列排序保证:

  1. 组件的更新由父到子。父组件创建早于子组件,watcher的创建也是
  2. 用户自定义watcher要早于渲染watcher执行,因为用户自定义watcher是在渲染watcher前创建的
  3. 如果一个组件在父组件watcher执行期间被销毁,那么它对应的watcher执行都可以被跳过,所以父组件的watcher应该先执行。
  • 队列遍历:排序完成后,对队列进行遍历,拿到对应的watcher,执行watcher.run()

run函数解析:先通过this.get()得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep模式任何一个条件,则执行watcher的回调,注意回调函数执行的时候会把第一个参数和第二个参数传入新值value和旧值oldValue,这就是当我们自己添加watcher时候可以在参数中取到新旧值的来源。对应渲染watcher而言,在执行this.get()方法求值的时候,会执行getter方法。因此在我们修改组件相关数据时候,会触发组件重新渲染,接着重新执行patch的过程

手写一个数据绑定:

<input id="input" type="text" />
<div id="text"></div>

let input = document.getElementById("input");
let text = document.getElementById("text");
let data = { value: "" };
Object.defineProperty(data, "value", {
  set: function(val) {
    text.innerHTML = val;
    input.value = val;
  },
  get: function() {
    return input.value;
  }
});
input.onkeyup = function(e) {
  data.value = e.target.value;
};

from daily-interview-question.

negativeentropy9 avatar negativeentropy9 commented on May 3, 2024 25

始终感觉这个问题有点问题,明明是单向绑定,只是 m -> v,在 vue 2.x 中 通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调,这个过程是 vue 黑盒提供的,也就是说数据驱动视图,开发人员只需关注数据的变更即可;再说 v -> m,通过 v-model 的方式,指令添加是开发人员加的吧,如果一个组件有多个 v-model ,你要自己写 v-on 和 data 的修改吧。

from daily-interview-question.

CodingMeUp avatar CodingMeUp commented on May 3, 2024 10

Model 改变 View的过程: 依赖于ES5的object.defindeProperty,通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调(不同于观察者模式,是发布订阅 + 中介)
View 改变 Model的过程: 依赖于 v-model ,该语法糖实现是在单向数据绑定的基础上,增加事件监听并赋值给对应的Model

附带补一个问题, 如果react也要双向绑定(虽然推荐是单向的),其实早期mixin【React.addons.LinkedStateMixin】可以实现, 也就是搞一些简单的setstate封装吧

from daily-interview-question.

db46rt00ors avatar db46rt00ors commented on May 3, 2024 6

底层就是defineProperty get是读取之前的旧数据,set中如果发现数据没改 直接return 原始值 ,如果改了就直接修改为NewValue
这个是一个简单的demo

<body>
    <div id="app">
        <input type="text" id="model"><br />
        <div id="modelText"></div>
    </div>
    <script>
        var model = document.querySelector("#model");
        var modelText = document.querySelector("#modelText");
        var defaultName = "defaultName";
        var userInfo = {}
        model.value = defaultName;
        Object.defineProperty(userInfo, "name", {
            get: function () {
                return defaultName;
            },
            set: function (value) {
                defaultName = value;
                model.value = value;
                console.log(value);
                modelText.textContent = value;
            }
        })

        userInfo.name = "new value";
        var isEnd = true;

        model.addEventListener("keyup", function () {
            if (isEnd) {
                userInfo.name = this.value;
            }
        }, false)
        //加入监听中文输入事件
        model.addEventListener("compositionstart", function () {
            console.log("开始输入中文");
            isEnd = false;
        })
        model.addEventListener("compositionend", function () {
            isEnd = true;
            console.log("结束输入中文");
        })
    </script>
</body>

这个图是 vue源码
image
他是最后通知 dep.notify() notify 调用 组件的watcher 中的update函数 大致就是这样

from daily-interview-question.

Mrfujianfei avatar Mrfujianfei commented on May 3, 2024 4

vue通过Object.defineProperty 劫持传进来的数据, 然后在数据getter的时候订阅重新编译模板的消息,然后通过js监听元素的事件,这里input事件也好,keyup事件也好,总是监听输入框值变化,将新的值重新赋值给被劫持的data,这样就会触发setter函数,再setter函数中就会去发布重新编译模板的消息;

from daily-interview-question.

seawind8888 avatar seawind8888 commented on May 3, 2024 3

一个比较直观的图,看了就明白了 https://seawind8888.github.io/2019/07/15/%E4%B8%80%E5%9B%BE%E7%9C%8B%E6%87%82Vue%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86/

from daily-interview-question.

ndfhsledk avatar ndfhsledk commented on May 3, 2024 1

v-model是v-bind和v-on的语法糖。
v-bind即model=>view,当model数据发生变化,在setter中,去触发对应组件重新生成Vnode,对比新旧虚拟树,更新差异。
v-on即view=>model,view操作后,触发事件,调用回调函数,在回调函数中更新model

from daily-interview-question.

XuedaoYuan avatar XuedaoYuan commented on May 3, 2024 1

其实都是单向数据流,v-model也只是语法糖。主要**还是 数据驱动 视图,按照这个去开发就完事了。不要写的不伦不类

from daily-interview-question.

cleverboy32 avatar cleverboy32 commented on May 3, 2024

了解更多

from daily-interview-question.

Primise avatar Primise commented on May 3, 2024

从页面渲染到加载数据,依赖于ES5的object.defindeProperty 此时收益数据依赖并当数据reactive,当修改数据的时候触发set 调用以来的notify并触发更新

from daily-interview-question.

Maricaya avatar Maricaya commented on May 3, 2024
<div id="content">
    name: {{name}}<br/>
    <input type="text" v-model = 'name'>
  </div>
<script>
   const el = document.getElementById('content');

   const template = el.innerHTML;
   
   const _data = {
     name: 'mark',
   }

   //new Proxy(target, handler);
   let changeName = new Proxy(_data, {
     set(obj, name, value){
       obj[name] = value;
       render()
     }

   })
   render();
   function render(){
     el.innerHTML = template.replace(/\{\{\w+\}\}/g, str=>{
       str = str.substring(2, str.length-2);
       return _data[str];
     }) 
     Array.from(el.getElementsByTagName('input')).filter(ele => {
       return ele.getAttribute('v-model');
     }).forEach(input=>{
       let name = input.getAttribute('v-model');
       input.value = changeName[name];
       input.oninput = function(){
         changeName[name] = this.value;
       }
     })
   }
 
 </script>

input 中输入内容,焦点会消失

from daily-interview-question.

Liu-10004 avatar Liu-10004 commented on May 3, 2024
<div id="content">
    name: {{name}}<br/>
    <input type="text" v-model = 'name'>
  </div>
<script>
   const el = document.getElementById('content');

   const template = el.innerHTML;
   
   const _data = {
     name: 'mark',
   }

   //new Proxy(target, handler);
   let changeName = new Proxy(_data, {
     set(obj, name, value){
       obj[name] = value;
       render()
     }

   })
   render();
   function render(){
     el.innerHTML = template.replace(/\{\{\w+\}\}/g, str=>{
       str = str.substring(2, str.length-2);
       return _data[str];
     }) 
     Array.from(el.getElementsByTagName('input')).filter(ele => {
       return ele.getAttribute('v-model');
     }).forEach(input=>{
       let name = input.getAttribute('v-model');
       input.value = changeName[name];
       input.oninput = function(){
         changeName[name] = this.value;
       }
     })
   }
 
 </script>
el.innerHTML = template.replace(/\{\{(\w+)\}\}/g, ($0,$1)=>{
       return _data[$1];
     }) 

这样会不会更简洁一些

from daily-interview-question.

Vevean avatar Vevean commented on May 3, 2024

想问一下Vue的双向绑定和vue的响应式原理有什么不同啊

from daily-interview-question.

861621821 avatar 861621821 commented on May 3, 2024

双向数据绑定不应该指v-model吗?而Object.defineProperty那一坨流程是Vue响应式系统吧?

from daily-interview-question.

gookyn avatar gookyn commented on May 3, 2024

Vue 的双向数据绑定和响应式数据是两个不同的概念。

双向数据绑定

  • Vue 中的双向数据绑定指的是当一个数据发生变化时,视图也会随之更新;而当用户在视图中修改了数据时,数据模型也会相应地更新
  • 双向数据绑定是一种语法糖,它允许将表单元素(如input、textarea等)上的值与 Vue 实例中的数据属性进行双向绑定

这种双向绑定可以通过 v-model 指令来实现,例如:

<input type="text" v-model="message">

上面的代码表示将输入框与 Vue 实例中的 message 属性进行双向绑定,当用户在输入框中输入内容时,message 属性的值也会随之改变。

响应式数据

Vue 中的响应式数据指的是当一个组件的数据发生变化时,Vue 会自动检测到这些变化,并且重新渲染视图。

Vue 通过 ProxyObject.defineProperty() 对数据对象进行代理或劫持,从而能够监听到数据的变化,并通知相关的组件进行重新渲染。例如:

const data = { message: 'Hello, Vue!' }
const vm = new Vue({
  el: '#app',
  data: data
})

上面的代码创建了一个 Vue 实例,并将 data 对象传入其中作为其数据模型。当 data.message 发生变化时,Vue 会自动检测到并通知相关的组件进行重新渲染。


因此:

  • 双向数据绑定主要用于处理表单等交互操作,让用户能够方便地修改数据
  • 响应式数据则是 Vue 实现数据驱动视图的核心机制,通过它使得视图能够自动地响应数据的变化

from daily-interview-question.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.