目录
Vue 数据双向绑定原理
实现过程
显示一个 Observer
实现 Watcher
实现 Compile
添加解析事件
完整版 myVue
首页 web前端 Vue.js vuejs实现双向绑定的原理是什么

vuejs实现双向绑定的原理是什么

Sep 28, 2021 pm 02:06 PM
vuejs 双向绑定

vuejs实现双向绑定的原理:利用数据劫持和发布订阅模式,通过“Object.defineProperty()”来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调,进而对视图进行更新。

vuejs实现双向绑定的原理是什么

本教程操作环境:windows7系统、vue2.9.6版,DELL G3电脑。

Vue 数据双向绑定原理

Vue实现数据双向绑定主要利用的就是: 数据劫持和发布订阅模式,利用的 Object.defineProperty() 方法进行的数据劫持,然后通知发布者(主题对象)去通知所有观察者,观察者收到通知后,就会对视图进行更新。

https://jsrun.net/RMIKp/embedded/all/light

MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。

视图变化更新数据,如果是像 input 这种标签,可以使用 oninput 事件..

数据变化更新视图可以使用 Object.definProperty() 的 set 方法可以检测数据变化,当数据改变就会触发这个函数,然后更新视图。

实现过程

我们知道了如何实现双向绑定了,首先要对数据进行劫持监听,所以我们需要设置一个 Observer 函数,用来监听所有属性的变化。

如果属性发生了变化,那就要告诉订阅者 watcher 看是否需要更新数据,如果订阅者有多个,则需要一个 Dep 来收集这些订阅者,然后在监听器 observer 和 watcher 之间进行统一管理。

还需要一个指令解析器 compile,对需要监听的节点和属性进行扫描和解析。

因此,流程大概是这样的:

  • 实现一个监听器 Observer,用来劫持并监听所有属性,如果发生变动,则通知订阅者。

  • 实现一个订阅者 Watcher,当接到属性变化的通知时,执行对应的函数,然后更新视图,使用 Dep 来收集这些 Watcher。

  • 实现一个解析器 Compile,用于扫描和解析的节点的相关指令,并根据初始化模板以及初始化相应的订阅器。

1.png

显示一个 Observer

Observer 是一个数据监听器,核心方法是利用 Object.defineProperty() 通过递归的方式对所有属性都添加 setter、getter 方法进行监听。

var library = {
  book1: {
    name: "",
  },
  book2: "",
};
observe(library);
library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍”

// 为数据添加检测
function defineReactive(data, key, val) {
  observe(val); // 递归遍历所有子属性
  let dep = new Dep(); // 新建一个dep
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      if (Dep.target) {
        // 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数
        dep.addSub(Dep.target); // 添加一个订阅者
      }
      return val;
    },
    set: function(newVal) {
      if (val == newVal) return; // 如果值未发生改变就return
      val = newVal;
      console.log(
        "属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”"
      );
      dep.notify(); // 如果数据发生变化,就通知所有的订阅者。
    },
  });
}

// 监听对象的所有属性
function observe(data) {
  if (!data || typeof data !== "object") {
    return; // 如果不是对象就return
  }
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key]);
  });
}
// Dep 负责收集订阅者,当属性发生变化时,触发更新函数。
function Dep() {
  this.subs = {};
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach((sub) => sub.update());
  },
};
登录后复制

思路分析中,需要有一个可以容纳订阅者消息订阅器 Dep,用于收集订阅者,在属性发生变化时执行对应的更新函数。

从代码上看,将订阅器 Dep 添加在 getter 里,是为了让 Watcher 初始化时触发,,因此,需要判断是否需要订阅者。

在 setter 中,如果有数据发生变化,则通知所有的订阅者,然后订阅者就会更新对应的函数。

到此为止,一个比较完整的 Observer 就完成了,接下来开始设计 Watcher.

实现 Watcher

订阅者 Watcher 需要在初始化的时候将自己添加到订阅器 Dep 中,我们已经知道监听器 Observer 是在 get 时执行的 Watcher 操作,所以只需要在 Watcher 初始化的时候触发对应的 get 函数去添加对应的订阅者操作即可。

那给如何触发 get 呢?因为我们已经设置了 Object.defineProperty(),所以只需要获取对应的属性值就可以触发了。

我们只需要在订阅者 Watcher 初始化的时候,在 Dep.target 上缓存下订阅者,添加成功之后在将其去掉就可以了。

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; // 缓存自己,用于判断是否添加watcher。
    var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
    Dep.target = null; // 释放自己
    return value;
  },
};
登录后复制

到此为止, 简单的额 Watcher 设计完毕,然后将 Observer 和 Watcher 关联起来,就可以实现一个简单的的双向绑定了。

因为还没有设计解析器 Compile,所以可以先将模板数据写死。

将代码转化为 ES6 构造函数的写法,预览试试。

https://jsrun.net/8SIKp/embedded/all/light

这段代码因为没有实现编译器而是直接传入了所绑定的变量,我们只在一个节点上设置一个数据(name)进行绑定,然后在页面上进行 new MyVue,就可以实现双向绑定了。

并两秒后进行值得改变,可以看到,页面也发生了变化。

// MyVue
proxyKeys(key) {
    var self = this;
    Object.defineProperty(this, key, {
        enumerable: false,
        configurable: true,
        get: function proxyGetter() {
            return self.data[key];
        },
        set: function proxySetter(newVal) {
            self.data[key] = newVal;
        }
    });
}
登录后复制

上面这段代码的作用是将 this.data 的 key 代理到 this 上,使得我可以方便的使用 this.xx 就可以取到 this.data.xx。

实现 Compile

虽然上面实现了双向数据绑定,但是整个过程都没有解析 DOM 节店,而是固定替换的,所以接下来要实现一个解析器来做数据的解析和绑定工作。

解析器 compile 的实现步骤:

  • 解析模板指令,并替换模板数据,初始化视图。

  • 将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。

为了解析模板,首先需要解析 DOM 数据,然后对含有 DOM 元素上的对应指令进行处理,因此整个 DOM 操作较为频繁,可以新建一个 fragment 片段,将需要的解析的 DOM 存入 fragment 片段中在进行处理。

function nodeToFragment(el) {
  var fragment = document.createDocumentFragment();
  var child = el.firstChild;
  while (child) {
    // 将Dom元素移入fragment中
    fragment.appendChild(child);
    child = el.firstChild;
  }
  return fragment;
}
登录后复制

接下来需要遍历各个节点,对含有相关指令和模板语法的节点进行特殊处理,先进行最简单模板语法处理,使用正则解析“{{变量}}”这种形式的语法。

function compileElement (el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/; // 匹配{{xx}}
        var text = node.textContent;
        if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
            self.compileText(node, reg.exec(text)[1]);
        }
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);  // 继续递归遍历子节点
        }
    });
},
function compileText (node, exp) {
    var self = this;
    var initText = this.vm[exp];
    updateText(node, initText);  // 将初始化的数据初始化到视图中
    new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
        self.updateText(node, value);
    });
},
function updateText (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}
登录后复制

获取到最外层的节点后,调用 compileElement 函数,对所有的子节点进行判断,如果节点是文本节点切匹配{{}}这种形式的指令,则进行编译处理,初始化对应的参数。

然后需要对当前参数生成一个对应的更新函数订阅器,在数据发生变化时更新对应的 DOM。

这样就完成了解析、初始化、编译三个过程了。

接下来改造一个 myVue 就可以使用模板变量进行双向数据绑定了。

https://jsrun.net/K4IKp/embedded/all/light

添加解析事件

添加完 compile 之后,一个数据双向绑定就基本完成了,接下来就是在 Compile 中添加更多指令的解析编译,比如 v-model、v-on、v-bind 等。

添加一个 v-model 和 v-on 解析:

function compile(node) {
  var nodeAttrs = node.attributes;
  var self = this;
  Array.prototype.forEach.call(nodeAttrs, function(attr) {
    var attrName = attr.name;
    if (isDirective(attrName)) {
      var exp = attr.value;
      var dir = attrName.substring(2);
      if (isEventDirective(dir)) {
        // 事件指令
        self.compileEvent(node, self.vm, exp, dir);
      } else {
        // v-model 指令
        self.compileModel(node, self.vm, exp, dir);
      }
      node.removeAttribute(attrName); // 解析完毕,移除属性
    }
  });
}
// v-指令解析
function isDirective(attr) {
  return attr.indexOf("v-") == 0;
}
// on: 指令解析
function isEventDirective(dir) {
  return dir.indexOf("on:") === 0;
}
登录后复制

上面的 compile 函数是用于遍历当前 dom 的所有节点属性,然后判断属性是否是指令属性,如果是在做对应的处理(事件就去监听事件、数据就去监听数据..)

完整版 myVue

在 MyVue 中添加 mounted 方法,在所有操作都做完时执行。

class MyVue {
  constructor(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
    Object.keys(this.data).forEach(function(key) {
      self.proxyKeys(key);
    });
    observe(this.data);
    new Compile(options.el, this);
    options.mounted.call(this); // 所有事情处理好后执行mounted函数
  }
  proxyKeys(key) {
    // 将this.data属性代理到this上
    var self = this;
    Object.defineProperty(this, key, {
      enumerable: false,
      configurable: true,
      get: function getter() {
        return self.data[key];
      },
      set: function setter(newVal) {
        self.data[key] = newVal;
      },
    });
  }
}
登录后复制

然后就可以测试使用了。

https://jsrun.net/Y4IKp/embedded/all/light

总结一下流程,回头在哪看一遍这个图,是不是清楚很多了。

2.png

可以查看的代码地址:Vue2.x 的双向绑定原理及实现

相关推荐:《vue.js教程

以上是vuejs实现双向绑定的原理是什么的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

使用Vue.js和Kotlin语言开发安卓应用的一些技巧 使用Vue.js和Kotlin语言开发安卓应用的一些技巧 Jul 31, 2023 pm 02:17 PM

使用Vue.js和Kotlin语言开发安卓应用的一些技巧随着移动应用的普及和用户需求的不断增长,安卓应用的开发越来越受到开发者的关注。在开发安卓应用时,选择合适的技术栈至关重要。近年来,Vue.js和Kotlin语言逐渐成为安卓应用开发的热门选择。本文将介绍使用Vue.js和Kotlin语言开发安卓应用的一些技巧,并给出相应的代码示例。一、搭建开发环境在开始

使用Vue.js和Python开发数据可视化应用的一些技巧 使用Vue.js和Python开发数据可视化应用的一些技巧 Jul 31, 2023 pm 07:53 PM

使用Vue.js和Python开发数据可视化应用的一些技巧引言:随着大数据时代的到来,数据可视化成为了一种重要的解决方案。而在数据可视化应用的开发中,Vue.js和Python的组合能够提供灵活性和强大的功能。本文将分享一些使用Vue.js和Python开发数据可视化应用的技巧,并附上相应的代码示例。一、Vue.js简介Vue.js是一款轻量级的JavaSc

Vue.js与Lua语言的融合,构建游戏开发的前端引擎的最佳实践和经验分享 Vue.js与Lua语言的融合,构建游戏开发的前端引擎的最佳实践和经验分享 Aug 01, 2023 pm 08:14 PM

Vue.js与Lua语言的融合,构建游戏开发的前端引擎的最佳实践和经验分享引言:随着游戏开发的不断发展,游戏前端引擎的选择成为了一个重要的决策。在这些选择中,Vue.js框架和Lua语言都成为了众多开发者的关注点。Vue.js作为一款流行的前端框架具有丰富的生态系统和便捷的开发方式,而Lua语言则因其轻量级和高效性能在游戏开发中得到广泛应用。本文将探讨如何将

Vue.js与Objective-C语言的集成,开发可靠的Mac应用的技巧和建议 Vue.js与Objective-C语言的集成,开发可靠的Mac应用的技巧和建议 Jul 30, 2023 pm 03:01 PM

Vue.js与Objective-C语言的集成,开发可靠的Mac应用的技巧和建议近年来,随着Vue.js在前端开发中的普及和Objective-C在Mac应用开发中的稳定性,开发者们开始尝试将这两者结合起来,以开发出更加可靠和高效的Mac应用程序。本文将介绍一些技巧和建议,帮助开发者正确集成Vue.js和Objective-C,并开发出高质量的Mac应用。一

如何使用Vue实现仿QQ聊天气泡特效 如何使用Vue实现仿QQ聊天气泡特效 Sep 20, 2023 pm 02:27 PM

如何使用Vue实现仿QQ聊天气泡特效在现如今的社交时代,聊天功能已经成为了手机应用和网页应用的核心功能之一。而聊天界面中最常见的元素之一就是聊天气泡,它可以清晰地将发送者和接收者的信息区分开来,有效地提高了信息的可读性。本文将介绍如何使用Vue实现仿QQ聊天气泡特效,以及提供具体的代码示例。首先,我们需要创建一个Vue组件来表示聊天气泡。组件包含两个主要部分

如何使用PHP和Vue.js实现图表上的数据筛选和排序功能 如何使用PHP和Vue.js实现图表上的数据筛选和排序功能 Aug 27, 2023 am 11:51 AM

如何使用PHP和Vue.js实现图表上的数据筛选和排序功能在网页开发中,图表是一种非常常见的数据展示方式。使用PHP和Vue.js可以轻松实现图表上的数据筛选和排序功能,使用户能够自定义查看图表上的数据,提高数据的可视化效果和用户体验。首先,我们需要准备一组数据供图表使用。假设我们有一个数据表格,包含姓名、年龄和成绩三列,数据如下:姓名年龄成绩张三1890李

使用Vue.js和Perl语言开发高效的网络爬虫和数据抓取工具 使用Vue.js和Perl语言开发高效的网络爬虫和数据抓取工具 Jul 31, 2023 pm 06:43 PM

使用Vue.js和Perl语言开发高效的网络爬虫和数据抓取工具近年来,随着互联网的迅猛发展和数据的日益重要,网络爬虫和数据抓取工具的需求也越来越大。在这个背景下,结合Vue.js和Perl语言开发高效的网络爬虫和数据抓取工具是一种不错的选择。本文将介绍如何使用Vue.js和Perl语言开发这样一个工具,并附上相应的代码示例。一、Vue.js和Perl语言的介

Vue.js与Dart语言的集成,构建酷炫的移动应用UI界面的实践和开发技巧 Vue.js与Dart语言的集成,构建酷炫的移动应用UI界面的实践和开发技巧 Aug 02, 2023 pm 03:33 PM

Vue.js与Dart语言的集成,构建酷炫的移动应用UI界面的实践和开发技巧引言:在移动应用开发中,用户界面(UI)的设计和实现是非常重要的一部分。为了能够实现酷炫的移动应用界面,我们可以将Vue.js与Dart语言进行集成,借助Vue.js的强大数据绑定和组件化特性,以及Dart语言的丰富的移动应用开发库,来构建出令人惊艳的移动应用UI界面。本文将介绍如何

See all articles