淘宝客怎么建立网站,人防pc网站开发计划书,平面设计岗位职责,博物馆文化网站建设UI render(state)
VUE的特点是数据驱动视图#xff0c;在这里可以把数据理解为状态#xff0c;而视图就是用户可以看到的页面#xff0c;页面是动态变化的#xff0c;而他的变化或是用户操作引起#xff0c;或是后端数据变化引起#xff0c;这些都可以说是数据的状态变…UI render(state)
VUE的特点是数据驱动视图在这里可以把数据理解为状态而视图就是用户可以看到的页面页面是动态变化的而他的变化或是用户操作引起或是后端数据变化引起这些都可以说是数据的状态变了导致页面随之变化。
把顶部公式拆为三部分UI、render、state。state和UI都是用户定的不变的是render所以VUE就担任了这个角色当VUE发现state变化后就通过一系列操作把他反应在了UI上。
那么VUE是如何发现state变化的引出了VUE的变化侦测
JS为我们提供了object.defineProperty()方法通过此方法我们可以知道数据什么时候发生了变化。
1.Object数据变得可观测
数据每次读写能够被我们看到我们能知道数据什么时候发生了变化将其称为数据的可观测。通过使用上述对象方法可以实现。如下
let person {}
let nameval lwh
Object.defineProperty(person,name,{get() {console.log(name被读取了)return nameval}set(newval) {console.log(name被修改了)nameval newval
}
})
通过Object.defineProperty()方法定义了person的Name属性并把这个属性的读写分别使用get和set方法进行拦截每当该属性进行读或写操作时候就会触发这两个方法意味着person的数据对象已经是可观测的了
但是为了将此对象的所有属性都变得可观测可以编写以下代码
export class Observe {constructor(value) {this.value value//给value新增一个__ob__属性值为该value的observe实例//相当于给value打上标记表示他已经转化为响应式了def(value,__ob__,this)if (Array.isArray(value)) {//为数组的逻辑} else {this.walk(value)}}walk(obj: Object) {const keys Object.keys(obj)for (let i 0; i keys.length; i) {defineReactive(obj,key[i])}}
}
function defineReactive(obj, key, val) {if(typeof val object) {new Observable(val)}object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {console.log(${key}属性被读取)return val;},set(newval) {if(val newval) {return}console.log(${key}属性被修改了)val newval}})
}
上面的代码中我们定义了observe类用来将一个普通的Object转化为可观测的object。并且给value新增一个ob属性表示这个已经被转化成响应式了。
然后判断数据类型只有Object 类型才会调用walk方法将每一个属性都转换成getter/setter形式来侦测变化最后在defineReactive中当传入的属性值还是一个object时使用new observe(val)来递归子属性这样我们就可以把obj中所有属性都变为可观测型的只要将object传入observe中那么这个object就会变成可观测响应式的Object.
observer类位于Vue2源码的src/core/observer/index.ts中。
2.依赖收集
当Object变得可观测后我们就能知道数据什么时候发生了变化发生了什么变化这样就可以通知视图更新。但是问题来了我们怎么知道哪些视图需要更新总不能一个数据变化全部视图全部更新一次吧。所以应该是视图里哪个使用到了这个数据的就更新。
视图里谁用到了这个数据谁更新说官方一点就是谁依赖了这个数据谁更新。我们建立一个依赖数组因为一个数据可能被多个地方使用所以需要建立数组谁依赖了这个数据就把谁放进这个数组。当数据变化时就根据它的依赖数组去通知哪些视图需要更新。这个过程就是依赖收集。
那么我们该如何进行依赖收集如何知道哪些依赖这个数据
谁用了这个数据那么当这个数据变化时就通知谁所谓谁用了这个数据就是谁获取了这个数据。根据上文可观测数据在被获取时会调用get方法那么我们就可以在get中收集这个依赖。同样数据变化会触发Set,所以我们就在set中通知更新。
所以我们应该给每个数据都配置一个依赖数组谁依赖了它就放入谁。不过单单使用数组的话功能可能有点欠缺且代码有点耦合。所以拓展一下的话我们给每一个数据建立一个类用作依赖管理。所以在vue2源码中有了这个依赖管理器Dep类代码如下
// 源码位置src/core/observer/dep.ts
export default class Dep {constructor () {this.subs []}addSub (sub) {this.subs.push(sub)}// 删除一个依赖removeSub (sub) {remove(this.subs, sub)}// 添加一个依赖depend () {if (window.target) {this.addDep(window.target)}}// 通知所有依赖更新notify () {const subs this.subs.slice()for (let i 0, l subs.length; i l; i) {subs[i].update()}}
}
/*** Remove an item from an array*/
export function remove (arr, item) {if (arr.length) {const index arr.indexOf(item)if (index -1) {return arr.splice(index, 1)}}
}
在上面依赖管理器中初始化了一个sub数组用来存放依赖并且定义了几个附加方法。
有了依赖管理器后我们就可以在getter中收集依赖setter中通知更新了
function defineReactive (obj,key,val) {if(typeof val object){new Observable(val)}const dep new Dep() //实例化一个依赖管理器生成一个依赖管理数组depObject.defineProperty(obj, key, {enumerable: true,configurable: true,get(){dep.depend() // 在getter中收集依赖return val;},set(newVal){if(val newVal){return}val newVal;dep.notify() // 在setter中通知依赖更新}})
}
接下来需要知道的是我们收集的依赖到底是谁
谁用到了这个数据谁就是依赖那代码上该如何描述这个谁呢
在Vue中还实现了一个叫做Watcher的类这个类的实例就是我们上面说的那个谁。换句话说谁用到了数据我们就为谁创建一个Watcher实例。在之后数据变化时我们不直接去通知依赖更新而是去通知依赖对应的watch实例由watcher实例去通知真正的视图
类的具体实现如下
export default class Watcher {constructor (vm,expOrFn,cb) {this.vm vm;this.cb cb;this.getter parsePath(expOrFn)this.value this.get()}get () {window.target this;const vm this.vmlet value this.getter.call(vm, vm)window.target undefined;return value}update () {const oldValue this.valuethis.value this.get()this.cb.call(this.vm, value, oldValue)}
}
/*** Parse simple path.* 把一个形如data.a.b.c的字符串路径所表示的值从真实的data对象中取出来* 例如* data {a:{b:{c:2}}}* parsePath(a.b.c)(data) // 2*/
const bailRE /[^\w.$]/
export function parsePath (path) {if (bailRE.test(path)) {return}const segments path.split(.)return function (obj) {for (let i 0; i segments.length; i) {if (!obj) returnobj obj[segments[i]]}return obj}
}
分析代码
1.实例化Watcher类时会先执行其构造函数
2.在构造函数中调用了this.get()实例方法
3.在get()方法中首先通过window.target this 把实例自身赋给了全局的一个唯一对象window.target上然后通过let value this.getter.call(vm,vm)获取一下被依赖的数据这里的目的是触发该数据的getter在上文说过调用getter会随之调用dep.depend()方法收集依赖而在dep.depend()上取到挂载到window.target的值并将其存到依赖数组中在get()方法最后需要将window.target释放掉
4.当数据变化时会触发setter在其中执行了dep.notify()方法在这个方法中遍历所有的依赖即watcher实例执行依赖的update()方法在方法中调用数据变化的更新回调函数从而更新视图。
3.不足之处
通过defineProperty方法实现了对Object数据的观测但是这个方法仅仅只能观测到其中数据及设置值如果向Object中添加或删除值时他是无法观察到的导致添加或者删除值时无法通知依赖无法驱动视图更新。vue也注意到了这一点所以为了解决这个问题vue添加了两个全局api$set $delete在后面学习全局API时会说到。
4.总结
通过Object.defineProperty方法实现了对object数据的可观测并且封装了Observer类让我们能够方便的把object数据中的所有属性包括子属性都转换成getter/seter的形式来侦测变化。
接着知道了在getter中收集依赖在setter中通知依赖更新以及封装了依赖管理器Dep用于存储收集到的依赖。
最后我们为每一个依赖都创建了一个Wtcher实例当数据发生变化时通知Watcher实例由Watcher实例去做真实的更新操作。
其整个流程大致如下 Data通过observer转换成了getter/setter的形式来追踪变化。 当外界通过Watcher读取数据时会触发getter从而将Watcher添加到依赖中。 当数据发生了变化时会触发setter从而向Dep中的依赖即Watcher发送通知。 Watcher接收到通知后会向外界发送通知变化通知到外界后可能会触发视图更新也有可能触发用户的某个回调函数等。