首先通过控制台输出一个定义在vue初始化数据上的对象是什么
代码:
var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});
结果:
a属性中有这两个方法
vue通过Object.defineProperty()来实现数据劫持
它有三个参数:
第一个是参数所在对象;
第二个是你要操作的属性;
第三个是被操作的属性的特性
这个参数的格式为{}
一般是两个
其中get为访问属性时使用,set为修改属性时使用
var obj = {}
var name = '';
Object.defineProperty(obj, 'name', {
set: function (value) {
name = value;
console.log('name改变为' + value);
},
get: function () {
return '《' + name + '》'
}
})
obj.name = 'mypro'; // name值为mypro
console.log(obj.name); // 《mypro》
//其实只用Object.defineProperty()已经可以实现双向绑定,缺点是效率非常低
观察者模式,它使双向绑定更有效率,它是一对多的一种模式,
在vue中,你改了某个data的数据,这是"一";
"多",就是页面上凡是用了这个数据的地方都更新
这就是页面上的很多地方,都观察这个data,
这就是一对多,所以应当用观察者模式。
实现数据绑定的做法有⼤致如下⼏种:
发布者-订阅者模式(backbone.js)(框架过老不建议使用)
脏值检查(angular.js) (使用轮询)
数据劫持(vue.js)
就是通过Object.defineProperty(),去操作数据的get、set
要实现mvvm的双向绑定,就必须要实现以下⼏点:
1、实现⼀个数据监听器Observer,能够对数据对象的所有属性进⾏监听,如有变动可拿到最新值并通知订阅 者
var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function defineReactive(data, key, val) {
observe(val); // 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
return val;
},
set: function(newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}
现在可以监听每个数据变化,现在需要通知订阅者
function defineReactive(data, key, val) {
var dep = new Dep();
observe(val); // 监听子属性
Object.defineProperty(data, key, {
// ... 省略
set: function(newVal) {
if (val === newVal) return;
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
dep.notify(); // 通知所有订阅者
}
});
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
// Observer.js
// ...省略
Object.defineProperty(data, key, {
get: function() {
// 由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher, 添加完移除
Dep.target && dep.addSub(Dep.target);
return val;
}
// ... 省略
});
// Watcher.js
Watcher.prototype = {
get: function(key) {
Dep.target = this;
this.value = data[key]; // 这里会触发属性的getter,从而添加订阅者
Dep.target = null;
}
}
2、实现⼀个指令解析器Compile,对每个元素节点的指令进⾏扫描和解析,根据指令模板替换数据,以及绑定 相应的更新函数
😂(我没怎么去理解),应该就是相当于解析模版命令,将变量替换为数据然后渲染视图。
3、实现⼀个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执⾏指令 绑定的相应回调函数,从⽽更新视图
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
this.value = this.get();
}
Watcher.prototype = {
update: function() {
this.run(); // 属性值变化收到通知
},
run: function() {
var value = this.get(); // 取到最新值
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
}
},
get: function() {
Dep.target = this; // 将当前订阅者指向自己
var value = this.vm[exp]; // 触发getter,添加自己到属性订阅器中
Dep.target = null; // 添加完毕,重置
return value;
}
};
// 这里再次列出Observer和Dep,方便理解
Object.defineProperty(data, key, {
get: function() {
// 由于需要在闭包内添加watcher,所以可以在Dep定义一个全局target属性,暂存watcher, 添加完移除
Dep.target && dep.addDep(Dep.target);
return val;
}
// ... 省略
});
Dep.prototype = {
notify: function() {
this.subs.forEach(function(sub) {
sub.update(); // 调用订阅者的update方法,通知变化
});
}
};
4、mvvm⼊⼝函数,整合以上三者
这个是文章的原作者https://segmentfault.com/a/1190000006599500?utm_source=tag-newest,我就稍微加了些自己的理解和觉得重要的知识点。