Vue.js 核心:

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

2、组件系统。

访问器属性

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

       var obj = { };

       // 为obj定义一个名为 hello 的访问器属性

       Object.defineProperty(obj, "hello", {

         get: function () {return sth},

         set: function (val) {/* do sth */}

       })

       obj.hello // 可以像普通属性一样读取访问器属性

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

       obj.hello // 读取属性,就是调用get函数并返回get函数的返回值

       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 整体返回插入挂载目标。

<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<input type="text" id="a">
<span id="b"></span>
</div> <script type="text/javascript">
var dom = nodeToFragment(document.getElementById('app'));
console.log(dom); function nodeToFragment (node) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
flag.append(child); // 劫持node的所有子节点
}
return flag;
} document.getElementById('app').appendChild(dom); // 返回到app中
</script>
</body>
</html>

数据初始化绑定

function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
if (node.nodeType === 1) {
var attr = node.attributes; // 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.value = vm.data[name]; // 将data的值赋值给该node
node.removeAttribute('v-model');
}
}
} // 节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 获取匹配到的字符串
name = name.trim();
node.nodeValue = vm.data[name];
}
}
} function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child; while (child = node.firstChild) {
compile(child, vm);
flag.append(child);
}
return flag;
} function Vue (options) {
this.data = options.data;
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this); // 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
} var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
})

效果

响应式的数据绑定

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

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

    function defineReactive (obj, key, val) {

      Object.defineProperty(obj, key, {
get: function () {
return val
},
set: function (newVal) {
if (newVal === val) return
val = newVal;
console.log(val); // console
}
});
} function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
});
} function Vue (options) {
this.data = options.data;
var data = this.data; observe(data, this); var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this); // 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
} function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.addEventListener('input', function (e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
});
node.value = vm[name]; // 将data的值赋给该node node.removeAttribute('v-model');
}
}; new Watcher(vm, node, name, 'input');
}
// 节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 获取匹配到的字符串
name = name.trim();
node.nodeValue = vm[name];
}
}
}

第二部完成效果

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

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

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

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

	var pub = {
publish: function() {
def.notify():
}
}
// 三个订阅者subscribers
var sub1 = {updata: function () {console.log(1)} }
var sub2 = {updata: function () {console.log(2)} }
var sub3 = {updata: function () {console.log(3)} } // 一个主题对象
funciton Dep () {
this.subs = [sub1, sub2, sub3];
}
Dep.prototype.notify = function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
// 发布者发布消息,主题对象执行notify方法,然后会触发订阅者实现更函数
var dep = new Dep();
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函数实现思路:

    function Watcher (vm, node, name, nodeType) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
} Watcher.prototype = {
update: function () {
this.get();
this.node.nodeValue = this.value;
},
// 获取data中的属性值
get: function () {
this.value = this.vm[this.name]; // 触发相应属性的get
}
}

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

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

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

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

    function defineReactive (obj, key, val) {

      var dep = new Dep();  // !!

      Object.defineProperty(obj, key, {
get: function () {
// 添加订阅者watcher到主题对象Dep // !!
if (Dep.target) dep.addSub(Dep.target); // !!
return val
},
set: function (newVal) {
if (newVal === val) return
val = newVal;
// 作为发布者发出通知
dep.notify();
}
});
}
function Dep () {
this.subs = []
} Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
}, notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};

最终效果:

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

感悟

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

<div id="app">
<h2>{{dataObj.text}}</h2>
</div> new Vue({
el: '#app',
data: {
dataObj: {}
},
ready: function () {
var self = this; /**
* 异步请求模拟
*/
setTimeout(function () {
self.dataObj = {};
self.dataObj['text'] = 'new text';
}, 3000);
}
})

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

self.dataObj = {};
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. Oracle EBS ERP中月结年结的流程总结

    月结与年结处理,是企业财务比较特殊而重要的业务操作.在实施与推广OracleERP系统过程中,如何结合现行的会计制度与惯例,充分利用软件功能,做好相应的关账.开账工作,是困扰许多企业财务人员乃至实施顾 ...

  2. Access text files using SQL statements by DB Query Analyzer

    Access text files using SQL statements by DB Query Analyzer Ma Gen feng (Guangdong Unitoll Services ...

  3. os x下如何挂载iso镜像

    在linux下可以使用 mount -o loop 在os x下mount好想没有loop选项,不过可以用系统自带的命令 hdiutil mount xxx.iso 即可,弹出可以用 hdiutil ...

  4. rails4 中使用分页的方法

    以前老版本的rails中默认自带分页方法,不过从rails2.0开始就将内置的分页pagination对象移除了,改以第三方gem提供支持.要在新的rails里使用分页也是非常简单啦,首先安装will ...

  5. 使用IntelliJ IDEA的小技巧快乐编程(2)

    前言 本篇介绍的技巧为IntelliJ IDEA中自动代码生成相关的技巧,合理的使用这些技巧将大大提高的你的编码效率 :) Trick 6. 使用模板代码 idea默认的提供了许多模板代码,你可以使用 ...

  6. 排序算法入门之选择排序-Java实现

    本文参考http://blog.csdn.net/m0_37568091/article/details/78023705 选择排序是先从对象数组中选出最小的放在第一个位置,再从剩下的元素中选择次小的 ...

  7. for循环嵌套讲解:

    1.for循环嵌套讲解: class ForForDemo {     public static void main(String[] args)     {         //大圈套小圈思想: ...

  8. HTMLConverter使用实例(转)

    ---- 本来,Applet的概念相当简单——只要在Web页面中加入一个< APPLET >标记就可以了.浏览器一遇到这个标记,就会下载对应的 Applet类文件,并启动自己的解释器运行这 ...

  9. (转)Go语言并发模型:使用 context

    转载自:https://segmentfault.com/a/1190000006744213 context golang 简介 在 Go http包的Server中,每一个请求在都有一个对应的 g ...

  10. dll附加依赖项查看——dumpbin 命令

    VS自带工具   查看程序或动态链接库需要的动态链接库 dumpbin /dependents d:\test.exe 查看动态链接库的输出函数 dumpbin /exports d:\libmysq ...