詳解如何使用Object.defineProperty實現(xiàn)簡易的vue功能
vue 雙向綁定的原理
實現(xiàn) vue
的雙向綁定,v-text
、v-model
、v-on
方法
Vue
響應(yīng)系統(tǒng),其核心有三點:observe、watcher、dep
:
observe
:遍歷data
中的屬性,使用Object.defineProperty
的get/set
方法- 對其進行數(shù)據(jù)劫持;dep
:每個屬性擁有自己的消息訂閱器dep
,用于存放所有訂閱了該屬性的觀察者對象;watcher
:觀察者(對象),通過dep
實現(xiàn)對響應(yīng)屬性的監(jiān)聽,監(jiān)聽到結(jié)果后,主動觸發(fā)自己的回調(diào)進行響應(yīng)。
class MinVue { constructor(options) { this.$data = options.data; this.$methods = options.methods; this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : el; this.bindData(this.$data); new Observer(this.$data); new Compile(this); } bindData(data) { Object.keys(data).forEach(key => { /* *當(dāng)value值為基本數(shù)據(jù)類型時,this.key數(shù)據(jù)變化,對用的$data不會變化 *只有當(dāng)value值為對象或者數(shù)組類型時,數(shù)據(jù)變化會同步 **/ Object.defineProperty(this, key, { enumerable: true, configurable: true, get: () => { return data[key]; }, set: newValue => { data[key] = newValue; }, }); }); } } class Observer { constructor(data) { this.work(data); } work(data) { if (Object.prototype.toString.call(data) === '[object Object]') { Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]); }); } } defineReactive(data, key, value) { const observer = this; const dep = new Dep(); // 當(dāng)value為對象時,遞歸調(diào)用 this.work(value); /* *當(dāng)一個變量需要影響多個元素時,即一個變量變化需要響應(yīng)多個元素內(nèi)容的變化 *先記錄下所有的依賴 */ Object.defineProperty(data, key, { enumerable: true, configurable: true, get: () => { if (Dep.target) { dep.add(Dep.target); } return value; }, set: newValue => { value = newValue; // 賦新值后,新值有可能為對象,重新綁定get set方法 observer.work(newValue); dep.notify(); }, }); } } class Dep { constructor() { this.watcher = new Set(); } add(watcher) { if (watcher && watcher.update) this.watcher.add(watcher); } notify() { this.watcher.forEach(watch => watch.update()); } } class Watcher { constructor(vm, key, cb) { Dep.target = this; this.vm = vm; this.key = key; // 會觸發(fā)Observer定義的getter方法,收集Dep.target this._old = vm.$data[key]; this.cb = cb; Dep.target = null; } update() { const newValue = this.vm.$data[this.key]; this.cb(newValue); this._old = newValue; } } class Compile { constructor(vm) { this.vm = vm; this.methods = vm.$methods; this.compile(vm.$el); } compile(el) { const childNodes = el.childNodes; Array.from(childNodes).forEach(node => { if (this.isTextNode(node)) { this.compileTextNode(node); } else if (this.isElementNode(node)) { this.compileElement(node); } if (node.childNodes && node.childNodes.length) this.compile(node); }); } isTextNode(node) { return node.nodeType === 3; } isElementNode(node) { return node.nodeType === 1; } compileTextNode(node) { // .+?正則懶惰匹配 const reg = /\{\{(.+?)\}\}/g; const text = node.textContent; if (reg.test(text)) { let key = RegExp.$1.trim(); node.textContent = text.replace(reg, this.vm[key]); new Watcher(this.vm, key, newValue => { node.textContent = newValue; }); } } compileElement(node) { const attrs = node.attributes; if (attrs.length) { Array.from(attrs).forEach(attr => { if (this.isDirective(attr.name)) { // 根據(jù)v-來截取一下后綴屬性名 let attrName = attr.name.indexOf(':') > -1 ? attr.name.substr(5) : attr.name.substr(2); let key = attr.value; this.update(node, attrName, key, this.vm[key]); } }); } } isDirective(dir) { return dir.startsWith('v-'); } update(node, attrName, key, value) { if (attrName === 'text') { node.textContent = value; new Watcher(this.vm, key, newValue => { node.textContent = newValue; }); } else if (attrName === 'model') { node.value = value; new Watcher(this.vm, key, newValue => { node.value = newValue; }); node.addEventListener('input', e => { this.vm[key] = node.value; }); } else if (attrName === 'click') { node.addEventListener(attrName, this.methods[key].bind(this.vm)); } } }
測試 MinVue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <h3>{{ msg }}</h3> <p>{{ count }}</p> <h1>v-text</h1> <p v-text="msg"></p> <input type="text" v-model="count" /> <button type="button" v-on:click="increase">add+</button> <button type="button" v-on:click="changeMessage">change message!</button> <button type="button" v-on:click="recoverMessage">recoverMessage!</button> </div> </body> <script src="./js/min-vue.js"></script> <script> new MinVue({ el: '#app', data: { msg: 'hello,mini vue.js', count: 666, }, methods: { increase() { this.count++; }, changeMessage() { this.msg = 'hello,eveningwater!'; }, recoverMessage() { console.log(this); this.msg = 'hello,mini vue.js'; }, }, }); </script> </html>
以上就是詳解如何使用Object.defineProperty實現(xiàn)簡易的vue功能的詳細(xì)內(nèi)容,更多關(guān)于Object.defineProperty vue的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue使用jsencrypt實現(xiàn)rsa前端加密的操作代碼
這篇文章主要介紹了vue使用jsencrypt實現(xiàn)rsa前端加密,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09使用vue-cli創(chuàng)建vue2項目的實戰(zhàn)步驟詳解
相信大部分Vue開發(fā)者都使用過vue-cli來構(gòu)建項目,它的確很方便,但對于很多初級開發(fā)者來說,還是要踩不少坑的,下面這篇文章主要給大家介紹了關(guān)于使用vue-cli創(chuàng)建vue2項目的實戰(zhàn)步驟,需要的朋友可以參考下2023-01-01elementUI自定義上傳文件功能實現(xiàn)(前端后端超詳細(xì)過程)
自定義上傳思路很簡單,下面這篇文章主要給大家介紹了關(guān)于elementUI自定義上傳文件功能實現(xiàn)(前端后端超詳細(xì)過程)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11vue無法加載文件C:\xx\AppData\Roaming\npm\vue.ps1系統(tǒng)禁止運行腳本
這篇文章主要介紹了vue?:?無法加載文件?C:\xx\AppData\Roaming\npm\vue.ps1...系統(tǒng)上禁止運行腳本問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07