Prinsip pengikatan dua hala dalam vuejs: menggunakan mod rampasan data dan penerbitan-langganan, rampasan penetap dan getter setiap harta melalui "Object.defineProperty()", menerbitkan mesej kepada pelanggan apabila data berubah , mencetuskan Panggilan balik pendengaran yang sepadan digunakan untuk mengemas kini paparan.
Persekitaran pengendalian tutorial ini: sistem Windows 7, vue versi 2.9.6, komputer DELL G3.
Kegunaan utama Vue untuk melaksanakan pengikatan data dua hala ialah: mod rampasan data dan terbitkan-langgan, menggunakan kaedah Object.defineProperty()
untuk merampas data , dan kemudian memberitahu keluaran Pemerhati (objek tema) memberitahu semua pemerhati Selepas pemerhati menerima pemberitahuan, ia akan mengemas kini paparan.
https://jsrun.net/RMIKp/embedded/all/light
Rangka kerja MVVM terutamanya merangkumi dua aspek, perubahan data mengemas kini paparan dan perubahan paparan mengemas kini data.
Lihat perubahan data kemas kini Jika ia adalah teg seperti input, anda boleh menggunakan acara oninput..
Perubahan data mengemas kini paparan Anda boleh menggunakan kaedah set Object.definProperty()
untuk mengesan perubahan data Apabila Perubahan data mencetuskan fungsi ini dan mengemas kini paparan.
Kami tahu cara melaksanakan pengikatan dua hala Pertama, kami perlu merampas dan memantau data, jadi kami perlu menyediakan fungsi Pemerhati untuk memantau perubahan dalam semua sifat. .
Jika atribut berubah, anda perlu memberitahu pemerhati pelanggan untuk melihat sama ada data perlu dikemas kini Jika terdapat berbilang pelanggan, anda memerlukan Dep untuk mengumpulkan pelanggan ini, dan kemudian antara pemerhati pendengar dan pengurusan bersatu pemerhati.
Penghurai arahan, menyusun, juga diperlukan untuk mengimbas dan menghuraikan nod dan atribut yang perlu dipantau.
Jadi, prosesnya lebih kurang seperti ini:
Laksanakan Pemerhati pendengar untuk merampas dan memantau semua sifat, dan memaklumkan pelanggan jika terdapat perubahan.
Laksanakan Pemerhati pelanggan Apabila menerima pemberitahuan perubahan harta benda, laksanakan fungsi yang sepadan, kemudian kemas kini paparan dan gunakan Dep untuk mengumpul Pemerhati ini.
Melaksanakan Parser Compile, yang digunakan untuk mengimbas dan menghuraikan arahan berkaitan nod dan memulakan pelanggan yang sepadan mengikut templat permulaan.
Pemerhati ialah pendengar data Kaedah teras ialah menggunakan Object.defineProperty()
untuk menambah setter dan secara rekursif kaedah getter kepada sifat untuk pemantauan.
var library = { book1: { name: "", }, book2: "", }; observe(library); library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南” library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍” // 为数据添加检测 function defineReactive(data, key, val) { observe(val); // 递归遍历所有子属性 let dep = new Dep(); // 新建一个dep Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) { // 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数 dep.addSub(Dep.target); // 添加一个订阅者 } return val; }, set: function(newVal) { if (val == newVal) return; // 如果值未发生改变就return val = newVal; console.log( "属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”" ); dep.notify(); // 如果数据发生变化,就通知所有的订阅者。 }, }); } // 监听对象的所有属性 function observe(data) { if (!data || typeof data !== "object") { return; // 如果不是对象就return } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); } // Dep 负责收集订阅者,当属性发生变化时,触发更新函数。 function Dep() { this.subs = {}; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach((sub) => sub.update()); }, };
Dalam analisis idea, perlu ada Dep pelanggan mesej pelanggan yang boleh digunakan untuk mengumpul pelanggan dan melaksanakan fungsi kemas kini yang sepadan apabila atribut berubah.
Dari sudut pandangan kod, menambahkan Dep pelanggan pada pengambil adalah untuk mencetuskan Pemerhati apabila ia dimulakan oleh itu, adalah perlu untuk menentukan sama ada pelanggan diperlukan.
Dalam setter, jika ada perubahan data, semua pelanggan akan dimaklumkan, dan kemudian pelanggan akan mengemas kini fungsi yang sepadan.
Pada ketika ini, Pemerhati yang agak lengkap selesai Seterusnya, mula mereka bentuk Pemerhati.
Pemerhati pelanggan perlu menambah dirinya semasa pemulaan. subscriber Dep, kami sudah tahu bahawa Observer ialah operasi Watcher yang dilakukan apabila mendapat, jadi kami hanya perlu mencetuskan fungsi get yang sepadan apabila Watcher dimulakan untuk menambah operasi subscriber yang sepadan.
Bagaimana untuk mencetuskan get? Oleh kerana kita telah menetapkan Object.defineProperty(), kita hanya perlu mendapatkan nilai harta yang sepadan untuk mencetuskannya.
Kami hanya perlu menyimpan cache pelanggan pada Dep.target apabila Pemerhati pelanggan dimulakan dan mengalih keluarnya selepas penambahan itu berjaya.
function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 将自己添加到订阅器的操作 } Watcher.prototype = { update: function() { this.run(); }, 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, oldVal); } }, get: function() { Dep.target = this; // 缓存自己,用于判断是否添加watcher。 var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; }, };
Setakat ini, reka bentuk Pemerhati yang ringkas telah siap, dan kemudian dengan mengaitkan Pemerhati dan Pemerhati, pengikatan dua hala yang mudah boleh dicapai.
Oleh kerana parser Compile belum direka bentuk lagi, data templat boleh dikodkan dengan keras terlebih dahulu.
Tukar kod ke dalam penulisan pembina ES6 dan cuba pratontonnya.
https://jsrun.net/8SIKp/embedded/all/light
Kod ini tidak melaksanakan pengkompil tetapi terus dalam pembolehubah terikat Kami hanya Tetapkan data (nama) pada nod untuk mengikat, dan kemudian melaksanakan MyVue baharu pada halaman untuk mencapai pengikatan dua hala.
dan buat perubahan selepas dua saat anda dapat melihat bahawa halaman itu juga telah berubah.
// MyVue proxyKeys(key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); }
Fungsi kod di atas adalah untuk memproksi kekunci this.data kepada ini, supaya saya boleh menggunakan this.xx dengan mudah untuk mendapatkan this.data.xx.
Walaupun pengikatan data dua hala dilaksanakan di atas, keseluruhan proses tidak menghuraikan stor bahagian DOM, tetapi menggantikannya dengan tetap, jadi seterusnya kita perlu melaksanakan parser untuk melakukannya kerja penghuraian dan pengikatan data.
Langkah pelaksanaan penyusunan penghurai:
Menghuraikan arahan templat, menggantikan data templat dan mulakan paparan.
将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。
为了解析模板,首先需要解析 DOM 数据,然后对含有 DOM 元素上的对应指令进行处理,因此整个 DOM 操作较为频繁,可以新建一个 fragment 片段,将需要的解析的 DOM 存入 fragment 片段中在进行处理。
function nodeToFragment(el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild; } return fragment; }
接下来需要遍历各个节点,对含有相关指令和模板语法的节点进行特殊处理,先进行最简单模板语法处理,使用正则解析“{{变量}}”这种形式的语法。
function compileElement (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; // 匹配{{xx}} var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } }); }, function compileText (node, exp) { var self = this; var initText = this.vm[exp]; updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); }); }, function updateText (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }
获取到最外层的节点后,调用 compileElement 函数,对所有的子节点进行判断,如果节点是文本节点切匹配{{}}这种形式的指令,则进行编译处理,初始化对应的参数。
然后需要对当前参数生成一个对应的更新函数订阅器,在数据发生变化时更新对应的 DOM。
这样就完成了解析、初始化、编译三个过程了。
接下来改造一个 myVue 就可以使用模板变量进行双向数据绑定了。
https://jsrun.net/K4IKp/embedded/all/light
添加完 compile 之后,一个数据双向绑定就基本完成了,接下来就是在 Compile 中添加更多指令的解析编译,比如 v-model、v-on、v-bind 等。
添加一个 v-model 和 v-on 解析:
function compile(node) { var nodeAttrs = node.attributes; var self = this; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); if (isEventDirective(dir)) { // 事件指令 self.compileEvent(node, self.vm, exp, dir); } else { // v-model 指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); // 解析完毕,移除属性 } }); } // v-指令解析 function isDirective(attr) { return attr.indexOf("v-") == 0; } // on: 指令解析 function isEventDirective(dir) { return dir.indexOf("on:") === 0; }
上面的 compile 函数是用于遍历当前 dom 的所有节点属性,然后判断属性是否是指令属性,如果是在做对应的处理(事件就去监听事件、数据就去监听数据..)
在 MyVue 中添加 mounted 方法,在所有操作都做完时执行。
class MyVue { constructor(options) { var self = this; this.data = options.data; this.methods = options.methods; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options.el, this); options.mounted.call(this); // 所有事情处理好后执行mounted函数 } proxyKeys(key) { // 将this.data属性代理到this上 var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function getter() { return self.data[key]; }, set: function setter(newVal) { self.data[key] = newVal; }, }); } }
然后就可以测试使用了。
https://jsrun.net/Y4IKp/embedded/all/light
总结一下流程,回头在哪看一遍这个图,是不是清楚很多了。
可以查看的代码地址:Vue2.x 的双向绑定原理及实现
相关推荐:《vue.js教程》
Atas ialah kandungan terperinci Apakah prinsip pengikatan dua hala dalam vuejs. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!