class Dep{
constructor() {
this.listenFunc = []
}
addFunc(obj) {
this.listenFunc.push(obj);
}
changeWatch() {
this.listenFunc.forEach(val => {
val.sendVal()
})
}
}
Dep.target = null;
const dep = new Dep()
class Watcher{
constructor(data, key, cbk) {
// 每一次實例watcher的時候,均會把當前實例賦值給Dep的target靜態屬性
Dep.target = this;
this.data = data;
this.key = key;
this.cbk = cbk;
// 每一次的實例都會調用該函數
this.init()
}
// 獲取對應key的值
init() {
// 獲取對應key的值
this.value = utils.getValue(this.data, this.key);
Dep.target = null;
return this.value;
}
sendVal() {
let newVal = this.init()
this.cbk(newVal)
}
}
class Observer{
constructor(data) {
if (!data || typeof data !== 'object') {
return;
}
this.data = data;
this.init()
}
init() {
Object.keys(this.data).forEach(val => {
this.observer(this.data, val, this.data[val])
})
}
observer(obj, key, value) {
// 經過遞歸實現每一個屬性的數據劫持
new Observer(obj[key])
Object.defineProperty(obj, key, {
// 添加劫持以後的屬性獲取方法
get() {
if (Dep.target) {
// 給dep實例屬性listenFunc添加一個watcher實例
dep.addFunc(Dep.target)
}
return value
},
// 添加劫持以後的屬性設置方法
set(newValue) {
if (value === newValue) {
return;
}
value = newValue;
// 觸發每個listenFunc裏面的watcher實例
dep.changeWatch();
// 爲了兼容新值爲一個對象的時候,該對象的屬性也得添加劫持
new Observer(value);
}
})
}
}
const utils = {
setValue(node, data, key) {
node.value = this.getValue(data, key)
},
getValue(data, key) {
if (key.indexOf('.') > -1) {
let arr = key.split('.');
for(let i = 0; i < arr.length; i++) {
data = data[arr[i]]
}
return data
} else {
return data[key]
}
},
getContent(node, key, data) {
node.textContent = this.getValue(data, key)
},
// 2.在input事件發生以後,改變對應的屬性值
changeKeyVal(data, key, newVal) {
if (key.indexOf('.') > -1) {
let arr = key.split('.');
for(let i = 0; i < arr.length - 1; i++) {
data = data[arr[i]]
}
data[arr[arr.length - 1]] = newVal
} else {
data[key] = newVal
}
}
}
// 實現雙向數據綁定
class Mvvm{
constructor({el, data}) {
this.el = el;
this.data = data;
// 初始化執行數據綁定實例對象的過程(以及數據劫持)
this.init();
// 替換文本中的屬性爲真實的數據
this.initDom();
}
init() {
Object.keys(this.data).forEach(val => {
this.observer(this, val, this.data[val])
})
// 給當前數據集合的每個屬性添加劫持
new Observer(this.data)
}
observer(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
value = newValue
}
})
}
initDom() {
this.$el = document.getElementById(this.el);
// 文本碎片--> 避免由於操做DOM而致使瀏覽器的屢次重繪(操做完成以後可把整個碎片添加進去,瀏覽器課一併識別渲染)
let newFargment = this.createFragment();
// 根據nodeType來替換對應的屬性值
this.compiler(newFargment);
this.$el.appendChild(newFargment);
}
createFragment() {
let newFragment = document.createDocumentFragment();
let firstChild;
while(firstChild = this.$el.firstChild) {
newFragment.appendChild(firstChild);
}
return newFragment;
}
compiler(node) {
if (node.nodeType === 1) {
let attributes = node.attributes;
Array.from(attributes).forEach(val => {
if (val.nodeName === 'v-model') {
// 1.捕捉input輸入框的修改事件
node.addEventListener('input', (e) => {
utils.changeKeyVal(this.data, val.nodeValue, e.target.value)
})
utils.setValue(node, this.data, val.nodeValue)
}
})
} else if (node.nodeType === 3) {
let contentVal = node.textContent.indexOf("{{") > -1 && node.textContent.split('{{')[1].split('}}')[0];
contentVal && utils.getContent(node, contentVal, this.data);
// 添加屬性監聽
contentVal && new Watcher(this.data, contentVal, (newVal) => {
node.textContent = newVal
})
}
// 經過遞歸的形式保證每一級的文本均可獲取到並替換
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach(val => {
this.compiler(val)
})
}
}
}