这次给大家带来vue做出observer有哪些方法,vue做出observer的注意事项有哪些,下面就是实战案例,一起来看一下。
导语:
本文是对 vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程。
响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程。依赖收集的过程,有三个很重要的类,分别是 watcher、dep、observer。本文主要解读 observer 。
这篇文章讲解上篇文章没有覆盖到的 observer 部分的内容,还是先看官网这张图:
observer 最主要的作用就是实现了上图中touch -data(getter) - collect as dependency这段过程,也就是依赖收集的过程。
还是以下面的代码为例子进行梳理:
(注:左右滑动即可查看完整代码,下同)
varvm = newvue({el: '#demo',data: {firstname: 'hello',fullname: ''},watch: {firstname(val) {this.fullname = val + 'talkingdata';},}})
在源码中,通过还原vue 进行实例化的过程,从开始一步一步到observer 类的源码依次为(省略了很多不在本篇文章讨论的代码):
// src/core/instance/index.jsfunctionvue(options) {if(process.env.node_env !== 'production'&&!(thisinstanceofvue)) {warn('vue is a constructor and should be called with the `new` keyword')}this._init(options)}// src/core/instance/init.jsvue.prototype._init = function(options?: object) {constvm: component = this// ...initstate(vm)// ...}// src/core/instance/state.jsexportfunctioninitstate(vm: component) {// ...constopts = vm.$optionsif(opts.data) {initdata(vm)}// ...}functioninitdata(vm: component) {letdata = vm.$options.datadata = vm._data = typeofdata === 'function'? getdata(data, vm): data || {}// ...// observe dataobserve(data, true/* asrootdata */)}
在initdata 方法中,开始了对data 项中的数据进行“观察”,会将所有数据的变成observable 的。接下来看observe 方法的代码:
// src/core/observer/index.jsfunctionobserve(value: any, asrootdata: ?boolean): observer| void{// 如果不是对象,直接返回if(!isobject(value) || value instanceofvnode) {return}letob: observer | voidif(hasown(value, '__ob__') && value.__ob__ instanceofobserver) {// 如果有实例则返回实例ob = value.__ob__} elseif(// 确保value是单纯的对象,而不是函数或者是regexp等情况observerstate.shouldconvert &&!isserverrendering() &&(array.isarray(value) || isplainobject(value)) &&object.isextensible(value) &&!value._isvue) {// 实例化一个 observerob = newobserver(value)}if(asrootdata && ob) {ob.vmcount++}returnob}
observe 方法的作用是给data 创建一个observer 实例并返回,如果data 有ob属性了,说明已经有observer 实例了,则返回现有的实例。vue 的响应式数据都会有一个ob的属性,里面存放了该属性的observer 实例,防止重复绑定。再来看new observer(value) 过程中发生了什么:
exportclassobserver{value: any;dep: dep;vmcount: number; // number of vms that has this object as root $dataconstructor(value: any) {this.value = valuethis.dep = newdep()this.vmcount = 0def(value, '__ob__', this)if(array.isarray(value)) {// ...this.observearray(value)} else{this.walk(value)}}walk (obj: object) {constkeys = object.keys(obj)for(leti = 0; i < keys.length; i++) {definereactive(obj, keys[i], obj[keys[i]])}}observearray (items: array<any>) {for(leti = 0, l = items.length; i < l; i++) {observe(items[i])}}}
通过源码可以看到,实例化observer 过程中主要是做了两个判断。如果是数组,则对数组里面的每一项再次调用oberser 方法进行观察;如果是非数组的对象,遍历对象的每一个属性,对其调用definereactive 方法。这里的definereactive 方法就是核心!通过使用object.defineproperty 方法对每一个需要被观察的属性添加get/set,完成依赖收集。依赖收集过后,每个属性都会有一个dep 来保存所有watcher 对象。按照文章最开始的例子来讲,就是对firstname和fullname分别添加了get/set,并且它们各自有一个dep 实例来保存各自观察它们的所有watcher 对象。下面是definereactive 的源码:
exportfunctiondefinereactive(obj: object,key: string,val: any,customsetter?: ?function,shallow?: boolean) {constdep = newdep()// 获取属性的自身描述符constproperty = object.getownpropertydeor(obj, key)if(property && property.configurable === false) {return}// cater for pre-defined getter/setters// 检查属性之前是否设置了 getter/setter// 如果设置了,则在之后的 get/set 方法中执行设置了的 getter/setterconstgetter = property && property.getconstsetter = property && property.set// 通过对属性再次调用 observe 方法来判断是否有子对象// 如果有子对象,对子对象也进行依赖搜集letchildob = !shallow && observe(val)object.defineproperty(obj, key, {enumerable: true,configurable: true,get: functionreactivegetter() {// 如果属性原本拥有getter方法则执行constvalue = getter ? getter.call(obj) : valif(dep.target) {// 进行依赖收集dep.depend()if(childob) {// 如果有子对象,对子对象也进行依赖搜集childob.dep.depend()// 如果属性是数组,则对每一个项都进行依赖收集// 如果某一项还是数组,则递归if(array.isarray(value)) {dependarray(value)}}}returnvalue},set: functionreactivesetter(newval) {// 如果属性原本拥有getter方法则执行// 通过getter方法获取当前值,与新值进行比较// 如果新旧值一样则不需要执行下面的操作constvalue = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if(newval === value || (newval !== newval && value !== value)) {return}/* eslint-enable no-self-compare */if(process.env.node_env !== 'production'&& customsetter) {customsetter()}if(setter) {// 如果属性原本拥有setter方法则执行setter.call(obj, newval)} else{// 如果原本没有setter则直接赋新值val = newval}// 判断新的值是否有子对象,有的话继续观察子对象childob = !shallow && observe(newval)// 通知所有的观察者,更新状态dep.notify()}})}
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
怎样对webpack4.0进行打包优化
做出json与数组键值大小写转换
以上就是vue做出observer有哪些方法的详细内容。