vue實現(xiàn)過程
1床玻、new Vue() ?先執(zhí)?初始化,對data執(zhí)?響應(yīng)化處理,這個過程發(fā)?在Observer中
- 同時對模板執(zhí)?編譯娘汞,找到其中動態(tài)綁定的數(shù)據(jù),從data中獲取并初始化視圖夕玩,這個過程發(fā)?在
Compile中- 同時定義?個更新函數(shù)和Watcher你弦,將來對應(yīng)數(shù)據(jù)變化時Watcher會調(diào)?更新函數(shù)
- 由于data的某個key在?個視圖中可能出現(xiàn)多次,所以每個key都需要?個管家Dep來管理多個
Watcher將來data中數(shù)據(jù)?旦發(fā)?變化燎孟,會?先找到對應(yīng)的Dep禽作,通知所有Watcher執(zhí)?更新函數(shù)
- Vue:框架構(gòu)造函數(shù)
- Observer:執(zhí)?數(shù)據(jù)響應(yīng)化(分辨數(shù)據(jù)是對象還是數(shù)組)
- Compile:編譯模板,初始化視圖揩页,收集依賴(更新函數(shù)旷偿、watcher創(chuàng)建)
- Watcher:執(zhí)?更新函數(shù)(更新dom)
- Dep:管理多個Watcher,批量更新
依賴收集
視圖中會?到data中某key爆侣,這稱為依賴萍程。同?個key可能出現(xiàn)多次,每次都需要收集出來??個
Watcher來維護它們兔仰,此過程稱為依賴收集茫负。
多個Watcher需要?個Dep來管理,需要更新時由Dep統(tǒng)?通知斋陪。
實現(xiàn)思路
- defineReactive時為每?個key創(chuàng)建?個Dep實例
- 初始化視圖時讀取某個key朽褪,例如name1,創(chuàng)建?個watcher1
- 由于觸發(fā)name1的getter?法无虚,便將watcher1添加到name1對應(yīng)的Dep中
- 當(dāng)name1更新缔赠,setter觸發(fā)時,便可通過對應(yīng)Dep通知其管理所有Watcher更新
實現(xiàn)代碼
//從頭實現(xiàn)vue的雙向綁定原理 以及視圖更新機制
class Vue {
constructor(option) {
//option代表vue的所有對象
this.$options = option
this.$data = option.data
//劫持data中的數(shù)據(jù)
observe(this.$data)
}
}
//劫持所有屬性
class Observe {
constructor(value) {
this.value = value
this.walk(this.value)
}
//walk遍歷對象中的每一個key,每一個屬性都調(diào)用一次defineReactive 來實現(xiàn)劫持
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
//定義一個管家 管家中存放自己需要管理的watcher
class Dep {
constructor() {
//存放watcher
this.deps = []
}
//這里將watch添加到管家中
addDep(dep) {
this.deps.push(dep)
}
//觸發(fā)自己管理的每一個watch的更新方法
notify() {
this.deps.forEach(dep => dep.update())
}
}
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm
this.updateFn = updateFn
this.key = key
//解析插值表達式的時候?qū)嵗疻atch友题,因為要用到值嗤堰,會觸發(fā)getter方法 這時候給Dep.target賦值
Dep.target = this
this.vm[this.key] //調(diào)用data中的值,觸發(fā)definePrototy的getter方法度宦,開始收集依賴
Dep.target = null //每次將watcher添加到dep中后清空 不影響下次添加
}
//更新視圖的方法
update() {
//傳入一個根實例踢匣,一個當(dāng)前的值
this.updateFn.call(this.vm, this.vm[key])
}
}
//初始化視圖方法
class Compile {
constructor(el, vm) {
this.$vm = vm
this.$el = document.querySelctor(el)
//如果拿到節(jié)點,就將節(jié)點編譯e
if (this.$el) {
this.complie(this.$el)
}
}
complie(value) {
const childNodes = value.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
console.log("編譯元素" + node.nodeName);
} else if (this.isInterpolation(node)) {
//編譯插值文本的時候創(chuàng)建watcher 并更新dom
console.log("編譯插值?本" + node.textContent);
this.compileText(node);
}
//遞歸編譯
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}
isElement(node) {
return node.nodeType == 1;
}
isInterpolation(node) {
return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileText(node) {
// console.log(RegExp.$1);
this.update(node, RegExp.$1, 'text')
}
update(node, exp, dir) {
const fn = this[dir + 'Updater']
fn && fn(node, this.$vm[exp]) //給節(jié)點賦值
new Watcher(this.$vm, exp, function (val) { //添加監(jiān)聽 觸發(fā)的時候調(diào)用此方法更新節(jié)點
fn && fn(node, val)
})
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val
}
}
function observe(obj) {
if (typeof obj != 'object' || obj === null) return
new Observe(obj)
}
function defineReactive(obj, key, val) {
//解決嵌套對象問題 采用遞歸方法
observe(val)
//實例化管家戈抄,為管家添加多個watcher
const dep = new dep()
Object.defineProperty(obj, key, {
get() {
//收集依賴 Dep.target在Watcher創(chuàng)建的時候被賦值 其實指向了watch的實例 watch的實例中有update方法离唬,
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal != val) {
//將管家中多個watch更新
dep.notify()
}
}
})
}
function compileElement(node) {
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
let attrName = attr.name;
let exp = attr.value;
if (this.isDirective(attrName)) {
let dir = attrName.substring(2);
this[dir] && this[dir](node, exp);
}
});
}