精华内容
下载资源
问答
  • 1.背景 平时使用过 Vue 这个前端框架的同学,对于数据绑定这个词肯定不会陌生,进一步,它与 react 有点不同的是它还有一个双向数据绑定 v-model。 数据绑定的方式能够极大程度上方便我们开发,不用去进行繁琐的 DOM...

    1.背景

    平时使用过 Vue 这个前端框架的同学,对于数据绑定这个词肯定不会陌生,进一步,它与 react 有点不同的是它还有一个双向数据绑定 v-model

    数据绑定的方式能够极大程度上方便我们开发,不用去进行繁琐的 DOM 操作,这也是 MVVM 框架的一个极大的优势所在。

    网上阅读到其他同学对 Vue 源码解读的文章,发现大家对 Vue 的依赖收集机制解读会有些不一样的方式。有些同学解释说依赖收集使用的观察者模式,有些同学则解释说是发布/订阅模式

    因为以前认为这两个名字只是不同的叫法而已也就没太在意,但偶然发现有人问这两个模式的区别,这个问题突然也引起了我的好奇心,因此也就花了些时间来比较这两个名词背后到底有什么不同。

    f32b710b9f205f8e58490ea32a50b5c1.png

    2.两个模式的异同

    61ef4100b7393963a11819c6deead7ca.png

    观察者模式与发布-订阅模式

    首先直接上图。从图中可以看出,无论是观察者模式还是发布-订阅模式,它们都是一些状态依赖于某些数据,当数据发生变化,这些状态也需要进行相应的更新的模式。不同点在于,发布-订阅模式比观察者模式多了个事件中心

    更细致点说,在观察者模式中,被观察者能够完全感知到观察者的存在,一旦发生变化,被观察者负责将变化通知到所有的观察者。

    举个例子,假设你要出租一套房子,你在网上发布了信息,有很多人联系你。当你的房子租出去之后,你要将这个信息通知到剩下的其他人,让他们去租别的房子。

    135e6309a6385603da26c82c2dd779b6.png

    而在发布-订阅模式中,因为有个事件中心的存在,当发布者发布新的信息之后,事件中心会去通知更新。

    还是出租房子的例子,你把你要出租房子的信息告诉中介,同时中介那边有很多客户让中介帮忙找房,当你的房子租出去后,你只需要告诉中介房源没了,中介则会去通知意向客户房源缺失的信息。

    在该模式中,订阅者向事件中心订阅某些事件,发布者并不知道有没有订阅者,只要发生变化,发布者就会通知事件中心。

    下面就用实际的代码来还原一下这两个模式。

    3.观察者模式

    ObserverList.js

    观察者列表类,由被观察者管理,添加或移除对应的观察者。

    b670dc8f2df1015e015488f6c3eccd44.png
    Observer.js

    观察者类,接到更新通知后,做出一些反馈。

    2225c8b992299fc15406a8fa6ea27e2d.png
    Observed.js

    被观察者类,负责维护观察者,并且将更新通知到所有观察者。

    4283626a7d9c3e63a2939b7424bb5d60.png
    index.js
    6f2f3c08dd5c1941097e4801099c49a0.png

    4.发布-订阅模式

    EventCenter.js

    事件中心类,包括处理订阅者的订阅/退订事件,以及发布者发布事件后的变更通知。

    17911eb7c0f46fa98d7fef1af2978f79.png
    Publisher.js

    发布者类,负责向事件中心发布变更。

    d315dac21e52c6734fefac8f1fdd9c28.png
    Subscriber.js

    订阅者类,对变更通知做出反馈。

    5417fe78733c3ea76c2b1c4d2d946b08.png
    index.js
    60a50cbdf3e83623fa283d10febd1e79.png

    5.小结

    总的来说,两种设计模式都是为了解耦,其中发布-订阅模式解耦度更高。也可以认为发布-订阅模式是观察者模式的进一步解耦,这也是有时候会认为这两个模式一样。

    现在反过来看 Vue 中的依赖搜集,它更多的倾向于观察者模式,因为对于某个数据的观察,拥有该数据的对象都能清楚的感知,当数据变化后,它都要通知到各个依赖对象。

    有人认为观察者模式更多的是同步操作,而发布-订阅模式更适合异步操作(引入消息队列),但就 Vue 来说,它在派发更新过程中,也引入了队列的概念,Vue 并不是每当数据更新就立马响应,而是放入一个队列,在下一个 Tick 中再将该队列整个刷新。

    6.用发布-订阅模式实现数据绑定

    既然前面已经知道 Vue 是通过观察者模式来实现依赖收集的,那这里就用发布-订阅模式来简单实现一下数据绑定的功能!

    8d5f49cd03316bf38dc976ecabcd4ea1.png

    说干就干!

    先让我们来分析一下做这个例子的流程。

    1、首先,我们需要一个事件中心eventCenter,能够满足 DOM 节点对响应式数据变化的订阅,能够在数据变化后发布信息给 DOM 节点。

    2、其次,我们需要对所有的响应式数据进行拦截,用Object.defineProperty方法进行改写,主要是在set函数中通过事件中心发布变化。

    最后,我们需要对管理的 DOM 节点进行遍历,将{{ 响应式变量 }}替换为实际数据,并且对数据进行变更订阅。

    既然流程搞清楚了,那就开始撸代码吧!

    Coding…

    • eventCenter.js
    819b825219330918717d5da2b5e6f1cb.png
    • dataBinding.js
    754800f3cfadacf1a2dc392d9255f8dc.png
    • index.html
    c24901a1338966443abc4e99481003a6.png

    eventCenter.js就是事件处理中心,dataBinding.js就是完成数据绑定的主要逻辑操作了。

    在遍历所有 DOM 节点的时候,对于文本节点,用正则表达式判断是否引用响应式数据,如果有则进行相应的替换,对于标签节点,首先判断是不是input节点,因为需要对v-model做双向绑定操作,然后再通过递归进行子节点的筛选。

    结果演示

    b33f437547e20b18f71418709cf65742.gif

    相较于 Vue 的实际数据绑定的实现,这个 demo 肯定是极其简略甚至粗糙的(╭(╯^╰)╮)~比如说,一个标签节点中既有普通文本,又有响应式数据,或者一个标签里有多个数据绑定,这些都没处理。

    但通过这个简单的例子,加深了自己对于 Vue 数据绑定的理解还是很有意义的~

    展开全文
  • 发布-订阅模式关于Vue数据双向绑定原理的应用前言一、Vue和MVVM二、数据劫持三、数据双向绑定实现 前言 本章主要讲诉发布-订阅模式关于Vue数据双向绑定原理的应用,全程大概耗时10分钟。 一、Vue和MVVM Vue是一个...

    发布-订阅模式关于Vue数据双向绑定原理的应用

    前言

    本章主要讲诉发布-订阅模式关于Vue数据双向绑定原理的应用,全程大概耗时10分钟。

    一、Vue和MVVM

    Vue是一个MVVM模式的框架,即Model-View-ViewModel

    1. Model是数据
    2. View是视图
    3. ViewModel是View和Model之间的桥梁,负责监听View(Model)的变化,并通知Model(view)更新,如下图所示:

    在这里插入图片描述
    数据双向绑定,用兵法就说就是敌不动我不动,敌一动我跟着动。也就是说用户对View视图进行更新时,Model数据也会跟着修改;而我们修改Model数据时,View视图也一样会跟着修改。而其原理,就是利用了发布-订阅模式+数据劫持实现的。我们先来说一下什么是数据劫持?

    二、数据劫持

    数据劫持,顾名思义,就是在操作数据的时候,劫持这个操作顺便做一些我们想做的事情。
    在Vue3.0之前,数据劫持是利用了Object.defineProperty来劫持对象属性的setter和getter操作来实现的。getter就是获取某个属性,setter就是设置某个属性。看下下面的代码实例:

    var people = {
       name:'pjj'
     }
     Object.keys(people).forEach(function(key) {
       Object.defineProperty(people, key, {
         get:function(){
             console.log('获取属性时劫持触发这个console.log');
         },
         set:function(){
             console.log('设置属性时劫持触发这个console.log');
         }
       })
     })
     people.name; //控制台会打印出 “获取属性时劫持触发这个console.log”
     people.name = 'panjj'; //控制台会打印出 "设置属性时劫持触发这个console.log"
    

    在Vue3.0,数据劫持改成了由Proxy来实现
    使用Proxy代替Object.defindProprety的很大原因是因为原先的数据劫持,如果属性值为复杂类型的数据,则需要进行深度遍历,没办法直接监听。Proxy是直接监听整个对象的,简单很多,但是是ES6的语法,所以兼容性不是很好。

    let people = {
    	name: 'pjj'
    }
    let handler = {
    	get: function(target, key) {
    		console.log('获取属性时劫持触发这个console.log');
            return key in target ? target[key] : 'newKey';
        },
    	set: function(target, key, newVal) {
    		let res = Reflect.set(target, key, newVal);
    		console.log('设置属性时劫持触发这个console.log');
    		return target[key] = newVal;
    	}
    }
    let p = new Proxy(obj, handler);
    //target指的是要被Proxy包装的任意目标对象
    //handler是一个对象,这个对象的属性是一些执行行为的函数
    p.name = 'panjj';	//控制台会打印出 “获取属性时劫持触发这个console.log”
    console.log(p.age); //控制台会打印出 “设置属性时劫持触发这个console.log”
    

    从上面的两段代码,我们可以看出,其实数据劫持就是劫持了数据属性的get和set,让数据的属性在被获取get或者设置set时做出额外的操作。

    基于这一点,只要我们把这个额外操作的用途用来监听数据和更新视图,其实就可以去实现数据->视图的更新了。
    视图->数据的更新,其实可以利用一些监听事件去实现,例如像input事件就可以实现了。

    三、数据双向绑定的实现

    在这里插入图片描述
    先进行一下梳理:

    1. 实现一个Observer,用来劫持,将属性转换成访问器属性getter和setter,用来数据监听。
    2. 实现一个Watcher,Watcher是数据的观察者,观察数据的更新,并执行相应的更新函数,从而更新视图。
    3. 实现一个Dep,用来连接Observer和Watcher。每一个observer会创建一个Dep实例,在get时让每一个watcher监听数据,在set时执行watcher里面的更新回调函数。
    4. 实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

    Observer和Dep

    //用来劫持并监听所有属性,如果有变动的,就通知订阅者。
    
    function Observer(data) {
    	this.data = data;
    	this.walk(data); // 遍历data的每个属性,进行数据劫持
    }
    
    Observer.prototype = {
    	walk: function(data) {
    		var self = this;
    		//这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听
    		Object.keys(data).forEach(function(key) {
    			self.defineReactive(data, key, data[key]);
    		});
    	},
    	defineReactive: function(data, key, val) {
    		//数据劫持
    		var dep = new Dep();
    		// 递归遍历所有子属性
    		Object.defineProperty(data, key, {
    			get: function getter() {
    				if (Dep.target) {
    					// 在这里添加一个订阅者,Dep.target????
    					dep.addSub(Dep.target);
    				}
    				return val;
    			},
    			// setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(),通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。
    			set: function setter(newVal) {
    				if (newVal === val) {
    					return;
    				}
    				val = newVal;
    				// 新的值是object的话,进行监听
    				childObj = observe(newVal);
    				dep.notify();
    			}
    		});
    	}
    };
    
    // 返回实例化的对象
    function observe(value, vm) {
    	if (!value || typeof value !== 'object') {
    		return;
    	}
    	return new Observer(value);
    }
    
    // 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后再属性变化的时候执行对应订阅者的更新函数
    function Dep() {
    	this.subs = []; //存储订阅者的subs
    }
    Dep.prototype = {
    	// 添加订阅
    	addSub: function(sub) {
    		this.subs.push(sub);
    	},
    	// 通知订阅者数据变更
    	notify: function() {
    		this.subs.forEach(function(sub) {
    			sub.update();
    		});
    	}
    };
    Dep.target = null;
    
    

    Watcher

    //可以收到属性的变化通知并执行相应的函数,从而更新视图。
    
    function Watcher(vm, exp, cb) {
    	this.vm = vm; // vm 就是一个new Vue对象
    	this.exp = exp; // exp就是v-model或者v-on等等绑定的属性,例如v-modle='name',exp就是name
    	this.cb = cb; // cb就是Watcher绑定的更新函数
    	this.value = this.get(); // 这里的value也就是下面的22行的value,是为了方便下面进行update时做新旧值的对比
    }
    
    Watcher.prototype = {
    	update: function() {
    		var value = this.vm.data[this.exp]; // 新value
    		var oldVal = this.value; // 原value
    		if (value !== oldVal) {
    			// 不相等才进行更新
    			this.value = value;
    			this.cb.call(this.vm, value, oldVal); // call调用更新函数cb进行更新
    		}
    	},
    	run: function() {
    		var value = this.vm.data[this.exp];
    		var oldVal = this.value;
    		if (value !== oldVal) {
    			this.value = value;
    			this.cb.call(this.vm, value);
    		}
    	},
    	get: function() {
    		Dep.target = this; // 这里让Dep.terget指向了自己(一个watcher)
    		var value = this.vm.data[this.exp]; // 这里this.vm.data[this.exp]也就是调用了上面例子中data的name,从而触发object.dedefineProperty中的get函数,把watcher添加到订阅器中
    		Dep.target = null; // 释放自己
    		return value;
    	}
    };
    
    

    Compile

    function Compile(el, vm) {
    	this.vm = vm;
    	this.el = document.querySelector(el); // 绑定的根元素
    	this.fragment = null;
    	this.init();
    }
    
    Compile.prototype = {
    	init: function() {
    		if (this.el) {
    			this.fragment = this.nodeToFragment(this.el);
    			this.compileElement(this.fragment);
    			this.el.appendChild(this.fragment);
    		} else {
    			console.log('Dom元素不存在');
    		}
    	},
    	// 将绑定的dom元素整个添加到fragment元素
    	nodeToFragment: function(el) {
    		var fragment = document.createDocumentFragment();
    		var child = el.firstChild;
    		while (child) {
    			// 将Dom元素移入fragment中
    			fragment.appendChild(child);
    			child = el.firstChild;
    		}
    		return fragment;
    	},
    	// 解析element
    	compileElement: function(el) {
    		var childNodes = el.childNodes;
    		var self = this;
    		[].slice.call(childNodes).forEach(function(node) {
    			var reg = /\{\{(.*)\}\}/; // 通过正则来获取胡子语法{}里面的data
    			var text = node.textContent;
    			// 如果是元素节点
    			if (self.isElementNode(node)) {
    				self.compile(node);
    				// 如果是文本节点
    			} else if (self.isTextNode(node) && reg.test(text)) {
    				//第 0 个元素是与正则表达式相匹配的文本 reg.exec(text)[0] 为 '{{data}}'
    				//第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本 reg.exec(text)[1]为'data'
    				self.compileText(node, reg.exec(text)[1]);
    			}
    			if (node.childNodes && node.childNodes.length) {
    				self.compileElement(node);
    			}
    		});
    	},
    	// 如果是指令
    	compile: function(node) {
    		var nodeAttrs = node.attributes;
    		var self = this;
    		Array.prototype.forEach.call(nodeAttrs, function(attr) {
    			var attrName = attr.name;
    			if (self.isDirective(attrName)) {
    				var exp = attr.value;
    				var dir = attrName.substring(2);
    				if (self.isEventDirective(dir)) {
    					// 事件指令
    					self.compileEvent(node, self.vm, exp, dir); //绑定监听事件
    				} else {
    					// v-model 指令
    					self.compileModel(node, self.vm, exp, dir);
    				}
    				node.removeAttribute(attrName);
    			}
    		});
    	},
    	// 如果是{}
    	compileText: function(node, exp) {
    		var self = this;
    		var initText = this.vm[exp];
    		this.updateText(node, initText);
    		new Watcher(this.vm, exp, function(value) {
    			self.updateText(node, value);
    		});
    	},
    	//绑定监听事件
    	compileEvent: function(node, vm, exp, dir) {
    		var eventType = dir.split(':')[1];
    		var cb = vm.methods && vm.methods[exp];
    
    		if (eventType && cb) {
    			node.addEventListener(eventType, cb.bind(vm), false);
    		}
    	},
    	compileModel: function(node, vm, exp) {
    		var self = this;
    		var val = this.vm[exp];
    		this.modelUpdater(node, val); // 完成挂载,{{ }}中的值被渲染为data中的值
    		new Watcher(this.vm, exp, function(value) {
    			self.modelUpdater(node, value);
    		});
    
    		node.addEventListener('input', function(e) {
    			var newValue = e.target.value;
    			if (val === newValue) {
    				return;
    			}
    			self.vm[exp] = newValue;
    			val = newValue;
    		});
    	},
    	updateText: function(node, value) {
    		node.textContent = typeof value == 'undefined' ? '' : value;
    	},
    	modelUpdater: function(node, value) {
    		node.value = typeof value == 'undefined' ? '' : value;
    	},
    	isDirective: function(attr) {
    		return attr.indexOf('v-') == 0;
    	},
    	isEventDirective: function(dir) {
    		return dir.indexOf('on:') === 0;
    	},
    	isElementNode: function(node) {
    		return node.nodeType == 1;
    	},
    	isTextNode: function(node) {
    		return node.nodeType == 3;
    	}
    };
    
    
    展开全文
  • 前端EventEmitter,发布/订阅模式前言实现拓展Vue 中非父子组件组件通信通俗易懂了解Vue双向绑定原理及实现Vue双向绑定原理,教你一步一步实现双向绑定参考的代码1参考代码2`推荐`[Source Code - JavaScript - 学习...

    前言

    发布订阅模式,很多地方都用到的一种模式,简单的说就是预定一件事情,时机成熟通知你,比如我们nodejs中的fs的读写文件的流,消息事件的触发等都用到了这种方式了,虽然不是特别难,但是非常实用,实现方式也简单,基本思想就是内部保存了一个对象存储订阅的函数,调用者通过名字来触发函数,订阅多个就按照队列的形式触发。

    DOM 的事件机制就是发布订阅模式最常见的实现,这大概是前端最常用的编程模型了,监听某事件,当该事件发生时,监听该事件的监听函数被调用。

    实现

    class EventEmitter {
      constructor() {
        this.events = Object.create(null)
      }
      on(type, handler) {
        ;(this.events[type] || (this.events[type] = [])).push(handler)
      }
      off(type, handler) {
        if (this.events[type]) {
          this.events[type].splice(this.events[type].indexOf(handler) >>> 0, 1)
        }
      }
      emit(type) {
        let args = [].slice.call(arguments, 1)
        let array = this.events[type] || []
        array.forEach(cb => {
          cb.apply(this, args)
        })
      }
      once(type, handler) {
        function _fn() {
          handler.apply(this, arguments)
          this.off(type, _fn)
        }
        this.on(type, _fn)
      }
    }
    export default EventEmitter
    

    使用

    let em = new EventEmitter()
    
    function fn(price) {
      console.log('price', price)
    }
    
    em.once('work', fn)
    em.off('work', fn)
    em.emit('work', 100)
    
    console.log(em)
    
    

    拓展

    Vue 中非父子组件组件通信

    在 Vue 中不同组件之间通讯,有一种解决方案叫Event Bus,这其实就是发布订阅模式的实现,非常简单好用。
    在这里插入图片描述

    通俗易懂了解Vue双向绑定原理及实现

    Vue双向绑定原理,教你一步一步实现双向绑定

    在正式开始之前我们先来说说数据绑定的事情,数据绑定我的理解就是让数据M(model)展示到 视图V(view)上。我们常见的架构模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是采用 MVVM 模式实现双向绑定,Vue 自然也不例外。但是各个框架实现双向绑定的方法略有所不同,目前大概有三种实现方式。

    • 发布订阅模式
    • Angular 的脏查机制
    • 数据劫持

    而 Vue 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定,数据劫持主要通过 Object.defineProperty来实现。

    所谓MVVM数据双向绑定,即主要是:数据变化更新视图,视图变化更新数据.

    实现Vue的数据双向绑定,需要如下:

    • Observer 监听器:用来监听属性的变化通知订阅者
    • Watcher 订阅者:收到属性的变化,然后更新视图
    • Dep 订阅器: 负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。
    • Compile 解析器:解析指令,初始化模版,绑定订阅者

    总结:

    实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
    在这里插入图片描述
    在这里插入图片描述

    参考的代码1

    这个有问题,没有传参数, 需要自行修改一下

    // 发布订阅模式
    class EventEmitter {
        constructor() {
            // 事件对象,存放订阅的名字和事件
            this.events = {};
        }
        // 订阅事件的方法
        on(eventName,callback) {
           if (!this.events[eventName]) {
               // 注意时数据,一个名字可以订阅多个事件函数
               this.events[eventName] = [callback]
           } else  {
              // 存在则push到指定数组的尾部保存
               this.events[eventName].push(callback)
           }
        }
        // 触发事件的方法
        emit(eventName) {
            // 遍历执行所有订阅的事件
           this.events[eventName] && this.events[eventName].forEach(cb => cb());
        }
        // 移除订阅事件
        removeListener(eventName, callback) {
            if (this.events[eventName]) {
                this.events[eventName] = this.events[eventName].filter(cb => cb != callback)
            }
        }
        // 只执行一次订阅的事件,然后移除
        once(eventName,callback) {
            // 绑定的时fn, 执行的时候会触发fn函数
            let fn = () => {
               callback(); // fn函数中调用原有的callback
               this.removeListener(eventName,fn); // 删除fn, 再次执行的时候之后执行一次
            }
            this.on(eventName,fn)
        }
    }
    

    使用方式

    let em = new EventEmitter();
    let workday = 0;
    em.on("work", function() {
        workday++;
        console.log("work everyday");
    });
    
    em.once("love", function() {
        console.log("just love you");
    });
    
    function makeMoney() {
        console.log("make one million money");
    }
    em.on("money",makeMoney)let time = setInterval(() => {
        em.emit("work");
        em.removeListener("money",makeMoney);
        em.emit("money");
        em.emit("love");
        if (workday === 5) {
            console.log("have a rest")
            clearInterval(time);
        }
    }, 1);
    

    参考代码2推荐

    推荐看这个
    在这里插入图片描述

    Source Code - JavaScript - 学习优雅的编码

      // source code
      all = all || Object.create(null);
    

    Object.create(null):生成的对象是一个原型为空的对象。节约内存且避免冲突,因为没有原型,且普通对象原型上的属性和方法也相应没有了。

      // source code
      (all[type] || (all[type] = [])).push(handler);
      
      // my code - bad
      if (all[type]) {
        all[type].push(handler)
      } else {
        all[type] = [handler]
      }
    

    简洁的队列赋值:短路逻辑判断 + 初始化 + 更新数组,简直不要太优雅。

      // source code
      all[type].splice(all[type].indexOf(handler) >>> 0, 1);
    
    • 按位操作符:1 >>> 0 = 1, -1 >>> 0 = 4294967295, 详情MDN
    • 补充:按位操作符~,可以结合.indexOf()使用,因为对任一数值 x 进行按位非操作的结果为 -(x + 1),即:~-1 = 0
      // source code
      (all[type] || []).slice().map((handler) => { handler(evt); });
      (all['*'] || []).slice().map((handler) => { handler(type, evt); });
    
      // source code
      !!(0)             // false
      !!(null)          // false
      !!('')            // false
      !!(undefined)     // false
      !!(NaN)           // false
      
      !!(2)             // true
    
    • !!: 强制转换成 boolean 类型,相当于 !(!val)。如果 val = 0/null/""/undefined/NaN 时,!!(val) = false,如果 val 是其他值,!!(val) = true
    • +: +val将字符串数字转为数字。如果 val 是非字符串数字,则 +val = NaN
      // source code
      +'123456'        // 123456, Number
      +new Date()      // 1527684413484, 相当于 new Date().getTime()
    

    参考

    TypeScript/理解Event Emitter (事件派发器)推荐
    Understanding Event Emitters
    上面案例的github代码
    一个例子 - 看尽并手写JS发布订阅模式
    前端必懂EventEmitter,不懂会丢人

    展开全文
  • 看代码 var inp = document.querySelector('input');...//发布订阅对象 var obj = {}; //发布订阅事件列表 obj.list = {}; //定义订阅事件函数 obj.listen = function(eventName,fn){ obj.list[event...

    看代码

    var inp = document.querySelector('input');
    var p = document.querySelector('p');
    
    //发布订阅对象
    var obj = {};
    //发布订阅事件列表
    obj.list = {};
    //定义订阅事件函数
    obj.listen = function(eventName,fn){
      obj.list[eventName] = fn;
    }
    //定义发布事件函数
    obj.trigger = function(eventName){
      obj.list[eventName]();
    }
    
    //数据
    var data = {str:'hello'}
    var str = data.str;
    //观察数据变动从而触发所订阅事件
    Object.defineProperty(data, 'str', {
      get(){
        return str;
      },
      set(newVal){
        str = newVal;
        console.log(data.str);
        obj.trigger('go')
      }
    })
    //订阅一个事件
    obj.listen('go',function(){
      p.innerText = data.str;
      inp.value = data.str;
    })
    
    //数据变动
    inp.oninput = function(){
      data.str = this.value;
    }
    
    setInterval(function(){
      data.str+='a';
    },1000)

     

    展开全文
  • 1.背景 平时使用过 Vue 这个前端框架的同学,对于数据绑定这个词肯定不会陌生,进一步,它与 react 有点不同的是它还有一个双向数据绑定 v-model。 数据绑定的方式能够极大程度上方便我们开发,不用去进行繁琐的 DOM...
  • Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式实现双向数据绑定的。当把一个普通 Js 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,通过用Object.defineProperty ...
  • 参考vue.js实现双向绑定的方法理解双向绑定原理(:Object.defineProperty和发布-订阅模式) 12 前端MVVM原理--参考vue.js实现 13 14 <li>Objdect.defineProperty实现属性劫持 15 实现一个Observer,能够...
  • 文章目录前言参考资料初级版本实现publisher实现消息订阅中心实现Subscriber实现绑定函数完整代码进阶引入compile,并封装成MVVMES6 class语法版本结语 前言 单向绑定非常简单,就是把Model绑定到View,当我们用...
  • Vue双向绑定(数据劫持,发布订阅模式,diff算法...首先想实现双向绑定,肯定要先知道数据的变化,这时候应该怎么做?就是使用一个方法进行数据劫持。这个方法就是Object.defineProperty()。 介绍一下这个方法: O...
  • JS实现双向绑定

    2019-03-06 13:02:54
    现在前端MVVM思想盛行,通过对数据的改变从而引起页面的变化,如:Vue中实现双向绑定的原理就是:采用数据劫持结合发布者-订阅模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据...
  • 几种实现双向绑定的做法 目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view...
  • vue实现双向绑定原理

    2018-04-11 16:19:20
    vue主要是借助对象的访问器属性(Object.defineProperty)劫持数据,并结合订阅者-发布模式实现数据双向绑定。 通过Object.defineProperty把data中的各数据属性改为访问器属性,来劫持每个属性的setter、getter...
  • 众所周知,v-model是vue中双向绑定的语法糖,使用起来非常方便,面试中问到双向绑定的原理,都知道是数据劫持(Object.defineProperty)和订阅发布模式结合实现,更有甚者让你手动实现双向绑定,鄙人有幸被怼,和大家分享一下...
  • Vue实现数据双向绑定的原理是基于数据劫持结合发布-订阅模式实现的,通过Object.defineProperty()来劫持各个属性,并在数据变动时发布消息给订阅者,触发相应的监听回调,更新视图
  • 一、几种实现双向绑定的做法 目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 ...
  • vue的双向绑定是由数据劫持结合发布者-订阅模式实现的,就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情 ...
  • 几种实现双向绑定的做法 目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view...
  • 几个例子中尝试使用了下面的方式实现双向绑定发布/订阅模式 属性劫持 脏数据检测 发布/订阅模式 实现数据双向绑定最直接的方式就是使用PubSub模式: 当model发生改变的时候,触发Model change事件,然后通过...
  • 1、如何实现双向绑定? 以用户提交表单为例,其原理是我们对input进行value的属性绑定(v-bind:value="…"),将Model中的变量绑定到View上(Model -> View),以及当用户对input进行操作时,进行事件监听(v-on: ...
  • 其实写双向绑定的文章也挺多了,不过都没有仔细讲发布者-订阅模式在其中的使用,本文尽量讲解清楚所有代码,代码结构、函数等完全按照vue@2.5.17源码思路实现(除了新建vue类以及初始化的时候)。github地址在这里...
  • vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现 数据劫持 指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果 典型的有 1.Object.defineProperty() 2.es6中...
  • 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据。 关键点在于data如何更新view,因为view更新data其实可以通过事件监听即可。我们着重来分析,当数据改变,如何更新视图的。 如何知道数据变了,...
  • 一、实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向数据绑定的做法大致有如下三种: 1.发布者-订阅模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的...
  • 一句话总结:vue中的双向绑定主要是通过发布者-订阅模式实现发布 订阅 1、单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:model--->view(model更新view) 以往的MVC模式是单向绑定...
  • Vue的双向数据绑定的底层原理其实是利用原生Js中的Object.defineProperty(对象,“属性名称”,对象) 来通过数据劫持结合发布者-订阅模式的方式来实现的 var data = {}; var temp = {}; //第三方变量 ...
  • 之前也了解过vue是通过数据劫持+订阅发布模式实现MVVM的双向绑定的,但一直没仔细研究,这次深入学习了一下,借此机会分享给大家。 首先先将流程图给大家看一下 参考文章:Vue.js双向绑定实现原理 我虽然参考...
  • vue实现数据双向绑定

    2020-05-13 20:08:09
    vue实现数据双向绑定主要是采用数据劫持结合发布者-订阅模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。其实就是通过Obeject....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 558
精华内容 223
关键字:

发布订阅模式实现双向绑定