相關文章網上已經不少了,趁 3.0 沒出跟風打個卡javascript
本文只作簡單介紹,結合代碼食用更佳:github/vue-learn-source-code
效果預覽:github pageshtml
defineProperty 讓咱們能夠劫持某個屬性的 getter 和 setter,舉個例子:vue
var person = {
firstName: 'meimei',
lastName: 'han'
};
Object.defineProperty(person, 'fullName', {
get() {
return this.lastName + ' ' + this.firstName;
},
set(val) {
let arr = val.split(' ');
this.lastName = arr[0];
this.firstName = arr[1];
}
});
複製代碼
劫持 fullName 後,改變 firstName 或 lastName 會更新 fullName,反之亦然java
本文的目標是仿造 vue 實現改變數據後更新 dom,讓如下代碼可以 work:git
<div id="app">
<p>firstName: {{firstName}}</p>
<p>lastName: {{lastName}}</p>
<p>fullName: {{fullName}}</p>
</div>
<script src="./vue.js"></script>
<script> let vm = new Vue({ el: '#app', data() { return { firstName: 'meimei', lastName: 'han' }; }, computed: { fullName: { get: function() { return this.lastName + ' ' + this.firstName; }, set: function(val) { let arr = val.split(' '); this.lastName = arr[0]; this.firstName = arr[1]; } } } }); </script>
複製代碼
咱們要作的是數據變化後去更新 dom,觀察者模式很適合
數據只須要收集依賴,當數據變化通知依賴更新便可,先建一個類描述這件事:github
class Dep {
constructor() {
this.subs=[]
}
addSub(item) {
this.subs.push(item);
}
notify() {
this.subs.forEach(item => {
item.update();
});
}
}
複製代碼
再細想一下,dom 依賴 data,則在獲取 dom 的過程當中須要用到 data 的 get,在 data get 時收集依賴便可,set data 時執行 dom 的 update
get data 時須要記錄依賴 data 的數據,給 class Dep 增長一個屬性 target 做爲記錄工具,結合 defineProperty 實現以下:app
Dep.target = undefined;
function defineReactive(obj, key) {
let dep = new Dep();
let val = obj[key];
Object.defineProperty(obj, key, {
get: function() {
if (Dep.target) {
// get 中收集依賴
dep.addSub(Dep.target);
}
return val;
},
set: function(value) {
val = value;
// set 中觸發更新
dep.notify();
}
})
}
複製代碼
工具已備齊,接下來就是遍歷 data 的屬性,用 defineReactive 走一遍dom
解析 dom 會用到 data 和 computed,computed 的 get 會用到 data工具
function initData(vm) {
let data = vm.$options.data;
data = typeof data === 'function' ? data() : data;
Object.keys(data).forEach(key => {
defineReactive(data, key);
});
// 把 data 的屬性代理到 vm 實例
proxy(data, vm);
}
複製代碼
function initComputed(vm) {
let computed = vm.$options.computed;
let defaultSetter = function(key) {
console.error(this, ' has no setter for ', key)
}
Object.keys(computed).forEach(key => {
let getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get;
let setter = typeof computed[key] === 'function' ? defaultSetter.bind(computed) : computed[key].set;
Object.defineProperty(computed, key, {
get: getter.bind(vm),
set: setter.bind(vm)
})
})
// 把 computed 的屬性代理到 vm 實例
proxy(computed, vm);
}
複製代碼
function mount(vm) {
let update = compile(vm);
let watcher = new Watcher(update);
// 把 target 標爲 dom
Dep.target = watcher;
update();
Dep.target = undefined;
}
function compile(vm) {
let el = vm.$options.el;
el = document.querySelector(el);
vm.$el = el;
let innerHTML = el.innerHTML;
let getter = function() {
return innerHTML.replace(/{{(.*?)}}/g, function() {
// 這裏用到了 data computed 的 get,收集了依賴
return vm[arguments[1]]
});
};
let update = function() {
let iHTML = getter();
el.innerHTML = iHTML;
}
return update;
}
複製代碼
在收集依賴時,咱們給 Dep 這個 class 增長一個屬性 target,在 vue 中還結合了 targetStack。這種收集方式稍微管理不慎就可能存在 bug,在另外一篇文章有提過:熟悉 Vue ?你能解釋這個死循環嗎?。
爲本身的填坑喝彩~post