共计 3365 个字符,预计需要花费 9 分钟才能阅读完成。
(一)变动侦测
- 初始化
- Object 变动侦测
- Array 变动侦测
observe 流程图
1、初始化
- 定义 Vue 构造函数
- 向 Vue 原型混入操作方法,不便前期扩大
- 在初始化函数中进行 state 初始化 -> data 初始化
// index.js
import {initMixin} from "init.js"
const Vue = function(options){
// 选项初始化
this._init(options);
}
// 向 Vue 原型混入操作方法
initMixin(Vue);
...
export default Vue;
// init.js
import {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.js
export 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.js
import {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.js
let 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 的实例,能够通过它劫持新增的元素数组(十分优雅 也有点恶心)
正文完