模拟vue双向绑定
数据劫持
Vue 内部使用了 Obeject.defineProperty() 来实现双向绑定,通过这个函数可以监听到 set 和 get 的事件。
var data = { name: 'sonya' }
observe(data)
let name = data.name // -> get value
data.name = 'aha' // -> change value
function observe(obj) {
// 判断类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
}
})
}
以上代码简单的实现了如何监听数据的 set 和 get 的事件,但是仅仅如此是不够的,还需要在适当的时候给属性添加发布订阅
<div>
</div>
在解析如上模板代码时,遇到 就会给属性 name 添加发布订阅。
// 通过 Dep 解耦
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
// sub 是 Watcher 实例
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 调用 update 方法更新 Dom
this.cb(this.value)
}
}
var data = { name: 'yck' }
observe(data)
// 模拟解析到 `` 触发的操作
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy'
接下来,对 defineReactive 函数进行改造
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}
以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加。
模拟vue响应式
function cb(val){
/* 渲染视图 */
console.log("视图更新了~~");
}
function defineReactive(obj,key,val){
observer(val); //递归子属性
/* 一个 Dep 类对象 */
const dep = new Dep();
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){
console.log("in getter:",val);
/* 将 Dep.target(即当前的 Watcher 对象存入 dep 的 subs 中) */
dep.addSub(Dep.target);
return val;
},
set:function reactSetter(newVal){
if(val === newVal) return;
console.log("in setter:",val,newVal);
/*cb(newVal);*/
/* 在 set 的时候触发 dep 的 notify 来通知所有的 Watcher 对象更新视图 */
dep.notify();
}
});
}
function observer(value){
if(!value || (typeof value !== "object")){
return;
}
Object.keys(value).forEach((key)=>{
defineReactive(value,key,value[key]);
});
}
/*实现一个Vue*/
/*class Vue{
constructor(options){
this._data = options.data;
observer(this._data);
}
}
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 视图更新啦~ */
/*订阅者Dep*/
class Dep{
constructor(){
/*用来存放watcher对象的数组*/
this.subs = [];
}
/* 在 subs 中添加一个 Watcher 对象 */
addSub(sub){
this.subs.push(sub);
}
/* 通知所有 Watcher 对象更新视图 */
notify(){
this.subs.forEach((sub)=>{
sub.update();
});
}
}
/*观察者Watcher*/
class Watcher{
constructor(){
/* 在new一个Watcher对象时将该对象赋值给Dep.target,在 get 中会用到 */
Dep.target = this;
}
/* 更新视图的方法 */
update(){
console.log("update:视图更新啦~~~~");
}
}
Dep.target = null;
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一个 Watcher 观察者对象,这时候 Dep.target 会指向这个 Watcher 对象 */
new Watcher();
/* 在这里模拟 render 的过程,为了触发 test 属性的 get 函数 */
console.log('render~', this._data.test);
}
}
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 视图更新啦~ */