html部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模擬vue雙向綁定</title>
</head>
<body>
<div id="root">
<ul>
<li>123</li>
<li>456</li>
<li>789</li>
</ul>
<p>{{nickname}}</p>
<p>{{age}}</p>
</div>
</body>
</html>
js部分:
class MyVue{
static sign = /\{\{(.+?)\}\}/g; // {{ }}正則匹配
constructor(options){
const _this = this;
// _ 標識內(nèi)部屬性 , $表示 只讀私有屬性
this._el = options.el;
this._data = options.data;
// 監(jiān)聽數(shù)據(jù)的變化
for(let i in this._data){
let d = _this[i] = this._data[i] //將this._data的數(shù)據(jù)綁定到MyVue實例中
Object.defineProperty(_this,i,{
get(){
return d
},
set(newValue){
console.log(newValue)
d = newValue;
_this.render()
}
})
}
this._templateDOM = document.getElementById(this._el)
this._parent = this._templateDOM.parentNode;
this.render()
options.mounted.bind(_this)()
}
// 渲染
render(){
// 1.獲取每個dom及其子節(jié)點,正則匹配到每個節(jié)點
let copyTemplate = this._templateDOM.cloneNode(true); //深拷貝dom,含子節(jié)點
this.compile(copyTemplate)
this.update(copyTemplate)
}
// 編譯
compile( template ){
let _this = this;
let childNodes = template.childNodes;
for(let i = 0;i<childNodes.length;i++){
let type = childNodes[i].nodeType;
if(type === 3){
// 文本節(jié)點
let txt = childNodes[i].nodeValue;
txt = txt.replace(MyVue.sign,function(_,group){
//js中的replace函數(shù) 是支持回調(diào)函數(shù)的,回調(diào)中的第一個參數(shù)是匹配到的參數(shù),return可以返回一個希望匹配替換成的值
let key = group.trim()
let value = _this[key]
return value;
})
childNodes[i].nodeValue = txt;
}else if(type === 1){
// node節(jié)點 遞歸調(diào)用
_this.compile(childNodes[i])
}
}
}
// 更新替換
update(realDom){
let replacdChild = document.getElementById(this._el);
this._parent.replaceChild(realDom,replacdChild)
}
}
let VueDom = new MyVue({
el:'root',
data:{
nickname:'我的名字',
age:20
},
mounted(){
setTimeout(() => {
console.log('mounted',this)
this.nickname = '你的你的'
}, 2000);
}
})
console.log(VueDom)