Vue2双向绑定数据模型实现
响应性原理
Vue 使用 Object.defineProperty
方法进行响应式数据劫持,利用 set、get 访问器描述符来检测数据的读写。
作为MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。
在vue中每个组件实例都对应一个渲染 watcher 实例,它会在组件渲染的过程中把“接触”过的响应式数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
8d11592e24978f47bf42de6439c80230
代码实现
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<button id="btn">click</button>
<script src="./index.js">
</script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
message: 'hello world',
girlfriend: {
iKun: 'kunkun'
}
}
},
methods: {
changeMessage() {
this.message = this.message === 'hello world' ? '你好 世界' : 'hello world'
}
}
})
let btn = document.querySelector('#btn')
btn.addEventListener('click',() => {
app.methods.changeMessage.call(app)
})
</script>
</body>
</html>
js部分
//依赖收集器
class Dep {
constructor() {
this.deps = new Set();
}
addDep(dep) {
this.deps.add(dep);
}
notify() {
this.deps.forEach(dep => {
dep.update()
})
}
}
//监听器
class Observer {
constructor(data) {
this.data = data;
//递归添加响应性
this.walk(data);
}
walk(obj) {
//先不处理数组
if (Array.isArray(obj)) return;
Object.keys(obj).forEach((key) => {
this.defineReactive(key, obj[key], obj);
});
}
defineReactive(key, value, obj) {
if (typeof value === 'object') observer(value);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addDep(Dep.target);
Object.defineProperty(obj, '_Observer', {
enumerable: false,
configurable: true,
writable: true,
value: dep,
});
}
return value;
},
set(newValue) {
if (value === newValue) return;
value = newValue;
dep.notify();
},
});
}
}
//订阅器
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.get();
}
get() {
Dep.target = this;
this.vm.patch();
Dep.target = null;
}
update(dep) {
this.cb.call(this.vm);
}
}
function observer(value) {
if (typeof value !== 'object') return;
new Observer(value);
}
//Vue类
class Vue {
constructor(options) {
this.el = document.querySelector(options.el);
this._data =
typeof options.data !== 'function' ? options.data : options.data();
this.proxyToThis(this._data);
observer(this._data);
new Watcher(this, '', this.patch);
this.methods = options.methods;
console.log(this._data);
}
//模拟render-patch
patch() {
this.el.innerHTML = this.message;
}
proxyToThis(data) {
if (typeof data !== 'object') return;
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newValue) {
if (data[key] === newValue) return;
data[key] = newValue;
},
});
});
}
}