前言: 本系列学习笔记从以下几个点展开
什么是双向数据绑定
双向数据绑定的好处
怎么实现双向数据绑定
实现双向数据数据绑定需要哪些知识点
数据劫持
发布订阅模式
先看看我们要实现的目标是什么, 如下动图:
0, 什么是双向数据绑定
单向数据绑定: 把 Model 绑定到 View 上, 当我们用 JS 修改模型 Model 时候, 视图 View 上对应的内容也会改动, 这就是 数据动, 页面动 .
双向数据绑定: 简言之 数据动 页面动, 页面动, 数据动, 典型的应用就是在做表单时候, 输入框的内容改动后, 跟该输入框的 value 的值改动.
看 vue 官网上的这个 V-model 的演示案例:
1, 双向数据绑定的好处
要说出这个好处的时候, 也只有在实际场景中才能对应的显示出来. 比如我们需要实时显示数据, 我们一边说话, 一边实时显示我们说的话的文字内容, 等等. 这让我想起了去年参加云栖大会, 台上的大佬一边说话, 下面的字幕实时更新.(当然实现这个技术有很多技术点, 我们不讨论这个内容, 小编也才疏学浅, 搞不懂)
以上的都是废话, 我们直接看看怎么实现这个双向数据绑定.
一, 实现原理
Vue 实现双向数据绑定的原理: 数据劫持 + 发布订阅模式(有的也称为观察者模式)
数据劫持的核心技术: Object.defineProperty()
**vue 3.0 已经用的不是这个技术了, 采用是 原生的 Proxy, 据说速度能够提升 100%, 截张尤大的 ppt,** 2018-11-21 修改本篇笔记
(香菇, 刚研究会一点, 就立马变了, 这就是前端世界),Proxy 的方式将会在本系列笔记结束后, 再记录这个技术点的使用
二, 数据劫持的方法 Object.defineProperty()
先上一个参照代码, 它长这个样子:
- var book = {
- _year: 2004,
- edition: 1
- };
- Object.defineProperty(book, "year", {
- get: function(){
- console.log('访问 year 了, 返回_year')
- return this._year;
- },
- set: function(newValue){
- if (newValue> 2004) {
- this._year = newValue;
- console.log('重新设置_year 了, 并返回 edition')
- this.edition += newValue - 2004;
- }
- }
- });
- book.year = 2005;
- alert(book.edition); //2
--- 摘自 JavaScript 高级程序设计
Object.defineProperty() 的具体介绍, 我们本文不做具体展开, 查看我这里的一篇文章, Object.defineProperty. 我们这里先要知道这么一个事情. 这个方法要传入三个参数, 传入的数据对象 data, 属性 key, 描述符对象. 其中, 描述符 (descriptor) 对象的属 性必须是: configurable,enumerable,writable 和 value. 设置其中的一或多个值, 可以修改 对应的特性值. 我们需要用的是这访问器属性. 当我们在读取访问器属性时, 会调用 getter 函数, 这个函数负责返回有效的值; 在写入访问器属性时, 会调用 setter 函数并传入新值, 这个函数负责决定如何处理数据. 上文代码上的 get 方法, 在读取属性时调用的函数, set 方法, 在写入属性时调用的函数.
三, 发布订阅者模式
我画了一个图, 来理解这个模式, 如下图:
代码解释:
- // 下面封装一个单例模式, 内容是发布订阅模式
- let event = {
- eventList: [],
- listener: function (key, fn) {
- if (!this.eventList[key]) { // 没有订阅过此类消息, 创建一个缓存列表
- this.eventList[key] = [];
- }
- this.eventList[key].push(fn)
- },
- trigger: function () {
- let key = Array.prototype.shift.call(arguments); // marry
- let fns = this.eventList[key];
- if (!fns || fns.length == 0) { // 没有订阅 则返回
- return false;
- }
- for (let i = 0, fn; fn = fns[i++];) {
- fn.apply(this, arguments)
- // 调用 event.listen 里面的 fn 方法, 通过 apply 将当前执行的对象指向当前的 this,arguments 传进 fn 函数
- }
- },
- remove: function (key, fn) { // 取消订阅
- let fns = this.eventList[key];
- if (!fns) {
- return false;
- }
- if(!fn) {
- fns && (fns.length = 0)
- } else {
- for (let l = fns.length-1; l>=0; l--) {
- let _fn = fns[l];
- if( _fn === fn) {
- fns.splice(l, 1)
- }
- }
- }
- },
- install: function (obj) {
- for (let i in this) {
- if (i === 'install') {
- return false
- }
- obj[i] = this[i];
- }
- }
- }
- let testMsg = {}
- event.install(testMsg)
- // 上面方法 就会将 event 的方法 浅拷贝给 testMsg, 这样 testMsg 就有 event 的方法和属性
- testMsg.listener('rich', fn1 = (name) => {
- console.log(`${name}知道你有钱了 `)
- })
- testMsg.listener('borrowMoney', fn2 = (name) => {
- console.log(`${name}想问你借钱 `)
- })
- // listen 方法将事件 放进队列
- // testMsg.remove('rich', fn1) // 取消订阅
- // trigger 方法, 处理事件队列的方法, 调用 listen 的函数的里面的回调函数 fn
- testMsg.trigger('rich', '张三')
- testMsg.trigger('rich', '张三 2')
- testMsg.trigger('borrowMoney', '李四')
- testMsg.trigger('borrowMoney', '李四 2')
- // 代码总结:
- // 订阅的事件具有对应的 key
- // 通过 listener 方法, 将具体的事件队列保存到 eventList , 可以理解为缓存列表也可以是事件队列;
- // 执行 trigger 方法, 将事件队列拿出来执行调用
- // remove 方法根据对应的 key 值, 删除对应的订阅事件
- // 模式总结: 封装一个单例 event, 执行 installEvent 方法, 将想要 event 对象拷贝到某个对象中去,
- // 发布者 trigger 方法, 监听者 listen 方法 ,trigger 发布一个东西, listen 立马知道你要发布的东西
- View Code
四, 实现分解
主函数入口
- function Myvue (options) {
- this.$options = options
- this.$el = document.querySelector(options.el);
- this.$data = options.data;
- Object.keys(this.$data).forEach(key => {
- this.$prop = key;
- })
- this.init()
- }
- Myvue.prototype.init = function () {
- // 监听数据变化
- observer(this.$data);
- // 获得值
- // let value = this.$data[this.$prop];
- // 不经过模板编译直接 通知订阅者更新 dom
- // new Watcher(this,this.$prop,value => {
- // console.log(`watcher ${this.$prop}的改动, 要有动静了 `)
- // this.$el.textContent = value
- // })
- // 通知模板编译来执行页面上模板变量替换
- new Compile(this)
- }
主函数调用
- <script>
- const vm = new Myvue({
- el: "#app",
- data: {
- name: "vue 双向数据绑定 test1"
- }
- });
- </script>
监听器
订阅者
模板编译器
未完待续, 错误之处, 敬请指出, 共同进步!
下一篇 vue 双向数据绑定的实现学习(二)- 监听器的实现
文章参考:
- https://www.cnblogs.com/beevesnoodles/p/9844854.html
- https://github.com/youngwind/blog/issues/87
- https://juejin.im/post/5a9108b6f265da4e7527b1a4
后记: 代码只做基本实现, 不做代码健壮性处理, 一些错误处理已经忽略
来源: https://www.cnblogs.com/adouwt/p/9928278.html