前言

vue是一个非常典型的MVVM框架,它的核心功能一是双向数据绑定系统,二是组件化开发系统。那么本文是以一种通俗易懂的的角度来实现一个简单

的双向数据绑定系统,如果你用过vue却对vue的实现原理不太清楚,或者没用过vue想学习vue那我相信看完本文你会的vue的实现有一个比较简单明确的了解。不过如果哪块有

错误,还望指出。

本文的实现目标:

input标签和{{text}}的内容与data中的text值保持一致,实现双向绑定

    <div id="app">
<input type="text" v-model="text">
{{text}}
</div>
    var vm=new Vue({
el:'app',
data:{
text:'hello world!'
}
})

分解任务(三步)

  • model→view的初始化绑定
  • view→model的绑定
  • model→view的绑定

看不太懂?不要着急,接下来先一步一步分析每一步都具体做了什么再回头看

Step1 : model→view的初始化绑定.

很简单,就是让v-mode="text"和{{text}}绑定到的data中text的值。这里会有两个函数帮我们做事情,一个是node2Fragment函数,帮我们取到结点,

一个是compile函数,操作我们取到的node结点的值去等于对应的data值,这样就完成了model到view的第一次初始化绑定。

  function node2Fragment(node,vm){
//这里是dom劫持,vue会新建一个文档片段来替换dom中本来的结点
var flag=document.createDocumentFragment();
//子节点
var child;
while(child=node.firstChild){
//开始编译每个结点
compile(child,vm);
//appendchild方法会自动删除node对象的child结点
flag.appendChild(child)
}
return flag;
}
  function compile(node,vm){
var reg=/\{\{(.*)\}\}/;
//节点类型为元素
if(node.nodeType===1){
var attr=node.attributes;
for(var i=0;i<attr.length;i++){
//匹配v-model这个属性名称
if(attr[i].nodeName=='v-model'){
var name=attr[i].nodeValue;
//将data的值赋给gainode
node.value=vm.data[name];
}
}
};
//节点类型为text
if(node.nodeType===3){
if(reg.test(node.nodeValue)){
var name=RegExp.$1;
name=name.trim();
//将data的值赋给该node
node.nodeValue=vm.data[name];
}
}
}
  //Vue对象
function Vue(options){
this.data=options.data;
var id=options.el;
var dom=node2Fragment(document.getElementById(id),this);
//编译完成后,将dom片段添加到el挂载的元素上(app)
document.getElementById(id).appendChild(dom)
}
//调用Vue
var vm=new Vue({
el:'app',
data:{
text:'hello world!'
}
})

最终达到的效果如下图:v-model绑定的input和{{text}}的值和data中的text保持一致

Step2 : view→model的绑定.

这一步的目标:当用户输入改变input的值(view层)时,反映到data中(model层)并改变对应的值

方法:

  • 在complie编译的时候监听node,并改变data中的值为node.value;
  • 通知data中的数据改变(这里会用到访问器属性,即Object.defineProperty

    这里我们先完成第二个点,通知数据改变,在全局中新添加两个函数
  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)
}
})
}
function observe(obj,vm){
Object.keys(obj).forEach(function(key){
defineReactive(vm,key,obj[key])
})
}
  //Vue对象
function Vue(options){
this.data=options.data;
var id=options.el;
var data=this.data;
//将data的属性全部通过访问器属性赋给vm对象,使读写vm实例的属性转成读写了vm.data的属性值,达到鱼目混珠的效果
observe(data,this);
var dom=node2Fragment(document.getElementById(id),this);
//编译完成后,将dom片段添加到el挂载的元素上(app)
document.getElementById(id).appendChild(dom)
}

监听node(修改complie函数)

  function compile(node,vm){
var reg=/\{\{(.*)\}\}/;
//节点类型为元素
if(node.nodeType===1){
var attr=node.attributes;
for(var i=0;i<attr.length;i++){
//匹配v-model这个属性名称
if(attr[i].nodeName=='v-model'){
var name=attr[i].nodeValue;
node.addEventListener('input',function(e){
//给对应的data属性赋值,并触发该属性的set函数
vm[name]=e.target.value;
});
//将data值赋给该node,注意这里本来是vm.data[name]
node.value=vm[name]
}
}
};
//节点类型为text
if(node.nodeType===3){
if(reg.test(node.nodeValue)){
var name=RegExp.$1;
name=name.trim();
//将data的值赋给该node,注意这里本来是vm.data[name]
node.nodeValue=vm[name];
}
}
}

那么step2完成了,当用户在input中输入值,data属性值也会发生改变,这样一来就完成了model→view的一个实现过程

### Step3 : model→view的绑定
诶不是之前已经绑定过一次model→view,怎么还要绑定?
第一次绑定是初始化绑定,我们现在要完成的是,当用户改变data值,再回过头去改变view层,这里刚好可以用到一个设计模式:
观察者模式-让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。
![](https://images2018.cnblogs.com/blog/1070235/201805/1070235-20180518102119569-32326473.png)

放到这里就是:每个data属性值在`defineReactive`函数监听处理的时候,添加一个主题对象,当data属性发生改变,通过set函数去通知所有的观察者们,
那么如何添加观察者们呢,就是在`complie`函数编译node时,通过初始化value值,触发set函数,在set函数中为主题对象添加
观察者。有点难理解?直接看代码就明白了。
```javascript
function compile(node,vm){
var reg=/\{\{(.*)\}\}/;
//节点类型为元素(这块在这里并没有修改)
//节点类型为text
if(node.nodeType===3){
if(reg.test(node.nodeValue)){
var name=RegExp.$1;
name=name.trim();
//初始化数据,并给对应的data属性值添加观察者
new Watcher(vm,node,name);
}
}
}
```
我们注意到新建了一个Watcher对象,这个对象的作用就是初始化数据(step1做的工作),以及触发get函数,添加这个node到观察者
```javascript
function Watcher(vm,node,name){
//Dep.target是一个Dep的静态属性,表示当前观察者。
Dep.target=this;
this.name=name;
this.node=node;
this.vm=vm;
//订阅者执行一次更新视图
this.update();
Dep.target=null;
}
Watcher.prototype={
update:function(){
//触发对应data属性值的get函数
this.get();
this.node.nodeValue=this.value;
},
get:function(){
this.value=this.vm[this.name]
}
}
```
观察者设置好了,现在设置主题,在`defineReactive`函数里
```javascript
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;
//作为发布者发出通知(更新所有订阅了这个属性的view)
dep.notify();
}
})
}
```
主题的结构:
```javascript
function Dep(){
//主题的订阅者们
this.subs=[];
}
Dep.prototype={
//添加订阅者的方法
addSub:function(sub){
this.subs.push(sub);
},
//发布信息的方法(让订阅者们全部更新view)
notify:function(){
this.subs.forEach(function(sub){
sub.update();
})
}
}
```
如此一来,一个简单的MVVM就实现了,思维导图如下:
![](https://images2018.cnblogs.com/blog/1070235/201805/1070235-20180518102128583-106487127.png)

不过这只是Vue的冰山一角,只是实现了一个v-model,陆陆续续会更新其他的操作和一些细节,敬请期待,如果你
看完了并且有所收获不妨点个star(滑稽脸)

vue源码解读1的更多相关文章

  1. Vue 源码解读(1)—— 前言

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  2. Vue 源码解读(2)—— Vue 初始化过程

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  3. Vue 源码解读(3)—— 响应式原理

    前言 上一篇文章 Vue 源码解读(2)-- Vue 初始化过程 详细讲解了 Vue 的初始化过程,明白了 new Vue(options) 都做了什么,其中关于 数据响应式 的实现用一句话简单的带过 ...

  4. Vue 源码解读(4)—— 异步更新

    前言 上一篇的 Vue 源码解读(3)-- 响应式原理 说到通过 Object.defineProperty 为对象的每个 key 设置 getter.setter,从而拦截对数据的访问和设置. 当对 ...

  5. Vue 源码解读(5)—— 全局 API

    目标 深入理解以下全局 API 的实现原理. Vue.use Vue.mixin Vue.component Vue.filter Vue.directive Vue.extend Vue.set V ...

  6. Vue 源码解读(6)—— 实例方法

    前言 上一篇文章 Vue 源码解读(5)-- 全局 API 详细介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会详细介绍各个实例方法的实现原理. 目标 深入理解以下实例方法的实现原理. v ...

  7. Vue 源码解读(8)—— 编译器 之 解析(上)

    特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了上下两篇,所以在阅读本篇文章时请同时打开 Vue 源码解读(8)-- 编译器 之 解析(下)一起阅读. 前言 V ...

  8. Vue 源码解读(8)—— 编译器 之 解析(下)

    特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了两篇文章,本篇是对 Vue 源码解读(8)-- 编译器 之 解析(上) 的一个补充,所以在阅读时请同时打开 Vu ...

  9. Vue 源码解读(9)—— 编译器 之 优化

    前言 上一篇文章 Vue 源码解读(8)-- 编译器 之 解析 详细详解了编译器的第一部分,如何将 html 模版字符串编译成 AST.今天带来编译器的第二部分,优化 AST,也是大家常说的静态标记. ...

  10. Vue 源码解读(10)—— 编译器 之 生成渲染函数

    前言 这篇文章是 Vue 编译器的最后一部分,前两部分分别是:Vue 源码解读(8)-- 编译器 之 解析.Vue 源码解读(9)-- 编译器 之 优化. 从 HTML 模版字符串开始,解析所有标签以 ...

随机推荐

  1. 微信小程序开发踩坑记录

    1.由于小程序wx.request()方法是异步的,在app.js执行ajax后,各分页加载app.js的全局数据时,无法按顺序加载.例: //app.js App({ ajax:function() ...

  2. springboot vue组件写的个人博客系统

    个人写的博客管理系统,学习java不到一年 欢迎探讨交流学习 https://github.com/Arsense/ssmBlog  项目地址 如果觉得好的 帮忙star一下 谢谢! 基本技术 环境: ...

  3. 亲测,很有效的忽略SSL证书方法

    1.在httpclient发起请求时,有时会出现下面这种情况 你的日志中出现有关SSL的异常,javax.net.ssl.SSLPeerUnverifiedException: peer not au ...

  4. 天气小雨, 心情多云, 练习标准的键盘ABC打法

    今天看到饿了么转型生活做千亿美元公司 突然想到一些就写下来 当时外卖一份8元 10元的年代那个开心啊 很久以前宁可跑个远, 都不愿意叫外卖 叫了大概1年的外卖了, 之前还感到便宜多样, 现在感觉到的是 ...

  5. javascript:中文等字符转成unicode

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. 转载《学习HTML5 canvas遇到的问题》

    学习HTML5 canvas遇到的问题 1. 非零环绕原则(nonzZero rule) 非零环绕原则是canvas在进行填充的时候是否要进行填充的判断依据. 在判断填充的区域拉一条线出来,拉到图形的 ...

  7. AE多用户同时编辑同一个版本数据的解决方法

    项目中做了入库的功能,测试一切正常,但是实际使用多个用户同时编辑default版本的时候,问题就来了,StopEditing 错误信息如下 FDO_E_VERSION_REDEFINED -21472 ...

  8. keychains

    keychain在ios中是保存在sqlite数据库中的.这个数据库文件的位置:真机:/private/var/Keychains/keychain-2.db虚拟机:/Users/USER-HOME/ ...

  9. mybatis3 step by step 快速上手

    作者:Panda Fang 出处:http://www.cnblogs.com/lonkiss/p/6895617.html 原创文章,转载请注明作者和出处,未经允许不可用于商业营利活动 官方网站 h ...

  10. 多线程(四)~数据操作的原子性,使用原子性操作AutomicInteger替换非原子性的i++的操作

    这一章,我们要来验证volatile关键字不是原子性的,OK,还是用代码来说话. ①.线程类,操作i++ 500次 package com.multiThread.thread; publicclas ...