Vue响应式原理与虚拟DOM实现步骤详细讲解

Vue响应式原理与虚拟DOM实现步骤详细讲解

1. Vue响应式原理

Vue的响应式原理核心是利用Object.defineProperty方法对数据进行拦截,当数据发生变化时,通知对应的界面进行更新。

1.1 监听对象

在Vue中对数据的监听由Observer对象负责,在Observer对象中使用Object.defineProperty方法对数据进行监听。在Observer对象的实现中,需要考虑如下几个问题:

  1. 如何递归观察对象或数组中的每一个属性。
  2. 如何判断一个变量是对象还是数组。
  3. 如何在数组中添加或删除元素时实时更新View。

示例1

我们在Vue的实例中定义一个data对象,在其中定义了两个属性,num和obj。

var vm = new Vue({
  data: {
    num: 1,
    obj: {
      name: 'John',
      age: 23
    }
  }
})

在Observer对象中,对数据进行递归监听,使得Vue能够监听到data对象的变化,并自动更新View。

function Observer(data) {
  this.data = data;
  this.observe(data);
}

Observer.prototype = {
  observe: function(data) {
    var self = this;
    if (!data || typeof data !== 'object') {
      return;
    }
    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() {
        if (Dep.target) {
          dep.depend();
        }
        return val;
      },
      set: function(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        childObj = observe(newVal);
        dep.notify();
      }
    });
  }
};

function observe(value, vm) {
  if (!value || typeof value !== 'object') {
    return;
  }
  return new Observer(value);
}

function Dep() {
  this.subs = []; /* subscribers */
}

Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },

  removeSub: function(sub) {
    var index = this.subs.indexOf(sub);
    if (index !== -1) {
      this.subs.splice(index, 1);
    }
  },

  depend: function() {
    Dep.target.addDep(this);
  },

  notify: function() {
    this.subs.forEach(function(sub) {
      sub.update();
    });
  }
};

Dep.target = null;

1.2 发布订阅模式

在Vue中,数据变化时会触发对应组件的重新渲染。而这个过程是通过订阅发布模式实现的,其中过程如下:

  1. 初始化时,Vue会为每个观察的对象创建一个Dep对象
  2. 当组件初始化完成后,Vue会对需要监听的属性(computed/watch data)强制求值,此时计算property.get
  3. Dep.target属性会被设置为当前运行中的Watcher对象
  4. 当获取到对象的值后,把Watcher对象添加到对应Dep对象的subs数组中
  5. 后续调用set方法时,observer会向dep.subs数组中的Watcher对象通知数据的更新。
  6. Watcher对象通知组件重新渲染

示例2

我们在Vue的实例中定义一个computed属性,以及一个Watch属性,其中computed属性的值将通过data.num和data.obj.age的计算得出,Watch属性会监听data.num的变化,并在num发生变化后调用回调函数。

var vm = new Vue({
  data: {
    num: 1,
    obj: {
      name: 'John',
      age: 23
    }
  },
  computed: {
    sum: function() {
      return this.num + this.obj.age;
    }
  },
  watch: {
    num: function(newVal, oldVal) {
      console.log('num changed:', newVal);
    }
  }
})

在Watcher对象中,对数据进行监听。

function Watcher(vm, expOrFn, cb) {
  this.vm = vm;
  expOrFn = expOrFn.trim();
  this.expOrFn = expOrFn;
  this.cb = cb;

  Dep.target = this;
  this.value = this.get();
  Dep.target = null;
}

Watcher.prototype = {
  update: function() {
    var value = this.get();
    var oldVal = this.value;
    if (value !== oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  },
  get: function() {
    var value;
    try {
      value = this.vm.$data[this.expOrFn];
    } catch (e) {
      value = undefined;
    }
    return value;
  },
  addDep: function(dep) {
    dep.addSub(this);
  }
};

2. Vue虚拟DOM实现

Vue的虚拟DOM实现非常简单,其核心思想是:在操作真实DOM时,避免直接修改DOM而导致大量重排,而是采用虚拟DOM来计算出最少的需要修改的节点,并进行批量修改。

2.1 节点的类型

在Vue的虚拟DOM实现中,将真实DOM分为以下三种类型的节点:

  • 元素节点(Element): 代表一个HTML元素,比如div, p, ul等,包含标签名,属性,子节点等信息。
  • 文本节点(Text): 代表一段文本内容。
  • 占位符节点(Comment): 代表HTML注释,用于模版,介绍等。

示例3

我们假设需要把以下HTML元素渲染到页面中,其中标题部分为动态内容,其余部分为静态内容。

<div class="container">
  <h1>{{title}}</h1>
  <p>这是一个演示文本。</p>
  <ul>
    <li>测试1</li>
    <li>测试2</li>
    <li>测试3</li>
  </ul>
</div>

render函数中,我们使用虚拟DOM的方式对HTML进行解析。

function createElement(tagName, attrs, children) {
  return new VNode(tagName, attrs, children)
}

function createTextNode(text) {
  return new VText(text)
}

function createComment(text) {
  return new VComment(text)
}

function VNode(tagName, attrs, children) {
  this.type = 'Element';
  this.tagName = tagName;
  this.attrs = attrs || {};
  this.children = children || [];
}

function VText(text) {
  this.type = 'Text';
  this.text = text;
}

function VComment(text) {
  this.type = 'Comment';
  this.text = text;
}

function render(data) {
  var container = document.createElement('div');
  var titleNode = createTextNode(data.title);
  var p1 = createTextNode('这是一个演示文本。');
  var ul1 = createElement('ul', null, [
    createElement('li', null, [createTextNode('测试1')]),
    createElement('li', null, [createTextNode('测试2')]),
    createElement('li', null, [createTextNode('测试3')])
  ]);

  container.appendChild(createElement('h1', null, [titleNode]));
  container.appendChild(createElement('p', null, [p1]));
  container.appendChild(ul1);

  return container;
}

2.2 DOM的更新

在Vue的虚拟DOM实现中,更新DOM的过程发生在如下两个步骤中:

  1. 使用虚拟DOM计算出需要修改的节点
  2. 对需要修改的节点进行优化后,一次性修改DOM。

示例4

我们假设我们需要根据用户的操作在已渲染的HTML中动态更新title属性。

function update(data) {
  // 对比旧虚拟节点和新虚拟节点,得出需要更新的节点
  var patch = diff(previousVNode, newVNode);

  // 对需要更新的节点进行优化后,一次性修改DOM
  patch(rootDomNode);
}

结论

通过对响应式原理和虚拟DOM的分析,我们了解了Vue是如何实现数据的自动更新和性能的优化的。Vue的响应式原理核心是使用Object.defineProperty方法对数据进行监听,而虚拟DOM的核心思想是在操作真实DOM时,在避免直接修改DOM的前提下,采用虚拟DOM来计算出最少的需要修改的节点,并进行批量修改DOM的方式来实现性能优化。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Vue响应式原理与虚拟DOM实现步骤详细讲解 - Python技术站

(0)
上一篇 2023年5月28日
下一篇 2023年5月28日

相关文章

  • Vue中使用 class 类样式的方法详情

    下面我将详细讲解在Vue中使用class样式的方法: 一、Vue中绑定class样式的常见方法 1. 绑定单个class样式 使用v-bind或:直接绑定class属性,即class=”[className]”,[className]为你想要应用的样式类名。 比如我们有一个<div>元素,需要加上red样式: <template> &…

    Vue 2023年5月28日
    00
  • vue-cli 打包后提交到线上出现 “Uncaught SyntaxError:Unexpected token” 报错

    当使用vue-cli进行代码打包后,提交到线上服务器运行时,有时会遇到类似于“Uncaught SyntaxError: Unexpected token”这样的报错,这种问题可能是由于代码中存在ES6语法而服务器不支持引起的。针对这种问题,可以采取以下几个步骤: 确认线上服务器是否支持ES6语法,如果不支持,则需要对代码进行转译处理。 使用babel对代码…

    Vue 2023年5月28日
    00
  • vue项目中form data形式传参方式

    在 Vue 项目中,直接利用 form 表单的方式进行数据传递是非常常见的。在 Vue 中,我们可以利用 axios 与后端进行通信,并将 form data 格式的数据进行传递。 以下是利用 axios 技术实现的参数传递方式示例: <template> <form @submit.prevent="submitForm&quo…

    Vue 2023年5月28日
    00
  • 简单了解vue 插值表达式Mustache

    下面是“简单了解vue 插值表达式Mustache”的完整攻略。 插值表达式Mustache 在Vue.js中,使用Mustache语法(双大括号,即{{}})可以用于实现对数据的简单渲染,这种方式被称为插值表达式Mustache。在Vue实例中使用Mustache语法可以对绑定到数据的值进行渲染,即实现数据与视图的绑定。 基本使用 使用插值表达式Musta…

    Vue 2023年5月27日
    00
  • VUE3中h()函数和createVNode()函数的使用解读

    下面我将为你详细讲解“Vue3中h()函数和createVNode()函数的使用解读”的完整攻略。 1. h()函数和createVNode()函数的基本概念 在Vue 3中,h()函数和createVNode()函数被用来创建虚拟DOM。虚拟DOM是Vue进行数据渲染的重要原理之一,它是由JavaScript对象模拟的真实DOM,通过比较新旧虚拟DOM的差…

    Vue 2023年5月27日
    00
  • vue3如何定义变量及ref、reactive、toRefs特性说明

    下面是关于Vue3如何定义变量及ref、reactive、toRefs特性说明的攻略。 定义变量 在Vue3中,定义变量有两种方式: 1. 使用const/let/var关键字 使用const/let/var关键字定义变量,这是ES6的语法。例如: const message = ‘Hello World’; // 定义常量 let count = 0; /…

    Vue 2023年5月27日
    00
  • Vue收集表单数据和过滤器总结

    Vue收集表单数据和过滤器总结 本文主要介绍Vue中如何收集表单数据和使用过滤器进行数据处理。 收集表单数据 v-model指令 v-model指令可以实现双向数据绑定,将表单元素的值与Vue实例的数据属性进行绑定。当表单元素的值发生变化时,对应的数据属性也会更新。 示例一: <template> <div> <input ty…

    Vue 2023年5月27日
    00
  • Vue 2阅读理解之initRender与callHook组件详解

    Vue 2阅读理解之initRender与callHook组件详解 1. 什么是initRender initRender是Vue在组件挂载前执行的一个钩子函数,用于初始化组件的渲染。在这个过程中,Vue会根据组件的各种属性和配置来建立组件的虚拟DOM,并通过组件的template编译生成可执行的渲染函数(render function)。 具体来说,ini…

    Vue 2023年5月28日
    00
合作推广
合作推广
分享本页
返回顶部