Vue.js的核心功能有两个:一是响应式的数据绑定系统,二是组件系统。本文是通过学习他人的文章,从而理解了双向绑定原理,从而在自己理解的基础上,自己动手实现数据的双向绑定。

  目前几种主流的mvc(vm)框架都实现了单向数据绑定,而双向数据绑定我觉得就是在单向绑定的基础上,给input、textarea等可输入元素添加change事件,从而动态修改model和view。我们可以动手做一个。

<body>
<input type="text" id="test">
<span id="show"></span>
<script type="text/javascript">
let obj = {};
Object.defineProperty(obj,'name',{
configurable: false,
enumerable: true,
get: function() {
return val
},
set: function(newVal) {
document.getElementById('test').value = newVal;
document.getElementById('show').innerHTML = newVal;
}
})
document.addEventListener('keyup',function(e) {
obj.name = e.target.value
})
</script>
</body>

此时的效果是:在input中输入内容,会同步显示到span中;在控制台中修改输入obj.name的值,视图也会得到相应的更新。这样就实现了一个简单的数据双向绑定。

  Vue.js是通过数据劫持结合发布者-订阅者的方式,通过Object.defineProperty()来劫持各个属性的getter、setter,在数据发生变动时发布消息给订阅者,同时触发相应的监听回调。

  整理了一下实现数据双向绑定的思路:

  1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如果有变动可以拿到最新值并通知订阅者;

  2.实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定到相应的更新函数

  3.实现一个Watcher,作为Observer和Compile的桥梁,能够订阅并接收每个属性变动的通知,执行绑定回调函数相应的更新函数,从而更新视图

  4.入口函数Vue

以下是我们需要实现的:

<div id="app">
<input type="text" v-model="name">
<p>{{name}}</p>
<p v-text="name"></p>
{{name}}
</div>
<script>
let vm = new Vue({
el: 'app',
data: {
name: 'zengfp'
}
})
</script>

实现Observer

我们知道可以利用Object.defineProperty()来监听属性的变动,那么我们将需要用observer对数据对象进行递归遍历,包括子属性对象的属性,(但是在这里,我做的比较简单,所以就不需要对进行递归遍历了。)都加上setter和getter。如果给这个对象赋值,就会触发setter函数,那么就能监听到数据变化。相关代码如下:

        function Observe(obj){
if(!obj || typeof obj !== 'object'){
return
}
Object.keys(obj).forEach(function(key) {
defineReactive(obj,key,obj[key])
})
} function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
configurable: false,
enumerable: true,
get: function() {
return val
},
set: function(newVal) {
val = newVal;
console.log('我的数据发生了改变:'+ val + '->' + newVal)
}
})
}

这样我们就可以监听到每个数据的变化了,接下来就是在监听到数据变化之后怎么通知订阅者了,所有接下来需要实现一个消息订阅器,用一个简单的数组,作为订阅者收集器,数据变动触发notify(),在调用订阅者的update()方法,代码改善后如下:

     function Dep() {
this.subs = []
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub)
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update()
})
}
}
    function defineReactive(obj,key,val){
        let dep = new Dep()
Object.defineProperty(obj,key,{
configurable: false,
enumerable: true,
get: function() {
return val
},
set: function(newVal) {
val = newVal;
console.log('我的数据发生了改变:'+ val + '->' + newVal              
              dep.notify()// 通知所有订阅者
           
}
        })
    }
 

上面的思路整理中我们已经明确订阅者应该是Watcher, 而且let dep = new Dep();是在defineReactive方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在 getter里面动手脚:

      Object.defineProperty(obj,key,{
......
get: function() {
            //由于需要在闭包内添加watcher,所以通过dep定义一个全局属性target,暂存于watcher,添加完移除
Dep.target && dep.addSub(Dep.target)
return val
},
.......
})

至此就实现了一个observer,接下来需要实现Compile了

实现Compile

compile主要做的事情就是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,就会收到通知更新视图,因为遍历解析的时候有多次操作dom节点,为提高性能和效率,会先将根节点el转换成文档碎片documentFragment进行解析编译操作,待解析完成,在将documentFragment添加到真实的dom节点中。

    function Compile(el,vm) {
this.$vm = vm;
this.$el = document.getElementById(el)
if(this.$el){
this.$fragment = this.nodeToFragment(this.$el);
this.compile(this.$fragment);
this.$el.appendChild(this.$fragment)
}
}
  
Compile.prototype = {
isElementNode: function(node) {
return node.nodeType == 1
},
isTextNode: function(node) {
return node.nodeType == 3
},
nodeToFragment: function(node) {
let fragment = document.createDocumentFragment() , child;
while(child = node.firstChild) {
fragment.appendChild(child)
}
return fragment
},
compile: function(node) {
let childNodes = node.childNodes,
_this = this;
[].slice.call(childNodes).forEach(function(node) {
let value = node.textContent,
reg = /\{\{(.*)\}\}/;
if(_this.isElementNode(node)){
_this.compileElement(node)//元素节点
}else if(_this.isTextNode(node) && reg.test(value)) {
_this.compileText(node,RegExp.$1)//文本节点
}
})
},
compileElement: function(node) {
let attrs = node.attributes,
value = node.textContent,
reg = /\{\{(.*)\}\}/,
_this = this;
if(attrs && attrs.length >0){
[].slice.call(attrs).forEach(function(attr) {
let attrName = attr.name;
if(attrName == 'v-model'){
let key = attr.value;
node.addEventListener('input',function(e) {
_this.$vm.data[key] = e.target.value;
})
node.value = _this.$vm.data[key]
}else if(attrName == 'v-text'){
let key = attr.value
// node.textContent = _this.$vm.data[key]
new Watcher(_this.$vm,node,key)
}
node.removeAttribute(attrName)
})
}else if(reg.test(value)){
let key = RegExp.$1;
new Watcher(_this.$vm,node,key)
}
},
compileText: function (node,key) {
new Watcher(this.$vm,node,key)
}
}

实现Watcher

watcher订阅者作为observer和compile之间通信的桥梁,主要做以下事情:

在自身实例化时往属性订阅器中dep中添加自己;

自身有一个update的方法

待属性自身变动dep.notify通知时,能够调用update方法,并触发compile中的回调

function Watcher(vm,node,key){
Dep.target = this;//将当前订阅器指向自己
this.key = key;
this.node = node;
this.vm = vm;
this.update();//触发get()函数,从而在dep中添加自己:Dep.target && dep.addSub(Dep.target)
Dep.target = null
}
Watcher.prototype = {
update: function () {
this.get();
this.node.textContent = this.value;
},
get: function() {
this.value = this.vm.data[this.key]
}
}

实现Vue

function Vue(options) {
this.data = options.data;
let data = this.data;
observe(data);
let id = options.el;
this.compile = new Compile(id || document.body, this)
}

总结

本文主要是让自己更加理解双向绑定原理。

observe每个数据的属性,object.defineProperty(),setter、getter函数 ——>在compile的时候为每个元素节点添加订阅者watcher——>添加watcher的过程中触发了update函数,进而调用watcher里面的get函数——>从而触发了访问器里面的get函数,从而触发了订阅器中的addSub函数,就把watcher添加到订阅器中——>从而实现了model->view的实现;

当在input输入框中输入文本,触发了input的监听事件,把input中的值赋给observe中的对象属性——>从而触发了Object.defineProperty()的set函数,从而消息订阅器发出通知dep.notify()——>从而每个订阅者执行更新函数sub.update(),从而触发watcher的get函数——>获取最新值时又触发了访问器里面的get函数——>从而更新最新值到视图中,实现了view->model

Vue之双向绑定原理动手记的更多相关文章

  1. vue的双向绑定原理及实现

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  2. vue数据双向绑定原理

    vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...

  3. vue的双向绑定原理解析(vue项目重构二)

    现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...

  4. vue的双向绑定原理浅析与简单实现

    很久之前看过vue的一些原理,对其中的双向绑定原理也有一定程度上的了解,只是最近才在项目上使用vue,这才决定好好了解下vue的实现原理,因此这里对vue的双向绑定原理进行浅析,并做一个简单的实现. ...

  5. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  6. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  7. Vue.js双向绑定原理

    Vue.js最核心的功能有两个,一个是响应式的数据绑定系统,另一个是组件系统.本文仅仅探究双向绑定是怎样实现的.先讲涉及的知识点,再用简化的代码实现一个简单的hello world示例. 一.访问器属 ...

  8. 探讨vue的双向绑定原理及实现

    1.vue的实现原理 vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属 ...

  9. 【Vue】vue的双向绑定原理及实现

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,那么vue是如果进行数据劫持的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西. 代码: var ...

随机推荐

  1. python之旅:面向对象进阶

    一 isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object) ...

  2. vue-router基本概念及使用

    index.html: <!DOCTYPE html> <html> <head> <title></title> <meta cha ...

  3. A*算法–A* algorithm tutorial

    Author:Justin Heyes-Jones From: http://heyes-jones.com/astar.php Date:2014.8.16 本文地址:http://www.cnbl ...

  4. SHELL (3) —— 变量知识进阶和实践

    摘自:Oldboy Linux运维——SHELL编程实战 SHELL中特殊切重要的变量 位置变量 作用说明 $0 获取当前执行的Shell脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径 $n ...

  5. 关于css中a标签的样式

    CSS为一些特殊效果准备了特定的工具,我们称之为“伪类”.其中有几项是我们经常用到的,下面我们就详细介绍一下经常用于定义链接样式的四个伪类,它们分别是: :link :visited :hover : ...

  6. bzoj千题计划259:bzoj3122: [Sdoi2013]随机数生成器

    http://www.lydsy.com/JudgeOnline/problem.php?id=3122 等比数列求和公式+BSGS #include<map> #include<c ...

  7. [C]语法, 知识点总结(一. 进制, 格式化输入/出, 指针)

    进制 概念: n进制, 最大的数是n-1, 逢n进1位. 数据类型 概念: 其实就是占的位数不同, 转换到计算机当中都是0和1. 常用: 类型名 占字节数 描述 char 1字节=8个二进制位 字符类 ...

  8. MySql数据库表设计规范

    建表规约 索引规约 SQL 语句 其他实战建议 选用utf8编码 建议使用InnoDB存储引擎 建议每张表都设置一个主键 建议字段定义为NOT NULL 唯一值字段要指定唯一性约束 ALTER TAB ...

  9. xml json

    简单概括的话就是,xml本身是一种格式规范,是一种包含了数据以及数据说明的文本格式规范. 比如,我们要给对方传输一段数据,数据内容是“too young,too simple,sometimes na ...

  10. Space Replacement

    Write a method to replace all spaces in a string with %20. The string is given in a characters array ...