Vue.js 核心:

1、响应式的数据绑定系统

2、组件系统。

访问器属性

访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 defineProperty() 方法单独定义。

  1. var obj = { };
  2. // 为obj定义一个名为 hello 的访问器属性
  3. Object.defineProperty(obj, "hello", {
  4. get: function () {return sth},
  5. set: function (val) {/* do sth */}
  6. })
  7. obj.hello // 可以像普通属性一样读取访问器属性

访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。

  1. obj.hello // 读取属性,就是调用get函数并返回get函数的返回值
  2. obj.hello = "abc" // 为属性赋值,就是调用set函数,赋值其实是传参

get 和 set 方法内部的 this 都指向 obj,这意味着 get 和 set 函数可以操作对象内部的值。另外,访问器属性的会"覆盖"同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略。

预期达到的效果:

1、随文本框输入文字的变化,span 中会同步显示相同的文字内容;

2、在js或控制台显式的修改 obj.hello 的值,视图会相应更新。这样就实现了 model => view 以及 view => model 的双向绑定。

模型图:

子任务:

1、输入框以及文本节点与 data 中的数据绑定

2、输入框内容变化时,data 中的数据同步变化。即 view => model 的变化。

3、data 中的数据变化时,文本节点的内容同步变化。即 model => view 的变化。

1、输入框以及文本节点与 data 中的数据绑定

这里需要对 DOM 进行编译,这里引入一个知识点:DocumentFragment。

DocumentFragment

DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到 DOM 中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用 DocumentFragment 处理节点,速度和性能远远优于直接操作 DOM。Vue 进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持,通过 append 方法,DOM 中的节点会被自动删除)到 DocumentFragment 中,经过一番处理后,再将 DocumentFragment 整体返回插入挂载目标。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title></title>
  5. </head>
  6. <body>
  7. <div id="app">
  8. <input type="text" id="a">
  9. <span id="b"></span>
  10. </div>
  11. <script type="text/javascript">
  12. var dom = nodeToFragment(document.getElementById('app'));
  13. console.log(dom);
  14. function nodeToFragment (node) {
  15. var flag = document.createDocumentFragment();
  16. var child;
  17. while (child = node.firstChild) {
  18. flag.append(child); // 劫持node的所有子节点
  19. }
  20. return flag;
  21. }
  22. document.getElementById('app').appendChild(dom); // 返回到app中
  23. </script>
  24. </body>
  25. </html>

数据初始化绑定

  1. function compile (node, vm) {
  2. var reg = /\{\{(.*)\}\}/;
  3. // 节点类型为元素
  4. if (node.nodeType === 1) {
  5. var attr = node.attributes;
  6. // 解析属性
  7. for (var i = 0; i < attr.length; i++) {
  8. if (attr[i].nodeName == 'v-model') {
  9. var name = attr[i].nodeValue; // 获取v-model绑定的属性名
  10. node.value = vm.data[name]; // 将data的值赋值给该node
  11. node.removeAttribute('v-model');
  12. }
  13. }
  14. }
  15. // 节点类型为text
  16. if (node.nodeType === 3) {
  17. if (reg.test(node.nodeValue)) {
  18. var name = RegExp.$1; // 获取匹配到的字符串
  19. name = name.trim();
  20. node.nodeValue = vm.data[name];
  21. }
  22. }
  23. }
  24. function nodeToFragment (node, vm) {
  25. var flag = document.createDocumentFragment();
  26. var child;
  27. while (child = node.firstChild) {
  28. compile(child, vm);
  29. flag.append(child);
  30. }
  31. return flag;
  32. }
  33. function Vue (options) {
  34. this.data = options.data;
  35. var id = options.el;
  36. var dom = nodeToFragment(document.getElementById(id), this);
  37. // 编译完成后,将dom返回到app中
  38. document.getElementById(id).appendChild(dom);
  39. }
  40. var vm = new Vue({
  41. el: 'app',
  42. data: {
  43. text: 'hello world'
  44. }
  45. })

效果

响应式的数据绑定

2、输入框内容变化时,data 中的数据同步变化。即 view => model 的变化。

思路:当我们在输入框输入数据的时候,首先触发 input 事件(或者 keyup、change 事件),在相应的事件处理程序中,我们获取输入框的 value 并赋值给 vm 实例的 text 属性。我们利用 defineProperty 将 data 中的 text 设置为 vm 的访问器属性,因此给 vm.text 赋值,就会触发 set 方法。这里set主要做了跟新属性值得操作。

  1. function defineReactive (obj, key, val) {
  2. Object.defineProperty(obj, key, {
  3. get: function () {
  4. return val
  5. },
  6. set: function (newVal) {
  7. if (newVal === val) return
  8. val = newVal;
  9. console.log(val); // console
  10. }
  11. });
  12. }
  13. function observe (obj, vm) {
  14. Object.keys(obj).forEach(function (key) {
  15. defineReactive(vm, key, obj[key]);
  16. });
  17. }
  18. function Vue (options) {
  19. this.data = options.data;
  20. var data = this.data;
  21. observe(data, this);
  22. var id = options.el;
  23. var dom = nodeToFragment(document.getElementById(id), this);
  24. // 编译完成后,将dom返回到app中
  25. document.getElementById(id).appendChild(dom);
  26. }
  27. function compile (node, vm) {
  28. var reg = /\{\{(.*)\}\}/;
  29. // 节点类型为元素
  30. if (node.nodeType === 1) {
  31. var attr = node.attributes;
  32. // 解析属性
  33. for (var i = 0; i < attr.length; i++) {
  34. if (attr[i].nodeName == 'v-model') {
  35. var name = attr[i].nodeValue; // 获取v-model绑定的属性名
  36. node.addEventListener('input', function (e) {
  37. // 给相应的data属性赋值,进而触发该属性的set方法
  38. vm[name] = e.target.value;
  39. });
  40. node.value = vm[name]; // 将data的值赋给该node
  41. node.removeAttribute('v-model');
  42. }
  43. };
  44. new Watcher(vm, node, name, 'input');
  45. }
  46. // 节点类型为text
  47. if (node.nodeType === 3) {
  48. if (reg.test(node.nodeValue)) {
  49. var name = RegExp.$1; // 获取匹配到的字符串
  50. name = name.trim();
  51. node.nodeValue = vm[name];
  52. }
  53. }
  54. }

第二部完成效果

订阅/发布模式(subscribe&publish)

data 中的数据变化时,文本节点的内容同步变化。即 model => view 的变化。

订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作

  1. var pub = {
  2. publish: function() {
  3. def.notify():
  4. }
  5. }
  6. // 三个订阅者subscribers
  7. var sub1 = {updata: function () {console.log(1)} }
  8. var sub2 = {updata: function () {console.log(2)} }
  9. var sub3 = {updata: function () {console.log(3)} }
  10. // 一个主题对象
  11. funciton Dep () {
  12. this.subs = [sub1, sub2, sub3];
  13. }
  14. Dep.prototype.notify = function () {
  15. this.subs.forEach(function (sub) {
  16. sub.update();
  17. })
  18. }
  19. // 发布者发布消息,主题对象执行notify方法,然后会触发订阅者实现更函数
  20. var dep = new Dep();
  21. pub.publish(); // 1, 2, 3

set在这里的作用是:作为发布者发出通知,而文本节点在这里是订阅者,收到消息之后执行相应的更新操作。

双向绑定的实现

每当 new 一个 Vue,主要做了两件事:

1.是监听数据:observe(data),

2.第二个是编译 HTML:nodeToFragement(id)。

在监听数据的过程中,会为 data 中的每一个属性生成一个主题对象 dep。

在编译 HTML 的过程中,会为每个与数据绑定相关的节点生成一个订阅者 watcher,watcher 会将自己添加到相应属性的 dep 中。

现在效果:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的 set 方法。

下一步实现:发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。

关键逻辑:如何将 watcher 添加到关联属性的 dep 中。

在编译 HTML 过程中,为每个与 data 关联的节点生成一个 Watcher。

Watcher函数实现思路:

  1. function Watcher (vm, node, name, nodeType) {
  2. Dep.target = this;
  3. this.name = name;
  4. this.node = node;
  5. this.vm = vm;
  6. this.update();
  7. Dep.target = null;
  8. }
  9. Watcher.prototype = {
  10. update: function () {
  11. this.get();
  12. this.node.nodeValue = this.value;
  13. },
  14. // 获取data中的属性值
  15. get: function () {
  16. this.value = this.vm[this.name]; // 触发相应属性的get
  17. }
  18. }

首先,将自己赋给了一个全局变量 Dep.target;

其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;

再次,获取属性的值,然后更新视图。

最后,将 Dep.target 设为空。因为它是全局变量,也是 watcher 与 dep 关联的唯一桥梁,任何时刻都必须保证 Dep.target 只有一个值。

  1. function defineReactive (obj, key, val) {
  2. var dep = new Dep(); // !!
  3. Object.defineProperty(obj, key, {
  4. get: function () {
  5. // 添加订阅者watcher到主题对象Dep // !!
  6. if (Dep.target) dep.addSub(Dep.target); // !!
  7. return val
  8. },
  9. set: function (newVal) {
  10. if (newVal === val) return
  11. val = newVal;
  12. // 作为发布者发出通知
  13. dep.notify();
  14. }
  15. });
  16. }
  17. function Dep () {
  18. this.subs = []
  19. }
  20. Dep.prototype = {
  21. addSub: function(sub) {
  22. this.subs.push(sub);
  23. },
  24. notify: function() {
  25. this.subs.forEach(function(sub) {
  26. sub.update();
  27. });
  28. }
  29. };

最终效果:

文本内容会随输入框内容同步变化,在控制器中修改 vm.text 的值,会同步反映到文本内容中。

感悟

1.异步更新带来的数据响应式误解

  1. <div id="app">
  2. <h2>{{dataObj.text}}</h2>
  3. </div>
  4. new Vue({
  5. el: '#app',
  6. data: {
  7. dataObj: {}
  8. },
  9. ready: function () {
  10. var self = this;
  11. /**
  12. * 异步请求模拟
  13. */
  14. setTimeout(function () {
  15. self.dataObj = {};
  16. self.dataObj['text'] = 'new text';
  17. }, 3000);
  18. }
  19. })

上面的代码非常简单,我们都知道vue中在data里面声明的数据才具有响应式的特性,所以我们一开始在data中声明了一个dataObj空对象,然后在异步请求中执行了两行代码,如下:

  1. self.dataObj = {};
  2. self.dataObj['text'] = 'new text';

模板更新了,应该具有响应式特性,如果这么想那么你就已经走入了误区,一开始我们并没有在data中声明.text属性,所以该属性是不具有响应式的特性的。

但模板切切实实已经更新了,这又是怎么回事呢?

那是因为vue的dom更新是异步的,即当setter操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新真正执行的时候,此时.text属性已经赋值,所以指令更新模板时得到的是新值。

具体流程如下所示:

self.dataObj = {};发生setter操作

vue监测到setter操作,通知相关指令执行更新操作

self.dataObj['text'] = 'new text';赋值语句

指令更新开始执行

所以真正的触发更新操作是self.dataObj = {};这一句引起的,所以单看上述例子,具有响应式特性的数据只有dataObj这一层,它的子属性是不具备的。

2.Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)。然而它可以使用Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上或者使用$set

vue学习之响应式原理的demo实现的更多相关文章

  1. Vue数据绑定和响应式原理

    Vue数据绑定和响应式原理 当实例化一个Vue构造函数,会执行 Vue 的 init 方法,在 init 方法中主要执行三部分内容,一是初始化环境变量,而是处理 Vue 组件数据,三是解析挂载组件.以 ...

  2. Vue 2.0 与 Vue 3.0 响应式原理比较

    Vue 2.0 的响应式是基于Object.defineProperty实现的 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 prop ...

  3. 【Vue源码学习】响应式原理探秘

    最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...

  4. vue 数据劫持 响应式原理 Observer Dep Watcher

    1.vue响应式原理流程图概览 2.具体流程 (1)vue示例初始化(源码位于instance/index.js) import { initMixin } from './init' import ...

  5. 手写实现vue的MVVM响应式原理

    文中应用到的数据名词: MVVM   ------------------        视图-----模型----视图模型                三者与 Vue 的对应:view 对应 te ...

  6. 学习 vue 源码 -- 响应式原理

    概述 由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教. vue 主要通过 Watcher.Dep 和 Observer 三个类来实现响应式视图.另外还有一个 sch ...

  7. vue核心之响应式原理(双向绑定/数据驱动)

    实例化一个vue对象时, Observer类将每个目标对象(即data)的键值转换成getter/setter形式,用于进行依赖收集以及调度更新. Observer src/core/observer ...

  8. Vue.js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  9. Vue 数据响应式原理

    Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...

随机推荐

  1. android本地音乐播放器

    乐乐音乐播放器更新到2.0版本了,之前1.0版本更多的是试验性实践,这次更新的2.0版本,更多的是将1.0的功能移植到2.0,在界面和皮肤风格上,参考了 天天动听 界面,在歌词显示方面 与 1.0 版 ...

  2. Apache Kafka简介与安装(二)

    Kafka在Windows环境上安装与运行 简介 Apache kafka 是一个分布式的基于push-subscribe的消息系统,它具备快速.可扩展.可持久化的特点.它现在是Apache旗下的一个 ...

  3. 继续死磕SDRAM控制器

    SDRAM控制器 博主上一篇介绍了一些SDRAM的基本原理是否有必要学习使用纯Verilog写一个SDRAM控制器,接下来记录SDRAM控制器的工作原理.首先是上电初始化. 上电初始化 时序图中,tR ...

  4. 万水千山ABP - 弹出对话框禁用回车

    模态对话框中禁用回车 ABP Zero 中,使用弹出对话框进行实体编辑,回车时会自动保存并关闭对话框.那么如何禁用这个回车功能 ? 查看实体列表视图 index.cshtml 所对应加载的脚本文件 i ...

  5. AS3编程规范

    整理了一些AS3的编程规范,有些规则对于大部分语言都是适用的,有什么问题请提出来,我会持续改进这份规范,谢谢!   [参考资料] 1.http://opensource.adobe.com/wiki/ ...

  6. unity零基础开始学习做游戏(一)为了实现你的游戏,你需要提前做的准备工作

    -------小基原创,转载请给我一个面子 正所谓,工欲善其事,必现准备好电脑.接下来跟着小基一步一步来搭建你的开发环境吧 1.下载安装unity 上面的那个是破解软件,下面是unity5.5.6的安 ...

  7. datetime的精度

    最近有需要将分钟线的数据进行内联拼接,但时间没有必要精确到秒,微秒. df['datetime'] = pd.to_datetime(df['datetime']) df = df.set_index ...

  8. Ocelot中文文档-服务发现

    Ocelot允许您指定服务发现提供程序,并使用它来查找Ocelot正在将请求转发给下游服务的主机和端口.目前,这仅在GlobalConfiguration部分中受支持,这意味着所有ReRoute将使用 ...

  9. EF Code First中的主外键约定和一对一、一对多关系的实现

    对于主外键约定的理解,其实是学习实体间一对一和一对多关系的基础. 1.1 主键(Key)约定 主键的默认约定是:只要字段名为--实体名(类名)+"id"(不区分大小写),这就算是默 ...

  10. Java Spring Boot 上传文件和预览文件地址解析

    @RequestMapping(value ="/upload",method = RequestMethod.POST) @Permission(isAjax=false) pu ...