上图,无图无真相 javascript
花了400大洋买了frontedmasters的一个月的会员,就是为了看男神的这份vue教程,没有中文字幕,痛苦地坚持啃完了,建议有钱而且英语好的人直接买会员去官网看视频,尤大神人长得帅,业务水平高,英语还超苏,对不起,忍不住犯了会儿花痴,若是没钱英语又渣的人只能看个人总结了,看个人文章的好处就是不用花钱,不用被英语折磨,坏处就是本人的文字水平和业务水平真的有限,不会一步步引导式地分析原理和代码的实现,只能贴代码和尽我所能来解释了,能理解多少就看你们的水平了,这算不算无良商家,不包售后啊,哈哈。vue
这个系列文章将从下面几个方面来介绍vuejava
相信了解过vue的同窗都知道,vue是MVVM框架,最大的特色就是双向数据绑定,数据Model修改了以后视图View会自动更新, view也能够经过v-model的将ViewModel的变化同步到Model里面react
这篇文章要探讨的问题:vue是如何监控数据的变化从而自动更新视图,也就是vue响应式是如何实现的,咱们一步步来看。数组
考虑下面这段代码,一个简单的代数计算关系,b依赖a, b是a的10倍闭包
var a= 10;
var b = a*10;
a = 20
//b = ? 如何监听b的变化
复制代码
请认真思考一下再往下看:当每次改变a的值的时候,如何监听a的变化而且更新b,保证他们的数学关系b是a的10倍?
给你们留了10行空白,不知道你们有没有认真思考上面的问题?app
不卖关子了,直接用两个数值,咱们目前没法监控,能够换种思路,直接监控一个数值变量行不通,咱们能够监控一个对象属性的变化,在属性改变的时候收集它的依赖,就能够触发依赖的更新框架
js有一个方法Object.defineProperty能够劫持属性的读取器getter和setter, 来看个例子frontend
let obj = {
a:10
};
observer(obj, 'a');
obj.a = 20;
let b = obj.a;
obj.a = 30;
// 实现一个函数,能够监控到对象属性的变化
function observer(obj, key) {
Object.defineProperty(obj, key, {
configurable: true, // 能够被delete
enumerable: true, // for in迭代
set(val) {
console.log(`对象的属性${key}被赋值了${val}`);
},
get() {
console.log(`对象的属性${key}被读取了`);
}
});
}
//对象的属性a被赋值了20
//对象的属性a被读取了
//对象的属性a被赋值了30
复制代码
从上面的例子能够看出, 咱们只要用 Object.defineProperty重写对象的属性读写器getter和setter就能够监听到对象属性的变化,上面的代码只能够监控一个属性的变化,对于对象的全部属性,须要遍历转换,改造一下observer方法(这里只考虑一层对象,深层次的须要递归遍历,数组的监控也须要另写方法,有兴趣的去看vue的源码)函数
function observer(obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get() {
console.log(`getting key "${key}": ${internalValue}`)
return internalValue
},
set(newVal) {
console.log(`setting key "${key}" to: ${internalValue}`)
internalValue = newVal
}
})
})
}
复制代码
如今咱们已经能够监听到一个对象全部属性的变化了,最开始提出的问题已经解决了一半了,接下来要解决的问题是当属性变化的时候,如何收集它的依赖,触发依赖的更新呢?也就是咱们这篇文章的主题,如何实现一个简易的响应式系统。
思路就是:在读取属性的时候收集依赖,在改变属性值的时候触发依赖的更新
按照这个思路,实习一个简易的响应式系统。
// 全局的依赖收集器Dep
window.Dep = class Dep {
constructor() {
this.subscribers = new Set(); // 保证依赖不重复添加
}
// 追加订阅者
depend() {
if(activeUpdate) { // activeUpdate注册为订阅者
this.subscribers.add(activeUpdate)
}
}
// 运行全部的订阅者更新方法
notify() {
this.subscribers.forEach(sub => {
sub();
})
}
}
let activeUpdate
// js单线程语言,任一时刻只能有一个函数执行,也就是任一时刻,只可能有一个依赖在更新 //用一个全局变量activeUpdate来标志,这里有点很差理解,你们多想一想就会明白autorun的巧妙之处了
// autorun接受一个更新函数
function autorun(update) {
function wrapperUpdate() {
activeUpdate = wrapperUpdate
update() // wrapperUpdate, 闭包
activeUpdate = null;
}
wrapperUpdate();
}
function observer(obj) {
Object.keys(obj).forEach(key => {
var dep = new Dep(); // 为每一个key建立订阅器Dep
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get() {
// console.log(`getting key "${key}": ${internalValue}`)
// 将当前正在运行的更新函数追加进订阅者列表
if(activeUpdate) {
dep.depend() //收集依赖
}
return internalValue
},
set(newVal) {
//console.log(`setting key "${key}" to: ${internalValue}`)
// 加个if判断,数据发生变化再触发更新
if(internalValue !== newVal) {
internalValue = newVal
dep.notify() // 触发依赖的更新
}
}
})
})
}
let state = {
count:0
}
observer(state);
autorun(() => {
console.log('state.count发生变化了', state.count)
})
state.count = state.count + 5;
// state.count发生变化了 0
// state.count发生变化了 5
复制代码
这篇文章先更到这里,后续内容有时间继续更新,虽然更新时间不定,可是计划月底更新完,但愿喜欢的小伙伴多多关注和支持,谢谢。