Vue之双向绑定原理动手记
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之双向绑定原理动手记的更多相关文章
- vue的双向绑定原理及实现
前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...
- vue数据双向绑定原理
vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...
- vue的双向绑定原理解析(vue项目重构二)
现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...
- vue的双向绑定原理浅析与简单实现
很久之前看过vue的一些原理,对其中的双向绑定原理也有一定程度上的了解,只是最近才在项目上使用vue,这才决定好好了解下vue的实现原理,因此这里对vue的双向绑定原理进行浅析,并做一个简单的实现. ...
- Vue数据双向绑定原理及简单实现
嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...
- 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分
最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...
- Vue.js双向绑定原理
Vue.js最核心的功能有两个,一个是响应式的数据绑定系统,另一个是组件系统.本文仅仅探究双向绑定是怎样实现的.先讲涉及的知识点,再用简化的代码实现一个简单的hello world示例. 一.访问器属 ...
- 探讨vue的双向绑定原理及实现
1.vue的实现原理 vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属 ...
- 【Vue】vue的双向绑定原理及实现
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,那么vue是如果进行数据劫持的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西. 代码: var ...
随机推荐
- Andrew Ng机器学习课程,第一周作业,python版本
Liner Regression 1.梯度下降算法 Cost Function 对其求导: theta更新函数: 代码如下: from numpy import * import numpy as n ...
- shell中exec命令
1.find中的-exec参数 在当前目录下(包含子目录),查找所有txt文件并找出含有字符串"bin"的行 find ./ -name "*.txt" -ex ...
- java基础基础总结----- 构造方法,可变参数列表
package com.mon11.day2; /** * 类说明 :构造方法,可变参数列表 * @author 作者 : chenyanlong * @version 创建时间:2017年11月2日 ...
- HTTP header location 重定向 URL
http头信息 头信息的作用很多,最主要的有下面几个:1.跳转当浏览器接受到头信息中的 Location: xxxx 后,就会自动跳转到 xxxx 指向的URL地址,这点有点类似用 js 写跳转.但是 ...
- postman提取接口的返回值及动态设置变量(一)
一.提取接口返回值 1.当返回值是返回JSON时 let json = JSON.parse(responseBody); // responseBody是包含整个返回内容的字符串 let foo ...
- 在Emacs中启用Fcitx输入法
安装fcitx输入法,在 ~/.xinitrc文件中添加如下内容 (我用startx启动图形环境,所以在~/.xinitrc中配置X会话) export LC_CTYPE="zh_CN.UT ...
- vue需要注意的事宜
1.Vue在进行点击事件的时候大部分是在标签上进行添加的,一般在标签上添加@click: 如果需要在组件上面进行点击事件的时候,直接写@click是木有变化的,需要在后面添加一个.native就如@c ...
- LVTTL与LVCMOS区别
TTL电平的VIH/VIL一般是2V/0.8V,VOH/VOL一般是 2.4V/0.4V,不论是3.3V还是5V的TTL都一样的:CMOS的VIH/VIL一般是70%VCC/30%VCC,VOH/VO ...
- Linux下可以使用ps命令来查看Oracle相关的进程
Linux下可以使用ps命令来查看Oracle相关的进程 Oracle Listener 这个命令会列出Oracle Net Listener的进程 [oracle@ www.linuxidc.com ...
- Nagios配置文件nagios.cfg详解
这里开始要讲一些Nagios的配置. 首先要看看目前Nagios的主配置路径下有哪些文件.[root@nagios etc]# ll总用量 152-rwxrwxr-x. 1 nagios nagios ...