侦测对象的变化
Object.defineProperty
function defineReactive (data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
}
})
}
data是一个对象,从data 的key 中读取数据时,get 函数被触发;每当往data 的key 中设置数据时,set 函数被触发。
上面的函数对一个key值进行操作,下面的Observer则可以对所有key值进行操作
Observer
作用是将一个数据内的所有属性(包括子属性)都转换成getter/setter的形式,然后去追踪它们的变化
export class Observer{
constructor(value){
this.value = value
if(!Array.isArray(value)){
this.walk(value)
}
}
walk(obj){
const keys = Object.keys(obj)
for(let i = 0;i< keys;k++){
defineReactive(obj,keys[i],obj[keys[i]])
}
}
defineReactive改为
function defineReactive (data, key, val) {
//新增
if(typeof val === 'object'){
new Observer(val)
}
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
}
})
}
上述新增,是对每个对象的值进行递归进行key的getter与setter的绑定。
这种方法,在后续的给对象添加或删除属性,无法追踪到后续添加或删除属性的变化,Vue.js提供了两个API——vm.delete来解决这个问题
收集依赖Dep
在getter中收集依赖,在setter中触发依赖 。
为了收集每个key的依赖,将引入一个数组,这里定义为dep,假设每个依赖是一个函数,则每个key都对应一个函数依赖,所有依赖都收集到dep里面之后。每一次key所对应的值改变,则去触发dep里面的依赖。所以上方程序则可以改造为:
function defineReactive (data, key, val) {
if(typeof val === 'object'){
new Observer(val)
}
let dep = [] //新增
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.push(依赖) //新增
return val
},
set: function (newVal) {
if(val === newVal){
return
}
//新增
for(let i = 0; i < dep.length; i++){
dep[i](newVal,val)
}
val = newVal
}
})
}
对于上方的代码有一个专门收集依赖的,可以将其封装为专门对依赖处理的一个类,如下代码:
export default class Dep {
constructor(){
this.subs = []//用来存储依赖
}
//收集依赖
depend(sub){
this.subs.push(sub)
}
//执行依赖
notify(){
const subs = this.subs.slice()
for(let i = 0;i < subs.length;i++){
subs[i].update()//执行依赖
}
}
}
改造defineReactive函数
function defineReactive (data, key, val) {
if(typeof val === 'object'){
new Observer(val)
}
let dep = new Dep() //修改,实例化依赖
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend(依赖) //修改,收集依赖
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
dep.notify() //修改,触发依赖
}
})
}
上方的依赖是谁,即属性发生改变之后,通知谁。通知下方的watcher
Watcher
数据发生变化时通知它,然后它再通知其他地方。
所谓的依赖,其实就是Watcher 。只有Watcher 触发的getter才会收集依赖,哪个Watcher 触发了getter,就把哪个Watcher 收集到Dep 中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。
Watcher 的原理是先把自己设置到全局唯一的指定位置(例如window.target ),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局唯一的那个位置读取当前正在读取数据的Watcher ,并把这个Watcher 收集到Dep 中去。通过这样的方式,Watcher 可以主动去订阅任意一个数据的变化。
export class Watcher{
constructor(vm,expOrFn,cb){
this.vm = vm,
this.expOrFn = expOrFn
this.cb = cb
this.get()
}
//为了收集这个watcher===this,读取一遍这个值this.vm[this.expOrFn],将会触发## Object.defineProperty里面的get函数,在到get函数里面将这个watcher收集Dep中
get(){
Dep.target = this
this.vm[this.expOrFn]
Dep.target = null
}
//执行,当前这个回调函数
update(){
const val = this.vm[this.expOrFn]
this.cb.call(this.vm,val)
}
}
改造defineReactive函数
function defineReactive (data, key, val) {
if(typeof val === 'object'){
new Observer(val)
}
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
Dep.target&&dep.depend(Dep.target) //修改,收集Watcher
return val
},
set: function (newVal) {
if(val === newVal){
return
}
val = newVal
dep.notify() //修改,触发收集的Watcher依赖
}
})
}
Data 、Observer 、Dep 和Watcher 之间的关系。
Data 通过Observer 转换成了getter/setter的形式来追踪变化。
当外界通过Watcher 读取数据时,会触发getter从而将Watcher 添加到依赖中。
当数据发生了变化时,会触发setter,从而向Dep中的依赖(Watcher )发送通知。
Watcher 接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。
Array 的变化侦测
数组无法用defineProperty进行劫持
解决方法:用自定的方法覆盖原型方法。可以用一个拦截器覆盖Array.prototype 。之后,每当使用Array 原型上的方法操作数组时,其实执行的都是拦截器中提供的方法
Array 原型中可以改变数组自身内容的方法有7个,分别是push 、pop 、shift、unshift 、splice 、sort 和reverse 。
//获取数组原型
const arrayProto = Array.prototype
//复制一个数组原型出来
export const arrayMethods = Object.create(arrayProto)
//遍历数组需要替换的方法
['push','pop','shift','unshift','splice','sort','reverse'].forEach(function(method){=
Object.defineProperty(arrayMethods,method,{
value:function muator(...args){
//这里可以做相应拦截之后的操作,然后讲传进来的值交给原生数组的方法进行处理
return arrayProto[method].apply(this,args)
},
enumerable:false,
writable:true,
configurable:true
})
})
改写Observer,使其对数组进行相应的操作
export class Observer{
constructor(value){
this.value = value
//修改
if(Array.isArray(value)){
//用自己封装好的拦截数组的方法替换原有数组原型上的方法
value.__proto__ = arrayMethods
}else{
this.walk(value)
}
}
walk(obj){
const keys = Object.keys(obj)
for(let i = 0;i< keys;k++){
defineReactive(obj,keys[i],obj[keys[i]])
}
}