詳解如何使用Object.defineProperty實(shí)現(xiàn)簡(jiǎn)易的vue功能
vue 雙向綁定的原理
實(shí)現(xiàn) vue 的雙向綁定,v-text、v-model、v-on 方法
Vue 響應(yīng)系統(tǒng),其核心有三點(diǎn):observe、watcher、dep:
observe:遍歷data中的屬性,使用Object.defineProperty的get/set方法- 對(duì)其進(jìn)行數(shù)據(jù)劫持;dep:每個(gè)屬性擁有自己的消息訂閱器dep,用于存放所有訂閱了該屬性的觀察者對(duì)象;watcher:觀察者(對(duì)象),通過dep實(shí)現(xiàn)對(duì)響應(yīng)屬性的監(jiān)聽,監(jiān)聽到結(jié)果后,主動(dòng)觸發(fā)自己的回調(diào)進(jìn)行響應(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ù)類型時(shí),this.key數(shù)據(jù)變化,對(duì)用的$data不會(huì)變化
*只有當(dāng)value值為對(duì)象或者數(shù)組類型時(shí),數(shù)據(jù)變化會(huì)同步
**/
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為對(duì)象時(shí),遞歸調(diào)用
this.work(value);
/*
*當(dāng)一個(gè)變量需要影響多個(gè)元素時(shí),即一個(gè)變量變化需要響應(yīng)多個(gè)元素內(nèi)容的變化
*先記錄下所有的依賴
*/
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: () => {
if (Dep.target) {
dep.add(Dep.target);
}
return value;
},
set: newValue => {
value = newValue;
// 賦新值后,新值有可能為對(duì)象,重新綁定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;
// 會(huì)觸發(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));
}
}
}
測(cè)試 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實(shí)現(xiàn)簡(jiǎn)易的vue功能的詳細(xì)內(nèi)容,更多關(guān)于Object.defineProperty vue的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue記住滾動(dòng)條和實(shí)現(xiàn)下拉加載的完美方法
這篇文章主要給大家介紹了關(guān)于Vue記住滾動(dòng)條和實(shí)現(xiàn)下拉加載的完美方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
vue父組件調(diào)用子組件方法報(bào)錯(cuò)問題及解決
這篇文章主要介紹了vue父組件調(diào)用子組件方法報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
vue使用jsencrypt實(shí)現(xiàn)rsa前端加密的操作代碼
這篇文章主要介紹了vue使用jsencrypt實(shí)現(xiàn)rsa前端加密,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
vue封裝一個(gè)簡(jiǎn)單的div框選時(shí)間的組件的方法
這篇文章主要介紹了vue封裝一個(gè)簡(jiǎn)單的div框選時(shí)間的組件的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01
Vue-cli3生成的Vue項(xiàng)目加載Mxgraph方法示例
這篇文章主要介紹了Vue-cli3生成的Vue項(xiàng)目加載Mxgraph方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
使用vue-cli創(chuàng)建vue2項(xiàng)目的實(shí)戰(zhàn)步驟詳解
相信大部分Vue開發(fā)者都使用過vue-cli來構(gòu)建項(xiàng)目,它的確很方便,但對(duì)于很多初級(jí)開發(fā)者來說,還是要踩不少坑的,下面這篇文章主要給大家介紹了關(guān)于使用vue-cli創(chuàng)建vue2項(xiàng)目的實(shí)戰(zhàn)步驟,需要的朋友可以參考下2023-01-01
elementUI自定義上傳文件功能實(shí)現(xiàn)(前端后端超詳細(xì)過程)
自定義上傳思路很簡(jiǎn)單,下面這篇文章主要給大家介紹了關(guān)于elementUI自定義上傳文件功能實(shí)現(xiàn)(前端后端超詳細(xì)過程)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
vue無法加載文件C:\xx\AppData\Roaming\npm\vue.ps1系統(tǒng)禁止運(yùn)行腳本
這篇文章主要介紹了vue?:?無法加載文件?C:\xx\AppData\Roaming\npm\vue.ps1...系統(tǒng)上禁止運(yùn)行腳本問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

