Artikel ini menyesuaikan Vue dan secara beransur-ansur melaksanakan pengikatan data dua hala. Ia akan memberi anda pemahaman langkah demi langkah tentang prinsip pengikatan dua hala Vue melalui contoh.
vue memerlukan sekurang-kurangnya dua parameter: templat dan data. [Pengesyoran berkaitan: tutorial video vue.js]
Buat objek Pengkompil, berikan data pada templat dan lekapkannya pada nod yang ditentukan.
class MyVue { // 1,接收两个参数:模板(根节点),和数据对象 constructor(options) { // 保存模板,和数据对象 if (this.isElement(options.el)) { this.$el = options.el; } else { this.$el = document.querySelector(options.el); } this.$data = options.data; // 2.根据模板和数据对象,渲染到根节点 if (this.$el) { // 监听data所有属性的get/set new Observer(this.$data); new Compiler(this) } } // 判断是否是一个dom元素 isElement(node) { return node.nodeType === 1; } }
Penyusun
1 Fungsi node2fragment mengekstrak elemen templat ke dalam memori, supaya selepas memaparkan data ke dalam templat, ia boleh dilekapkan ke halaman sekaligus
2 ke dalam memori, gunakan fungsi buildTemplate untuk melintasi Elemen templat
Nod elemen
Nod teks
3. Cipta kelas CompilerUtil untuk memproses Arahan vue dan {{}}, lengkapkan pemaparan data
4. Ini melengkapkan pemaparan data pertama untuk mengemas kini paparan secara automatik apabila data berubah.
class Compiler { constructor(vm) { this.vm = vm; // 1.将网页上的元素放到内存中 let fragment = this.node2fragment(this.vm.$el); // 2.利用指定的数据编译内存中的元素 this.buildTemplate(fragment); // 3.将编译好的内容重新渲染会网页上 this.vm.$el.appendChild(fragment); } node2fragment(app) { // 1.创建一个空的文档碎片对象 let fragment = document.createDocumentFragment(); // 2.编译循环取到每一个元素 let node = app.firstChild; while (node) { // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失 fragment.appendChild(node); node = app.firstChild; } // 3.返回存储了所有元素的文档碎片对象 return fragment; } buildTemplate(fragment) { let nodeList = [...fragment.childNodes]; nodeList.forEach(node => { // 需要判断当前遍历到的节点是一个元素还是一个文本 if (this.vm.isElement(node)) { // 元素节点 this.buildElement(node); // 处理子元素 this.buildTemplate(node); } else { // 文本节点 this.buildText(node); } }) } buildElement(node) { let attrs = [...node.attributes]; attrs.forEach(attr => { // v-model="name" => {name:v-model value:name} let { name, value } = attr; // v-model / v-html / v-text / v-xxx if (name.startsWith('v-')) { // v-model -> [v, model] let [_, directive] = name.split('-'); CompilerUtil[directive](node, value, this.vm); } }) } buildText(node) { let content = node.textContent; let reg = /\{\{.+?\}\}/gi; if (reg.test(content)) { CompilerUtil['content'](node, content, this.vm); } } }
let CompilerUtil = { getValue(vm, value) { // 解析this.data.aaa.bbb.ccc这种属性 return value.split('.').reduce((data, currentKey) => { return data[currentKey.trim()]; }, vm.$data); }, getContent(vm, value) { // 解析{{}}中的变量 let reg = /\{\{(.+?)\}\}/gi; let val = value.replace(reg, (...args) => { return this.getValue(vm, args[1]); }); return val; }, // 解析v-model指令 model: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.value = newValue; }); let val = this.getValue(vm, value); node.value = val; }, // 解析v-html指令 html: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.innerHTML = newValue; }); let val = this.getValue(vm, value); node.innerHTML = val; }, // 解析v-text指令 text: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.innerText = newValue; }); let val = this.getValue(vm, value); node.innerText = val; }, // 解析{{}}中的变量 content: function (node, value, vm) { let reg = /\{\{(.+?)\}\}/gi; let val = value.replace(reg, (...args) => { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, args[1], (newValue, oldValue) => { node.textContent = this.getContent(vm, value); }); return this.getValue(vm, args[1]); }); node.textContent = val; } }
Pemerhati
1, gunakan fungsi defineRecative untuk membuat Objek pada data .defineProperty pemprosesan, supaya setiap data dalam data boleh dipantau dengan mendapatkan/set
Seterusnya, kami akan mempertimbangkan bagaimana untuk mengemas kini kandungan paparan selepas memantau perubahan nilai data? Menggunakan corak reka bentuk Pemerhati, buat kelas Dep dan Air.
class Observer { constructor(data) { this.observer(data); } observer(obj) { if (obj && typeof obj === 'object') { // 遍历取出传入对象的所有属性, 给遍历到的属性都增加get/set方法 for (let key in obj) { this.defineRecative(obj, key, obj[key]) } } } // obj: 需要操作的对象 // attr: 需要新增get/set方法的属性 // value: 需要新增get/set方法属性的取值 defineRecative(obj, attr, value) { // 如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法 this.observer(value); // 第三步: 将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来 let dep = new Dep(); // 创建了属于当前属性的发布订阅对象 Object.defineProperty(obj, attr, { get() { // 在这里收集依赖 Dep.target && dep.addSub(Dep.target); return value; }, set: (newValue) => { if (value !== newValue) { // 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法 this.observer(newValue); value = newValue; dep.notify(); console.log('监听到数据的变化'); } } }) } }
Gunakan corak reka bentuk pemerhati untuk mencipta kelas Dep dan Air
1 :
Menghuraikan templat dan mengumpulkan set nod DOM yang data tertentu dalam data digunakan dalam templat Apabila data berubah, mengemas kini set nod DOM merealisasikan kemas kini data .
Dep: digunakan untuk mengumpul set nod dom yang bergantung pada atribut data tertentu dan menyediakan kaedah kemas kini
Pemerhati: untuk setiap dom node Package object
2. Pada ketika ini, saya rasa idea itu OK dan saya sudah yakin untuk menang. Jadi bagaimana untuk menggunakan Dep dan Watcher?
Tambahkan dep untuk setiap atribut untuk mengumpul dom bergantung
Kerana data data akan dibaca apabila halaman pertama dipaparkan Di kali ini, pengambil data akan dicetuskan, jadi kumpulkan dom
di sini bagaimana untuk mengumpulnya secara khusus apabila kelas CompilerUtil menghuraikan v-model, {{}} dan lain-lain arahan, pengambil akan dicetuskan , kami mencipta Air sebelum mencetuskan, menambah atribut statik pada Pemerhati, menunjuk ke dom, kemudian mendapatkan pembolehubah statik dalam fungsi pengambil, dan menambahnya pada kebergantungan, dengan itu melengkapkan koleksi. Oleh kerana pembolehubah statik diberikan nilai setiap kali pengambil dicetuskan, tidak ada kes pengumpulan kebergantungan yang salah.
class Dep { constructor() { // 这个数组就是专门用于管理某个属性所有的观察者对象的 this.subs = []; } // 订阅观察的方法 addSub(watcher) { this.subs.push(watcher); } // 发布订阅的方法 notify() { this.subs.forEach(watcher => watcher.update()); } }
class Watcher { constructor(vm, attr, cb) { this.vm = vm; this.attr = attr; this.cb = cb; // 在创建观察者对象的时候就去获取当前的旧值 this.oldValue = this.getOldValue(); } getOldValue() { Dep.target = this; let oldValue = CompilerUtil.getValue(this.vm, this.attr); Dep.target = null; return oldValue; } // 定义一个更新的方法, 用于判断新值和旧值是否相同 update() { let newValue = CompilerUtil.getValue(this.vm, this.attr); if (this.oldValue !== newValue) { this.cb(newValue, this.oldValue); } } }
3 Pada ketika ini, paparan dikemas kini secara automatik apabila data terikat pada asalnya saya mahu melaksanakannya secara langkah demi langkah melalui kod, tetapi sukar untuk mengendalikan, jadi saya hanya Kelas lengkap disiarkan.
sebenarnya memantau input dan peristiwa perubahan kotak input. Ubah suai kaedah model CompilerUtil. Kod khusus adalah seperti berikut
model: function (node, value, vm) { new Watcher(vm, value, (newValue, oldValue)=>{ node.value = newValue; }); let val = this.getValue(vm, value); node.value = val; // 看这里 node.addEventListener('input', (e)=>{ let newValue = e.target.value; this.setValue(vm, value, newValue); }) },
Prinsip pengikatan dua hala Vue
vue menerima templat dan parameter data. 1. Mula-mula, melintasi data secara rekursif dalam data, jalankan Object.defineProperty pada setiap sifat, dan tentukan fungsi dapatkan dan tetapkan. Dan tambahkan tatasusunan dep untuk setiap harta. Apabila get dilaksanakan, pemerhati akan dibuat untuk nod DOM yang dipanggil dan disimpan dalam tatasusunan. Apabila ditetapkan dilaksanakan, nilai ditetapkan semula dan kaedah pemberitahuan tatasusunan dep dipanggil untuk memberitahu semua pemerhati yang menggunakan atribut ini dan mengemas kini kandungan dom yang sepadan. 2. Muatkan templat ke dalam ingatan, ulangi elemen dalam templat, dan kesan bahawa elemen tersebut mempunyai arahan bermula dengan v- atau arahan pendakap berganda, dan nilai yang sepadan akan diambil daripada data untuk mengubah suai kandungan templat kali ini, kandungan templat akan diubah suai Elemen dom ditambahkan pada tatasusunan dep atribut. Ini melaksanakan paparan dipacu data. Apabila memproses arahan model v, tambahkan peristiwa input (atau tukar) pada dom, dan ubah suai nilai atribut yang sepadan apabila dimasukkan, merealisasikan data dipacu halaman. 3. Selepas mengikat templat pada data, tambahkan templat pada pokok DOM sebenar.
Bagaimana untuk meletakkan pemerhati dalam tatasusunan dep?
Apabila menghuraikan templat, nilai atribut data yang sepadan akan diperolehi mengikut arahan v Pada masa ini, kaedah get atribut akan dipanggil dahulu dapatkan nilai atribut di dalamnya , disimpan di dalam pemerhati sebagai nilai lama Sebelum mendapatkan nilai, kami menambah atribut Watcher.target = ini pada objek prototaip Watcher, yang akan menjadi Watcher.target = null; supaya apabila get dipanggil, Dapatkan objek instance watcher mengikut Watcher.target.
Prinsip kaedah
Apabila membuat contoh vue, terima parameter kaedah
Temui v-on semasa menghuraikan arahan templat . Pendengar untuk acara yang sepadan akan ditambahkan pada elemen dom dan kaedah panggilan akan digunakan untuk mengikat vue kepada kaedah ini: vm.$methods[value].call(vm, e);
Prinsip pengiraan
Apabila membuat contoh vue, terima parameter yang dikira
Apabila memulakan tika vue, lakukan pemprosesan Object.defineProperty untuk kunci yang dikira dan tambahkan atribut get.
(Belajar perkongsian video: bahagian hadapan web)
Atas ialah kandungan terperinci Artikel ini akan memberi anda analisis mendalam tentang prinsip pengikatan dua hala Vue (memahaminya dengan teliti). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!