Vue MVVMフレームワークの実現原理
MVVMのフレームワーク原理データハイジャック パブリッシュ購読モード 原理を実現する過程 dataオプションの属性を巡回し、データの観測を追加し、observeの方法を実行し、Objectを使用する.definePropertyメソッドをgetメソッドとsetメソッドに変換し、データのハイジャックを実現し、各要素ノードを判断するcompilerメソッドを追加し、テキストノードであれば命令テンプレートに基づいてデータ を置換するデータが変化すると、observeのsetメソッドがトリガーされ、直ちにDep.notify()メソッドが呼び出され、すべてのサブスクライバを巡り始め、実行者のUpdateメソッドが呼び出され、サブスクライバは通知を受けた後にビューを更新する . v-model実装データの双方向バインドcomplierメソッドで各要素ノードタイプを判定する場合、ラベルノードであれば、v-model命令が存在するか否かを判定し、存在するとサブスクライバを作成し、そのラベルノードの初期値を設定し、inputイベントリスニングを追加し、入力ボックスに入力された値が変化するとset,getメソッドを実行し、ビューを更新する .
実装原理のコード
index.html
mvvm.js
実装原理のコード
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
{{ message }}
<div>{{message}}div>
<p>{{count}}p>
<input type="text" v-model="message"/>
div>
div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="./mvvm.js">script>
<script>
var app = new MVVM({
el: '#app',
data: {
message: 'hello world',
count: 1
}
});
script>
body>
mvvm.js
// publish
class Dep{
constructor(){
this.subs = [];
}
//
addSub(watcher){
this.subs.push(watcher);
}
// ,
notify(newVal){
this.subs.forEach(sub=>{
sub.update(newVal);
})
}
}
// subscribe dom
class Watcher{
constructor(cb){
this.cb = cb;
}
update(newVal){
console.log(' ....');
this.cb(newVal);
}
}
// , key , new Dep
// new Dep()
// , new watcher
// new Watcher()
// ,
// addSub()
// ,
// notify()
let watch = null;
class MVVM{
constructor(options){
//
this.$options = options;
this.$data = this._data = options.data;
//
this.observer(this.$data);
//
this.compiler(options.el);
}
//
observer(data){
// data key value
Object.entries(data).forEach(([key, value])=>{
//
console.log(' :', key);
const dep = new Dep();
// key
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
//
set(newVal){
// console.log('set run......');
if(newVal !== value){
//
value = newVal;
// dom
console.log(' .....');
dep.notify(newVal);
}
},
//
get(){
// console.log('get run......');
console.log(' .....');
if(watcher){
dep.addSub(watcher);
watcher = null;
}
return value;
}
});
})
}
//
compiler(el){
// dom
const element = document.querySelector(el);
// dom
this.compilerNode(element);
}
compilerNode(element){
// dom
const childNodes = element.childNodes;
// ,
Array.from(childNodes).forEach(node=>{
const {nodeType, textContent} = node;
//
if(nodeType === 3){
//
let reg = /\{\{\s*(\S*)\s*\}\}/;
//
if(reg.test(textContent)){
// ,dom
//
console.log(' :', RegExp.$1);
watcher = new Watcher((newVal)=>{
//
node.textContent = newVal;
});
// dom
node.textContent = this.$data[RegExp.$1];
}
}
//
else if(nodeType === 1){
//
let attrs = Array.from(node.attributes);
//
attrs.forEach(attr=>{
//
if(attr.name.startsWith('v-')){
// ,
let dirName = attr.name.substr(2);
if(dirName === 'model'){ //v-model="message"
let key = attr.value;
//
watcher = new Watcher((newVal)=>{
node.value = newVal;
});
//
node.value = this.$data[key];
//
node.addEventListener('input', (ev)=>{
this.$data[key] = ev.target.value;
});
}
else if(dirName === 'bind'){
}
}
})
}
// ,
if(node.childNodes.length > 0){
//
this.compilerNode(node);
}
})
}
}