5. vue 响应式原理
2020-09-30 本文已影响0人
一土二月鸟
数组和对象类型当值变化时如何劫持到?
- 对象内部通过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。
- 多层对象是通过递归来实现劫持,Vue3中是使用proxy来实现响应式数据。
内部依赖收集是怎样做到的?
- 每个属性都拥有自己的dep属性,存放他所依赖的watcher,当属性变化后会通知自己对应的watcher去更新
- 1)对象层级过深,性能就会差 (2)不需要响应数据的内容不要放到data中 (3)Object.freeze() 可以冻结数据(4)需要响应的数据需要在defineProperty运行之前定义。
- 对象响应式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript">
let data = { count: 1 };
let tempFn = null;
const defineReactive = data => {
for (let key in data) {
const dep = []; // 为每一个属性设置单独的回调工厂,get时,添加回调。set时,执行所有的回调。即实现了reactive。
let value = data[key]; // 此处的data[key] 为初始值
Object.defineProperty(data, key, {
set(val) {
value = val;
dep.forEach(fn => fn());
return val;
},
get() {
if(tempFn) dep.push(tempFn);
return value;
}
})
}
}
defineReactive(data); // 这也是为什么注册响应式属性时,要有初始属性的原因。如果提前没有定义属性,则无法通过defineReactive对该属性预设逻辑。
const watcher = (fn) => {
tempFn = fn;
fn();
tempFn = null; // 挂载完函数,将其清空,否则在运行get方法时,会添加重复的函数到fnArr中
}
watcher(() => { // 注册函数
test.innerHTML = data.count; // 执行此行代码时,会触发get方法,则会将缓存的tempFn存储到fnArr中
});
watcher(() => {
console.log(data.count); // 执行此行代码时,会触发get方法,则会将缓存的tempFn存储到fnArr中
});
</script>
</body>
</html>
数组响应式
- 因为调用数组是通过api方法的方式,通过改写数组对象的原型链,实现拦截方法的目的。
- 数组不适用defineProperty的原因是数组的属性为自己的索引,如果数组的数据量特别大,不适合给每个索引进行定义,性能太差。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript">
const arr = [];
function defineReactive(arr) {
let arrayMethods = Object.create( Array.prototype ); // 使用Array.prototype是为了保留数组的其他方法可供arr使用
arr.__proto__ = arrayMethods; // arr 的原型的原型为Array.prototype
arrayMethods.push = function (val) {
Array.prototype.push.call(this, val);
render();
}
}
function render() {
test.innerHTML = arr;
}
defineReactive(arr);
arr.push(123);
</script>
</body>
</html>