(一)变动侦测
- 初始化
- Object变动侦测
- Array变动侦测
observe流程图
1、初始化
- 定义Vue构造函数
- 向Vue原型混入操作方法,不便前期扩大
- 在初始化函数中进行 state初始化 -> data初始化
// index.jsimport {initMixin} from "init.js"const Vue = function(options){ // 选项初始化 this._init(options);}// 向Vue原型混入操作方法initMixin(Vue);...export default Vue;
// init.jsimport {initState} from "state.js"export function initMixin(Vue){ Vue.prototype._init = function(options){ const vm = this; vm.$options = options; // 初始化状态 initState(vm); }}
// state.js 初始化状态import {observe} from 'observe/index.js'export function initState(vm){ const opt = vm.$options; if(opt.data){ // 初始化data initData(vm); }}function initData(vm){ let data = vm.$options.data; // 判断data是否为函数 data = typeof data === 'function' ? data.call(vm) : data; // 对对立后的data对象从新挂载在vm实例上 vm._data = data // 数据侦测与劫持 observe(data);}
2、Object变动侦测
Object.defineProperty()毛病
- 不能侦测新增与删除属性
2.1 数据劫持
// observe/index.js// 对数据进行侦测/重写 返回可侦测对象export function observe(data){ // 非对象类型不进行劫持 if(typeof data != 'object' || data == null) return; return new Observe(data);}// 数据侦测类class Observe { constructor(data){ this.walk(data); } // 对象数据劫持 - 相当于重写 性能瓶颈 walk(){ Object.keys.forEach(key => { defineReactive(data,key,data[key]) }) }}// 数据劫持公共办法export const defineReactive(target,key,value){ Object.defineProperty(target,key,{ enumerable:true, // 默认也为true configurable:true, // 同上 get(){ return value; // 闭包 }, set(newVal){ if(newVal === value) return; value = newVal; } })}
此时咱们能够通过observe办法对传入的对象进行数据侦测,劫持数据的取值
与更改
然而数据是在_data
上的,为了开发模式语法尽量简洁,这里须要数据代理
2.2 数据代理
// state.js 对initData进行补充function initData(vm){ //...other code for(let key in data){ proxy(vm,'_data',key); }}function proxy(vm,target,key){ Object.defineProperty(vm,key,{ get(){ return vm[target][key]; }, set(newVal){ vm[target][key] = newVal; } })}
const vm = new Vue({ data(){ return { name:"foo", age:11 } }})
当咱们执行以上代码时能够在vm上读取到name属性,并且name与age都领有
getter
与setter
2.3 深度侦测与新值侦测
当data中的值是嵌套
的对象,以及对data属性设置对象
值时,咱们心愿依然对其进行侦测,
并且对于曾经侦测的数据不再进行重写
// obseve/index.jsexport function observe(data){ ... // data曾经存在了Observe的一个实例 阐明曾经被侦测过 if(data.__ob__ instanceof Observe) return data.__ob__ ...}class Observe { constructor(data){ Object.defineProperty(data,"__ob__",{ value:this, // 间接应用实例赋值 前面数组侦测须要该属性 enumerable:true // 避免深度侦测时 递归爆栈 }) ... }}export const defineReactive = function(target,key,value){ // 深度侦测 observe(value); Object.defineProperty(target,key,{ ... set(newVal){ ... // 新值侦测 observe(value); ... } })}
3、Array变动侦测
上述observe流程图中的hasMethod判断是指数组调用的办法是否在被重写列表中
数组个别数据元素较多,如果一一下标进行侦测,会节约性能,因为相较于对下标的批改咱们更常应用的是数组办法批改
留神并不是Object.defineProperty
不能侦测,而是Vue在设计时摈弃了侦测下标这种形式
- 数组中
援用数据类型
的元素仍然应用observe进行侦测 - 对能批改原数组的办法进行
切面补充
(原型链继承的形式)
// observe/index.jsimport {newArrayProto} from "observe/array.js"...class Observe { constructor(data){ ... //数组类型判断 if(Array.isArray(data)){ // 设置data的原型对象 Object.setPrototypeOf(data,newArrayProto) this.observeArray(data) }else { } } // 数组劫持 observeArray(data){ // 元素为数组/对象进行递归劫持 data.forEach(item => observe(item)) }}...
// observe/array.jslet originArrayProto = Array.prototype// 创立一个以originArrayProto为原型的对象export let newArrayProto = Object.create(originArrayProto);// 批改数组的7种办法const methods = ["push","pop","unshift","shift","sort","splice","reverse"]methods.forEach(method => { newArrayProto[method] = function(..args){ // 调用原始办法 const result = originArrayProto[method].apply(this,args); const ob = this.__ob__; // __ob__为上述增加的Observe实例 // 数据劫持新数据 let inserted; switch(method){ case 'push': case 'unshift': inserted = args; breake; case 'splice': inserted = args.slice(2);// 第三个参数为新数据 break; } // 新数据侦测 if(inserted){ ob.observeArray(inserted); } return result; }})
通过原型链继承将中间层对象设置为原数据的原型对象,是一种面向切面编程的形式,只重写局部办法__ob__
是原数据的一个属性,值为Observe的实例,能够通过它劫持新增的元素数组(十分优雅 也有点恶心)