Vue源码解析-响应式原理(一)
2021-01-11 本文已影响0人
Raral
Vue 响应式
概述
数据和视图相互关联,相互依赖,数据变化了,视图也随之更新,视图改变了数据也会随之改变。
Vue2.0 响应式原理
Vue2.0的响应式原理核心是通过es5的Object.defineProperty
中的访问属性中的get和set方法,data中声明的属性都被添加了访问器属性,当读取data中的数据时自动调用get方法,当修改data中的数据时,会自动调用set 方法,检测到数据的变化,会通知观察者Wacher
,观察者会自动触发重新render当前组件,生成新的虚拟DOM树,Vue框架会遍历并对比新的虚拟DOM树和旧的虚拟DOM树中每个节点的差别,并记录下来,最后加载操作,将所有记录的不同点,局部修改到真实DOM树上。
整体过程分3部分
- 数据劫持 / 数据代理
- 依赖收集
- 发布订阅模式
原理图
vue.png手写Vue2.0 响应式--数据代理(数据劫持)
function render() {
console.log("视图更新")
}
//重写数组方法
let methods = ["pop","push","unshift","shift","sort","reverse","splice"];
let arrayProto = Array.prototype;
let proto = Object.create(arrayProto);
methods.forEach(method => {
proto[method] = function() {
arrayProto[method].call(this, ...arguments);
}
})
//观察者
function observe(obj) {
//判断是否数组,如果是改变数组的原型
if(Array.isArray(Obj)) {
obj._proto_ = proto;
return
}
if(!obj || typeof obj !=="object") {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj,key, obj[key])
})
}
//使用Object.defineProerty 拦截数据/代理数据
function defineReactive(data, key, value) {
observe(value);//递归子属性
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get:function getter() {
return value;
},
set: function setter(newValue) {
if(newValue != value) {
value = newValue;
render();//更新视图
}
}
})
}
let data = {
name: 'zhangsan',
location: { x: 100, y: 100 }
}
observe(data);
注意
-
无法检测到对象属性的添加或删除(如data.location.a=1)。
这是因为 Vue 通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。如果是删除属性,我们可以用vm.$delete实现,那如果是新增属性,该怎么办呢?
- 可以使用 Vue.set(location, a, 1) 方法向嵌套对象添加响应式属性;
- 也可以给这个对象重新赋值,比如data.location = {...data.location,a:1}
-
Object.defineProperty 不能监听数组的变化,需要进行数组方法的重写
- 我们需要通过对原型方法增强(代理)
proxy实现 数据劫持/数据代理
Proxy 是 JavaScript 2015 的一个新特性。Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性,Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外Proxy支持代理数组的变化。
function render() {
console.log("视图更新")
}
function observe(data) {
let handler = {
get(target, key) {
if(typeof target[key] == "object" && target[key] !== null) {
return new Proxy(target[key], handler);
}
return Reflect.get(target, key);
},
set(target, key, value) {
if(key === "length") return true;
render();
return Reflect.set(target, key, value);
}
}
let proxy = new Proxy(data, handler);
return proxy;
}
let data = {
name: 'zhangsan',
location: { x: 100, y: 100 },
arr: [1,3,5]
}
let proxy = observe(data);
console.log(proxy);
console.log(proxy.name);//zhangsan
proxy.arr[0] = "lisi"// 视图更新
proxy.arr.push(100)// 视图更新
console.log(proxy.arr)//['lisi', 3, 5, 100]