现在假设我们已经理解了vue响应式的原理,然或者希望这篇文章能有点作用juejin.im/post/5cc005…,现在我们创建一个girl,她的所有属性读写已经变得可观察了:
function defineReactive(obj, key, val, cb){
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
console.log(`${key}属性被获取了`);
return val
},
set:newVal=> {
console.log(`${key}属性被修改了`);
val = newVal;
// cb();
}
})
}
let girl = { height: '1.7', weight: '48', whole: '90分'}
function observe(value) {
Object.keys(value).forEach((key) => defineReactive(value, key, value[key]));
}
observe(girl)
girl.height
height属性被获取了
"1.7"
girl.weight
weight属性被获取了
"48"
girl.height = '1.9'
height属性被修改了
"1.9"
复制代码
但是这就可以了吗,我们如果想知道这个女孩是否漂亮怎么办呢,假设啊,girl身高大于1.65且体重小于48kg就是漂亮啊,原谅我这么肤浅,那我们改该怎么做呢?
计算属性
假设有这么一个方法叫watcher(观察者
),我们在girl对象上面创建了一个属性isBeautiful
,现在我们定义了她的值取决于该对象的另外两个属性,当满足条件时就是true了,现在我们来实现一下这个方法:
// 检测监听属性的值更新时调用
function updateNotice(val) {
console.log(`girl的isBeautiful属性为${val}`)
}
// obj 监听对象
// key 监听的属性
// cb 监听的回调函数
function watcher(obj, key, cb) {
Object.defineProperty(obj, key, {
get: function() {
var val = cb();
<!--通知监听属性的值-->
updateNotice(val);
return val;
},
set: function() {
console.log('watcher属性不可以被修改');
}
})
}
watcher(girl, 'isBeautiful', () => {
return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
})
girl.isBeautiful
height属性被获取了
girl的isBeautiful属性为差点漂亮
"差点漂亮"
girl.height = '1.7'
height属性被修改了
"1.7"
girl.weight = '47'
weight属性被修改了
"47"
girl.isBeautiful
height属性被获取了
weight属性被获取了
girl的isBeautiful属性为漂亮
"漂亮"
复制代码
perfect现在,要是只要我们获取下girl的isBeautiful属性就能知道她的类型了,就是有些麻烦,我们还是想观察者要是能检测到对应的依赖属性发生变化时,能主动通知我们她是否漂亮的属性的值就更完美了
依赖收集
现在我们怎么实现呢,不妨想想,上面说了(对应的依赖属性发生变化时)对应的就是每个可观察对象的set方法执行的时候,如果我们在set方法里面能够调用监听属性变化的方法updateNotice
,不就可以实现主动通知了吗,道理是这个道理, 但是updateNotice
方法需要接受回调值作为参数,而set方法的上下作用域是没有的,我们需要一个工具将监听器
和可观测对象
连接起来,这个工具的作用就是收集监听器
里面的回调参数cb
及监听属性更新的方法updateNotice
用来在可观测对象
里面来使用。 下面我们给这个工具起个好听的名字依赖收集器
:
let Dep = {
target: null // 用来接收监听器里面的回调参数和更新通知方法
}
复制代码
target等于我们监听器
里面的回调参数cb
及监听属性更新的方法updateNotice
,下面我们来优化下对应的updateNotice
方法:
// obj 监听对象
// key 监听的属性
// cb 监听的回调函数
function watcher(obj, key, cb) {
// 所依赖的属性发生变化时调用
function onDepUpdated () {
var val = cb();
updateNotice(val)
}
Object.defineProperty(obj, key, {
get: function() {
Dep.target = onDepUpdated;
var val = cb();
Dep.target = null;
return val;
},
set: function() {
console.log('watcher属性不可以被修改');
}
})
}
复制代码
重点来了,大家可能会好奇Dep.target先是等于onDepUpdated后又为null的作用,解释下,当我们首次获取监听对象的监听属性时,举个列子girl.isBeautiful这是会执行对应的get方法,这个时候我们先是将对应的Dep.target = onDepUpdated
(收集监听器
里面的回调参数cb
及监听属性更新的方法updateNotice
),这一步非常关键,通过这样的操作,依赖收集器就获得了监听器的回调值以及updateNotice()方法,然后执行对应的cb方法,监听属性所依赖的属性这个时候被获取,将会触发对应的defineReactive的get方法,这个时候我们对应的依赖收集下,cb执行之后再将Dep.target = null
,是不是豁然开朗,Dep.target作为全局变量,理所当然的能够被可观测对象的get/set所使用。下面我们将代码补充下:
function defineReactive(obj, key, val, cb){
let deps = [];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target && deps.indexOf(Dep.target) === -1) {
deps.push(Dep.target)
}
console.log(`${key}属性被获取了`);
return val
},
set:newVal=> {
console.log(`${key}属性被修改了`);
val = newVal;
deps.forEach((dep) => {
dep();
})
}
})
}
复制代码
正如上面写的,当被依赖的属性获取时,对应的deps便会收集依赖属性(计算属性)的onDepUpdated方法,当被依赖属性发生变化的时候,便会主动通知所有依赖者(计算属性),也就是执行对应的onDepUpdated方法。 下面贴上完整代码:
function defineReactive(obj, key, val, cb){
let deps = [];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target && deps.indexOf(Dep.target) === -1) {
deps.push(Dep.target)
}
console.log(`${key}属性被获取了`);
return val
},
set:newVal=> {
console.log(`${key}属性被修改了`);
val = newVal;
deps.forEach((dep) => {
dep();
})
}
})
}
function observe(value) {
Object.keys(value).forEach((key) => defineReactive(value, key, value[key]));
}
let Dep = {
target: null // 用来接收监听器里面的回调参数和更新通知方法
}
// 检测监听属性的值更新时调用
function updateNotice(val) {
console.log(`girl的isBeautiful属性为${val}`)
}
// obj 监听对象
// key 监听的属性
// cb 监听的回调函数
function watcher(obj, key, cb) {
// 所依赖的属性发生变化时调用
function onDepUpdated () {
var val = cb();
updateNotice(val)
}
Object.defineProperty(obj, key, {
get: function() {
Dep.target = onDepUpdated;
var val = cb();
Dep.target = null;
return val;
},
set: function() {
console.log('watcher属性不可以被修改');
}
})
}
let girl = { height: '1.7', weight: '48', whole: '90分'}
observe(girl)
watcher(girl, 'isBeautiful', () => {
return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
})
girl.isBeautiful
height属性被获取了
weight属性被获取了
"差点漂亮"
girl.weight = '46'
weight属性被修改了
height属性被获取了
weight属性被获取了
girl的isBeautiful属性为漂亮
"46"
复制代码
到此,我们已经实现了我们初始时候的设想,是不是对依赖收集也有了简单的了解了,如果能对你有一点点的帮助,再好不过了, 下面我们贴上优化后的代码,将依赖收集有关的功能、观察者、订阅者都功能集成下:
<!--订阅者-->
class Observer {
constructor(data) {
return this.walk(data);
}
walk(data) {
const key = Object.keys(data);
key.forEach( key => {
this.defineProperty(data, key, data[key]);
})
return data;
}
defineProperty(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
console.log(`${key}属性被获取了`);
dep.addSub();
return val
},
set(newVal) {
console.log(`${key}属性被修改了`);
val = newVal;
dep.notify();
}
})
}
}
<!--观察者-->
class Watcher {
constructor(obj, key, cb, onComputedUpData) {
this.obj = obj;
this.key = key;
this.cb = cb;
this.onComputedUpData = onComputedUpData;
return this.defineComputed();
}
defineComputed() {
let _this = this;
let DepUpdated = function() {
let val = _this.cb();
_this.onComputedUpData(val);
}
Object.defineProperty(_this.obj, _this.key, {
get() {
Dep.target = DepUpdated;
let val = _this.cb();
Dep.target = null;
return val;
},
set() {
console.log('wacther不能被修改');
}
})
}
}
<!--依赖收集器-->
class Dep {
constructor(){
this.deps = [];
}
addSub() {
if (Dep.target && this.deps.indexOf(Dep.target) == -1) {
this.deps.push(Dep.target);
}
}
notify() {
this.deps.forEach(dep => {
dep();
})
}
}
Dep.target = null;
<!--验证-->
let girl = new Observer({ height: '1.7', weight: '48', whole: '90分'})
watcher(girl, 'isBeautiful', () => {
return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
}, (val)=> {
console.log(`girl的isBeautiful属性为${val}`)
})
复制代码
扩展
终于结束了,来稍微总结下,依赖收集仅仅是用来实现计算属性的吗,下面我们看一个列子,我们使用vue的时候:
new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
复制代码
由于响应式的原因,大家都知道当我们修改对应的text1或者text2的时候,数据驱动对应的dom便会发生更新,但是当我们修改对应的text3呢,对应的set是不是也会执行对应的渲染呢,其实不然,那么vue是怎么做的呢,原理图如下
-
每个组件实例都有相应的watcher实例
-
渲染组件的过程,会把属性记录为依赖
-
当我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新
联想下,
- 既然模板渲染需要用到某个数据,那么一定会对这个数据进行访问,所以只要拦截getter,进行依赖收集,
- 当依赖的数据被设置时,setter能获得这个通知,如果有依赖的话进行对应更新render 如此的话,上面说的问题是不是就解决了,vue在渲染视图的时候便会将有用到的data进行依赖收集,也就是只有被Dep.target标记过的才会进行收集。
在vue里面,对应的观察者和观察目标又有哪些呢?
- 依赖的数据是观察目标
- 视图、计算属性、侦听器这些是观察者
差不多了,希望这篇文章对大家有帮助,如果发现有任何错漏的地方,也欢迎向我指出,谢谢大家~(鄙人才疏学浅,如有理解不到或是错误的地方还望各位看客见谅)
学习文章