一文彻底搞懂Vue的MVVM响应式原理

目录
  • 前言
  • Vue的MVVM原理
  • 创建一个html示例
  • 在MVue.js中创建MVue入口
  • 创建Compile
    • 1.处理元素节点compileElement(child)
    • 2.处理文本节点compileText(child)
    • 3.实现compileUtil指令处理
  • 更新器Updater更新数据
  • 实现数据观察者Observer
  • 数据依赖器Dep
  • 观察者Watcher
  • 实现视图驱动数据驱动视图
  • 总结

前言

这些天都在面试,每当我被面试官们问到Vue响应式原理时,回答得都很肤浅。如果您在回答时也只是停留在MVVM框架是model层、view层和viewmodel层这样的双向数据绑定,那么建议您彻底搞定Vue的MVVM响应式原理。
(全文约13900字,阅读时间约25分钟。建议有一定vue基础后再阅读)

怎么来的?

要想清楚的知道某件事物的原理,就该追根溯源,刨根问底。在Vue之前,各框架都是怎么去实现MVVM双向绑定的呢?

大致分为以下几种:

  • 发布者-订阅者模式(backbone.js)脏值检查(angular.js)数据劫持(vue.js)
  • 发布者-订阅者模式,通过sub、pub实现视图的监听绑定,通常的做法是vm.$set(‘property’, value)。

脏值检查,内部其实就是setnterval,当然,为了节约性能,不显的那么low,一般是对特定的事件执行脏值检查:

DOM事件,如输入文本、点击按钮(ng-click)XHR响应事件($http)浏览器locaton 变更事件($location)Timer事件($timeout, $interval)执行$digest()或 $apply()

vue则是采用发布者-订阅者模式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue的MVVM原理

话不多说,先上图

首先,请尽可能记住这一张图,并能够自己画出来,后面所有原理都是围绕这张图展开。感觉很懵逼对么?不过,相信许多人在Vue官方文档里看过这张图:

其实,这两张图要表达的是一个意思——二者都表示了双向数据绑定的原理流程,官方文档中展示的更为简洁一些。看您更能接受哪种描述,后面自己实现响应式原理后,这两张图都能记得住了。

这里就用第一张图来介绍,在我们创建一个vue实例时,其实vue做了这些事情:

创建了入口函数,分别new了一个数据观察者Observer和一个指令解析器Compile;Compile解析所有DOM节点上的vue指令,提交到更新器Updater(实际上是一个对象);Updater把数据(如{{}},msg,@click)替换,完成页面初始化渲染;Observer使用Object.defineProperty劫持数据,其中的getter和setter通知变化给依赖器Dep;Dep中加入观察者Watcher,当数据发生变化时,通知Watcher更新;Watcher取到旧值和新值,在回调函数中通知Updater更新视图;Compile中每个指令都new了一个Watcher,用于触发Watcher的回调函数进行更新。 简单实现Vue的响应式原理 完整源码:详见

按照前面的思路,下面我们来一步一步实现一个简单的MVVM响应式系统,加深我们对响应式原理的理解。

创建一个html示例

现在我们创建了一个简单的Vue渲染示例,我们要做的就是使用自己的MVue去把里面的data、msg、htmlStr、methods中的数据都渲染到标签上。完成数据驱动视图、视图驱动数据驱动视图。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id ="app">
        <h2>{{person.name}} -- {{person.age}}</h2>
        <h3>{{person.fav}}</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
        <h3>{{msg}}</h3>
        <div v-text="person.fav"></div>
        <div v-text="msg"></div>
        <div v-html="htmlStr"></div>
        <input type="text" v-model="msg">
        <button v-on:click="handleClick">click</button>
        <button @click="handleClick">@click2</button>

    </div>
    <script src="./Observer.js"></script>
    <script src="./MVue.js"></script>
    <script>
     //创建Vue实例,得到 ViewModel
     const vm = new MVue({
        el: '#app',
        data: {
            person: {
                name: "我的vue",
                age: 18,
                fav: "坦克世界"
            },
            msg: "学习MVVM框架原理",
            htmlStr: "<h3>热爱前端,金子总会发光</h3>"
        },
        methods: {
            handleClick() {
                console.log(this);
            }
        }
     });
    </script>
</body>
</html>

在MVue.js中创建MVue入口

class MVue {
  constructor(options) {
    this.$el = options.el;
    this.$data = options.data;
    this.$options = options;

    if (this.$el) {
      // 1、实现一个数据观察者
      // 2、实现一个指令观察者
      new Compile(this.$el, this);
    }
  }

思路:

首先自然是要构建MVue这一个类,MVue类构造函数中需要用到options参数和其中的el、data。
然后需要保证el存在条件下,先实现一个指令解析器Compile,后面再去实现Observer观察者。
显然,Compile应该需要传入MVue实例的el和整个MVue实例,用来解析标签的指令。

创建Compile

思路:在解析标签指令之前,我们首先做的是:

判断el是不是元素节点,如果不是,就要取到el这个标签,然后传入vm实例;递归拿到所有子节点,便于下一步去解析它们。【注意:这一步会频繁触发页面的回流和重绘,所以我们需要把节点先存入文档碎片对象中,就相当于把他们放到了内存中,减少了页面的回流和重绘。】在文档碎片对象中编译好模板;最后再把文档碎片对象追加到根元素上。

class Compile {
  constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el);
    this.vm = vm;
    // 获取文档碎片对象 放入内存中会减少页面的回流和重绘
    const fragment = this.node2Fragment(this.el);
    // 编译模板
    this.compile(fragment);

    // 追加子元素到根元素
    this.el.appendChild(fragment);
  }

这里我们先自己定义了几个方法:

  • 判断是否是元素节点isElementNode(el)
  • 存入文档碎片对象node2Fragment(el)
  • 编译模板compile(fragment)

分别在构造函数之后去实现:

 node2Fragment(el) {
    // 创建文档碎片对象
    const f = document.createDocumentFragment();
    // 递归放入
    let firstChild;
    while ((firstChild = el.firstChild)) {
      f.appendChild(firstChild);
    }
    return f;
  }
  isElementNode(node) {
    return node.nodeType === 1;
  }

编译模板compile(fragment)实现思路:递归获取所有子节点,判断节点是元素节点还是文本节点,再分别定义两个方法compileElement(child)compileText(child)去处理这两种节点。

compile(fragment) {
    // 获取子节点
    const childNodes = fragment.childNodes;
    [...childNodes].forEach((child) => {
      if (this.isElementNode(child)) {
        // 是元素节点
        // 编译元素节点
        // console.log("元素节点",child);
        this.compileElement(child);
      } else {
        // 是文本节点
        // 编译文本节点
        // console.log("文本节点", child);
        this.compileText(child);
      }

      // 一层一层递归遍历
      if (child.childNodes && child.childNodes.length) {
        this.compile(child);
      }
    });
  }

好了,现在Compile的一个基本框架已经搭好了。希望看到这里的您还没有犯困,打起精神来!现在,我们继续往下淦元素节点和文本节点的处理。

1.处理元素节点compileElement(child)

思路:

拿到标签里的每个vue指令,如v-text v-html v-model v-on:click,显然它们都是以v-开头的,当然还有@开头的指令也不要忘记把节点、节点值、vm实例、(on的事件名)传入compileUtil对象,后面用它处理每个指令,属性对应指令方法;别忘了,最后的视图标签上是没有vue指令的,所以我们要把它们从节点属性中删去。

compileElement(node) {
    const attributes = node.attributes;
    [...attributes].forEach((attr) => {
      const { name, value } = attr;
      if (this.isDirective(name)) {
        // 是一个指令 v-text v-html v-model v-on:click
        const [, directive] = name.split("-"); // text html model on:click
        const [dirName, eventName] = directive.split(":"); // text html model on
        // 更新数据 数据驱动视图
        compileUtil[dirName](node, value, this.vm, eventName);

        // 删除有指令标签上的属性
        node.removeAttribute("v-" + directive);
      } else if (this.isEventName(name)) {
        // @click='handleClick'
        let [, eventName] = name.split('@');
        compileUtil["on"](node, value, this.vm, eventName);
      }
    });
  }

判断是否是指令,以v-开头

isDirective(attrName) {
    return attrName.startsWith("v-");
  }

2.处理文本节点compileText(child)

主要使用正则匹配双大括号即可:

compileText(node) {
    // {{}} v-text
    const content = node.textContent;

    if (/\{\{(.+?)\}\}/.test(content)) {
      compileUtil["text"](node, content, this.vm);
    }
  }

3.实现compileUtil指令处理

思路:

每个指令对应各自方法,除了on需要额外传入事件名称,其他的指令处理函数只需要传节点、值(或表达式expr)、vm

实例:

const compileUtil = {
  text(node, expr, vm) {

  },
  html(node, expr, vm) {

  },
  model(node, expr, vm) {

  },
  on(node, expr, vm, eventName) {

  }
};

没有一下子放出代码来的话,骨架原来这么简单啊,继续逐个击破它们!
v-html指令处理,思路:拿到值,把值传给updater更新器,更新,完事儿。

html(node, expr, vm) {
    const value = this.getVal(expr, vm);
    this.updater.htmlUpdater(node, value);
  },

v-model指令处理,同上。先实现数据=>视图这条线,双向绑定最后实现。

model(node, expr, vm) {
    const value = this.getVal(expr, vm);
    this.updater.modelUpdater(node, value);
  },

比较复杂的,v-on,思路:获取事件名,从methods中取到对应的函数,添加到事件中,注意this要绑定给vm实例,false默认事件冒泡。

on(node, expr, vm, eventName) {
    // 获取事件名, 从method里面取函数
    let fn = vm.$options.methods && vm.$options.methods[expr];
    node.addEventListener(eventName, fn.bind(vm), false)
  },

v-text指令处理:

text(node, expr, vm) {
    // expr:msg: "学习MVVM框架原理"
    // 对传入不同的字符串不同操作 <div v-text="person.name"></div>
    // {{}}
    let value;
    if (expr.indexOf('{{') !== -1) {
        // {{person.name}} -- {{person.age}}
        value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(args[1], vm)
        })
    } else {
        value = this.getVal(expr, vm);
    }
    this.updater.textUpdater(node, value);
  },

用到args这个数组,console.log一下args,发现args[1]就有我们要找的具体属性:

例如,取到person.name后,传入到this.getVal('person.name',vm),最后能取到vm.$data.person.name

怎么拿到它们对应的值呢?

显然,不论是htmlStr、msg、person,它们都在实例vm的data内,在自定义方法getVal中,可以使用split分割小圆点“.”得到数组,再用高逼格的reduce方法去遍历找到data每个属性(对象)下的每个属性的值,像这样:

getVal(expr, vm) {
    return expr.split(".").reduce((data, currentVal) => {
      return data[currentVal];
    }, vm.$data);
  },

(不记得怎么用reduce?请在右上角新建标签页,去CSDN上补一补。)进阶拿到双大括号内对应的属性的值:

getContentVal(expr, vm) {
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      console.log(args);
      return this.getVal(args[1], vm);
    });
  },

更新器Updater更新数据

在指令方法的后面接着创建一个updater属性,实则是一个类,我们把它亲切地称作更新器,长得还很一目了然,您马上就能记住它的样子:

// 更新的函数
  updater: {
    textUpdater(node, value) {
      node.textContent = value;
    },
    htmlUpdater(node, value) {
      node.innerHTML = value;
    },
    modelUpdater(node, value){
      node.value = value;
    }
  },

在每个指令方法取到值后,更新到node节点上。

至此,我们已经完成了原理图上的MVVM到Compile到Updater这一条线:

实现数据观察者Observer

class MVue {
  constructor(options) {
    this.$el = options.el;
    this.$data = options.data;
    this.$options = options;

    if (this.$el) {
      // 1、实现一个数据观察者
      new Observer(this.$data);
      // 2、实现一个指令观察者
      new Compile(this.$el, this);
    }
  }

Observer类构造函数应该传什么给它?对,Observer要监听所有数据,所以我们将vm实例的data作为参数传入。

  • 递归,将data中所有的属性、对象、子对象……都遍历出来
  • 对每个key,使用Object.defineProperty劫持数据(Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性)
  • Object.defineProperty下有get方法和set方法,也就是官方原理图上的getter和stter啦
  • 在劫持数据之前,创建依赖器Dep实例dep
  • 对于gettter,订阅数据变化时,往dep中添加观察者;
  • 对于setter,当数据变化时,将newVal赋值为新值,并用notify通知dep变化。(此处正好对应官方原理图)

4、5、6这最后三点可以说是MVVM实现中最关键、最巧妙的3步,正是这画龙点睛的三笔,把整个系统桥梁成功架起来,注意它们各自放置在代码中位置。

class Observer {
  constructor(data) {
    this.observer(data);
  }
  observer(data) {
    /**
        {
            person:{
                name:'张三',
                fav: {
                    a: '爱好1',
                    b: '爱好2'
                }
            }
        }
         */
    if (data && typeof data === "object") {
      Object.keys(data).forEach((key) => {
        this.defineReactive(data, key, data[key]);
      });
    }
  }
  defineReactive(obj, key, value) {
    // 递归遍历
    this.observer(value);
    const dep = new Dep();
    // 劫持数据
    Object.defineProperty(obj, key, {
      // 是否可遍历
      enumerable: true,
      // 是否可以更改编写
      configurable: false,
      // 编译之前,初始化的时候
      get() {
        // 订阅数据变化时,往Dep中添加观察者
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      // 外界修改数据的时候
      set: (newVal) => {
        // 新值也要劫持
        this.observer(newVal); // 这里的this要指向当前的实例,所以改用箭头函数向上查找
        // 判断新值是否有变化
        if (newVal !== value) {
          value = newVal;
        }
        // 告诉Dep通知变化
        dep.notify();
      },
    });
  }
}

数据依赖器Dep

主要作用:

  • 收集要更新的观察者
  • 通知每个观察者去更新
// 数据依赖器
class Dep {
    constructor() {
        this.subs = [];
    }
    // 收集观察者
    addSub(watcher) {
        this.subs.push(watcher);
    }
    // 通知观察者去更新
    notify() {
        console.log("通知了观察者", this.subs);
        this.subs.forEach(w =>w.update())
    }
}

观察者Watcher

注意Dep.target = this;这一步,是为了把观察者挂载到Dep实例上,关联起来。所以当观察者Watcher获取旧值后,应该解除关联,否则会重复地添加观察者,以下是未取消关联的错误示范:

最后,使用callback回调函数传递要处理的新值给Updater即可。

class Watcher {
  constructor(vm, expr, callback) {
    // 把新值通过cb传出去
    this.vm = vm;
    this.expr = expr;
    this.callback = callback;
    // 先把旧值保存起来
    this.oldVal = this.getOldVal();
  }
  getOldVal() {
    // 把观察者挂载到Dep实例上,关联起来
    Dep.target = this;
    const oldVal = compileUtil.getVal(this.expr, this.vm);
    // 获取旧值后,取消关联,就不会重复添加
    Dep.target = null;
    return oldVal;
  }
  update() {
    // 更新,要取旧值和新值
    const newVal = compileUtil.getVal(this.expr, this.vm);
    if (newVal !== this.oldVal) {
        this.callback(newVal);
    }
  }
}

如何Updater如何接收从Watcher传来的新值做回调处理呢?

只需要在刚刚写好的compileUtil对象的每个指令处理方法内都new(添加)一个Watcher实例即可。注意text指令方法下new Watcher实例的value参数,可以用args[1]传入,重新处理newVal。

const compileUtil = {
  getVal(expr, vm) {
    return expr.split(".").reduce((data, currentVal) => {
      return data[currentVal];
    }, vm.$data);
  },
  setVal(expr, vm, inputVal) {
    return expr.split(".").reduce((data, currentVal) => {
      data[currentVal] = inputVal; // 把当前新值复制给旧值
    }, vm.$data);
  },
  getContentVal(expr, vm) {
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      console.log(args);
      return this.getVal(args[1], vm);
    });
  },
  text(node, expr, vm) {
    // expr:msg: "学习MVVM框架原理"
    // 对传入不同的字符串不同操作 <div v-text="person.name"></div>
    // {{}}
    let value;
    if (expr.indexOf('{{') !== -1) {
        // {{person.name}} -- {{person.age}}
        value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            new Watcher(vm, args[1], () => {
              // 额外处理expr: {{person.name}} -- {{person.age}}
              // 还要重新处理newVal
              this.updater.textUpdater(node, this.getContentVal(expr, vm));
            });
            return this.getVal(args[1], vm)
        })
    } else {
        value = this.getVal(expr, vm);
    }
    this.updater.textUpdater(node, value);
  },
  html(node, expr, vm) {
    const value = this.getVal(expr, vm);
    // 绑定观察者,将来数据发生变化 出发这里的回调 进行更新
    new Watcher(vm, expr, newVal => {
        this.updater.htmlUpdater(node, newVal);
    })
    this.updater.htmlUpdater(node, value);
  },
  model(node, expr, vm) {
    const value = this.getVal(expr, vm);
    // 绑定更新函数 数据=>驱动视图
    new Watcher(vm, expr, (newVal) => {
      this.updater.modelUpdater(node, newVal);
    });
    // 视图 => 数据 => 视图
    node.addEventListener('input', e => {
        // 设置值
        this.setVal(expr, vm, e.target.value);
    })
    this.updater.modelUpdater(node, value);
  },
  on(node, expr, vm, eventName) {
    // 获取事件名, 从method里面取函数
    let fn = vm.$options.methods && vm.$options.methods[expr];
    node.addEventListener(eventName, fn.bind(vm), false)
  },
  bind(node, expr, vm, attrName) {
      // 类似on。。。
  },
  // 更新的函数
  updater: {
    textUpdater(node, value) {
      node.textContent = value;
    },
    htmlUpdater(node, value) {
      node.innerHTML = value;
    },
    modelUpdater(node, value){
      node.value = value;
    }
  },
};

实现视图驱动数据驱动视图

还是借着上面这个代码块,我们只需要在model指令方法下,为input标签绑定事件,并自定义setVal方法为node赋值即可。

到这里,我们已经基本完整实现了Vue的MVVM双向数据绑定

小改进:

在MVue实例中,我们一开始使用的是$data获取到数据,这里可以做一层代理proxy,便于我们省略$data

methods: {
            handleClick() {
                // console.log(this);
                this.person.name = "这是做了一层代理"
                // 把this.$data 代理成 this
                this.$data.person.name = "数据更改了"
            }
        }

还是使用Object.defineProperty数据劫持,遍历data下的每个key,让getter返回data[key],setter设置data[key]直接等于newVal即可。

class MVue {
  constructor(options) {
    this.$el = options.el;
    this.$data = options.data;
    this.$options = options;

    if (this.$el) {
      // 1、实现一个数据观察者
      new Observer(this.$data);
      // 2、实现一个指令观察者
      new Compile(this.$el, this);
      this.proxyData(this.$data);
    }
  }
  proxyData(data) {
      for(const key in data) {
          Object.defineProperty(this, key, {
              get() {
                  return data[key]
              },
              set(newVal) {
                data[key] = newVal;
              }
          })
      }
  }
}

总结

再次体会官方文档对响应式原理的描述:

当我们把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property
被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装
vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的
setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

以及开头时我自己总结的原理描述:

在我们创建一个vue实例时,其实vue做了这些事情:

创建了入口函数,分别new了一个数据观察者

  • Observer和一个指令解析器Compile;
  • Compile解析所有DOM节点上的vue指令,提交到更新器Updater(实际上是一个对象);
  • Updater把数据(如{{}},msg,@click)替换,完成页面初始化渲染;Observer使用Object.defineProperty劫持数据,其中的getter和setter通知变化给依赖器Dep;
  • Dep中加入观察者Watcher,当数据发生变化时,通知Watcher更新;
  • Watcher取到旧值和新值,在回调函数中通知Updater更新视图;
  • Compile中每个指令都new了一个Watcher,用于触发Watcher的回调函数进行更新。

在实现代码的过程中,我们能深刻地体会到Vue的数据驱动视图,视图驱动数据驱动视图 这一核心的巧妙,也知道了Object.defineProperty具体应用场景。

到此这篇关于一文彻底搞懂Vue的MVVM响应式原理的文章就介绍到这了,更多相关 Vue的MVVM响应式 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • web面试MVC与MVVM区别及Vue为什么不完全遵守MVVM解答

    目录 MVC和MVVM区别 前述 MVC 概述 优缺点 MVVM 概述 MVVM实现者 - Vue Vue为什么没有完全遵守MVVM? MVC和MVVM区别 前述 首先, 这两种都只是一种思想, 一种设计模式 很多编程语言根据这种思想设计出了很多框架, 例如Java中的SpringMVC, PHP中的ThinkPHP , JavaScript中的Vue.React.Angular等 MVC 概述 1.数据要渲染到视图上   定义一个模板引擎   得到数据   再将数据渲染到模板引擎中 2.视图操

  • vue中的mvvm模式讲解

    学习了MVVM模式 起先还觉得有点难,后面与双向数据绑定链接起来还是很容易理解的. 那么什么是MVVM呢? 不明思议咋们肯定和我想的一样   每个单词的首写字母呗! 对没错就是它 Model-View-ViewModel.  可以实现我们的双向数据绑定 下面我来用我的理解解析下它们之间的关系: 哈哈 有点小尴尬哈 先来说下View与Model之间有联系吗? 在MVVM架构下,View 和 Model 之间其实并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel

  • 详解Vue中的MVVM原理和实现方法

    下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到: 1.Vue数据双向绑定核心代码模块以及实现原理 2.订阅者-发布者模式是如何做到让数据驱动视图.视图驱动数据再驱动视图 3.如何对元素节点上的指令进行解析并且关联订阅者实现视图更新 一.思路整理 实现的流程图: 我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点: 1.实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者. 2.实现一个解析器Compi

  • 详细聊聊Vue.js中的MVVM

    目录 MVVM的理解 MVVM的原理 脏检查机制: 数据劫持 相同点 实现MVVM 总结 MVVM的理解 MVVM拆开来即为Model-View-ViewModel,有View,ViewModel,Model三部分组成.View层代表的是视图.模版,负责将数据模型转化为UI展现出来.Model层代表的是模型.数据,可以在Model层中定义数据修改和操作的业务逻辑.ViewModel层连接Model和View. 在MVVM的架构下,View层和Model层并没有直接联系,而是通过ViewModel

  • Vue双向数据绑定(MVVM)的原理

    MVVM MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定. Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层.它的核心是 MVVM 中的 VM,也就是 ViewModel. ViewModel负责连接 View 和 Model,保证视图和数据的一致性

  • 浅析vue中的MVVM实现原理

    现成MVVM 菜单教程 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title> <script src="https://unpkg.com/vue/dist/vue.js"></script> </head> <bod

  • 一文彻底搞懂Vue的MVVM响应式原理

    目录 前言 Vue的MVVM原理 创建一个html示例 在MVue.js中创建MVue入口 创建Compile 1.处理元素节点compileElement(child) 2.处理文本节点compileText(child) 3.实现compileUtil指令处理 更新器Updater更新数据 实现数据观察者Observer 数据依赖器Dep 观察者Watcher 实现视图驱动数据驱动视图 总结 前言 这些天都在面试,每当我被面试官们问到Vue响应式原理时,回答得都很肤浅.如果您在回答时也只是停

  • vue.js数据响应式原理解析

    目录 Object.defineProperty() 定义 defineReactive 函数 递归侦测对象的全部属性 流程分析 observe 函数 Observer 类 完善 defineReactive 函数 One More Thing Object.defineProperty() 得力于 Object.defineProperty() 的特性,vue 的数据变化有别于 react 和小程序,是非侵入式的.详细介绍可以看 MDN 文档,这里特别说明几点: get / set 属性是函数

  • 详解实现vue的数据响应式原理

    这篇文章主要是给不了解或者没接触过 vue 响应式源码的小伙伴们看的,其主要目的在于能对 vue 的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」 响应式的理解 响应式顾名思义就是数据变化,会引起视图的更新.这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析. 在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据.vue 内部通过

  • 一篇文章带你彻底搞懂VUE响应式原理

    目录 响应式原理图 编译 创建compile类 操作fragment 获取元素节点上的信息 获取文本节点信息 操作fragment 响应式 数据劫持 收集依赖 响应式代码完善 Dep类 全局watcher用完清空 依赖的update方法 需要注意的一个地方 双剑合璧 总结 首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重. 响应式原理图 一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程.

  • 深入理解Vue的数据响应式

    1. ES语法的getter和setter 在开始了解 Vue 的数据响应式原理前应该先搞清楚 ES语法 中的 getter 和 setter 方法的具体用法. getter和setter 方法是以 get 和 set 关键字来为对象添加虚拟属性的一种方式.这种属性其实并不真实存在,而是以取值函数 getter 和存值函数 setter 来模拟的一种属性.目的是对某个属性设置存值函数和取值函数,拦截该属性的存取行为,以便于对该属性的存取做一些限定处理.如下所示(以下代码来源于 mdn) gett

  • Vue响应式原理深入解析及注意事项

    前言 Vue最明显的特性之一便是它的响应式系统,其数据模型即是普通的 JavaScript 对象.而当你读取或写入它们时,视图便会进行响应操作.文章简要阐述下其实现原理,如有错误,还请不吝指正.下面话不多说了,来随着小编来一起学习学习吧. 响应式data <div id = "exp">{{ message }}</div> const vm = new Vue({ el: '#exp', data: { message: 'This is A' } }) vm

  • 一文搞懂Vue中computed和watch的区别

    目录 一.computed介绍 1.1.get 和 set 用法 1.2.计算属性缓存 二.watch介绍 三.两者区别 3.1.对于 computed 3.2.对于 watch 四.应用场景 一.computed介绍 computed 用来监控自己定义的变量,该变量在 data 内没有声明,直接在 computed 里面定义,页面上可直接使用. //基础使用 {{msg}} <input v-model="name" /> //计算属性 computed:{ msg:fu

  • 一文搞懂Vue八大生命周期钩子函数

    目录 一.速识概念: 二.八大生命周期钩子函数: 三.结合代码了解: 1. beforeCreate: 2.created: 3.beforeMount: 4.mounted: 5.beforeUpdate: 6.updated: 7.beforeDestroy: 8.destroyed: 一.速识概念:   我们把一个对象从生成(new)到被销毁(destory)的过程,称为生命周期.而生命周期函数,就是在某个时刻会自动执行的函数.  按照官方的原话,就是每个 Vue 实例在被创建时都要经过一

  • 一文搞懂hashCode()和equals()方法的原理

    Java中的超类java.lang.Object 有两个非常重要的方法: public boolean equals(Object obj) public int hashCode() 这两个方法最开发者来说是十分重要的,必须清楚的理解,但实际上,甚至很多经验丰富的Java开发者有时候也没有真正搞清楚这两个方法的使用和原理.当我们自定义了对象,并且想要将自定义的对象加到Map中时,我们就必须对自定义的对象重写这两个方法,才能正确使用Map.我们接下来将用这篇文章指出在使用hashcode和equ

  • 一篇文章带你搞懂Vue虚拟Dom与diff算法

    前言 使用过Vue和React的小伙伴肯定对虚拟Dom和diff算法很熟悉,它扮演着很重要的角色.由于小编接触Vue比较多,React只是浅学,所以本篇主要针对Vue来展开介绍,带你一步一步搞懂它. 虚拟DOM 什么是虚拟DOM? 虚拟DOM(Virtual   Dom),也就是我们常说的虚拟节点,是用JS对象来模拟真实DOM中的节点,该对象包含了真实DOM的结构及其属性,用于对比虚拟DOM和真实DOM的差异,从而进行局部渲染来达到优化性能的目的. 真实的元素节点: <div id="wr

随机推荐