开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
前言
关于双向绑定,这个词出现的频率太高了。有的人也会称他为vue reactive也就是vue响应式,其实他们的底层都是一个东西。相信大家对这个东西都不陌生。
要实现一个源码,首先我们要先弄懂 原理和流程,这样子我们才能快速实现
提出几个问题?
- vue是双向数据绑定,react是单向数据流,为什么这样设计,有什么优缺点?
- 热更新原理和双向数据绑定相同点
原理
双向绑定的原理可以理解为副作用+桶+数据劫持
副作用函数
是的,因为他要改变dom,那肯定是有副作用的了
function effect(){
document.body.innerText = 'hello world';
}
但是这样写太简单了,因为我们的副作用函数多种多样,有可能是匿名函数,有可能使用名称的,所以我们写一个副作用函数注册的函数
function effect(fn){
fn();
}
现在我们想添加什么副作用函数,直接添加到effect中就行了
桶
桶可以理解为存储数据的地方
const data = {text: 'hello world'}
我们要将这段数据与副作用函数建立绑定,对应的数据关系可以理解为
target---->key---->effect
为什么要建立数据对应关系呢?
因为只有这样,你才知道哪一个
key值改变了,要更新哪一个副作用
target就是这一段数据,因为我们要以这一段数据做数据绑定
key就是text,因为这个副作用是和这个key建立的联系
effect那就是副作用了
这是一种属性结构,所以我们使用map来进行存储
为了能让表达式执行完毕以后,副作用函数和变量能够被垃圾回收,所以我们使用weakMap,而key和effect的对应关系是一对多的关系,所以使用set进行存储
const bucket = new WeakMap() // 副作用函数的桶 使用WeakMap
数据劫持
数据劫持我们使用proxy实现
他主要分为两个步骤
- 当读取操作发生时,将副作用函数收集到桶中
- 当设置操作发生时,从桶中取出副作用函数并执行
const obj = new Proxy(data, {
get: (target, key) => {
let depsMap = bucket.get(target)
if (!depsMap) { // 不存在,则创建一个Map
bucket.set(target, depsMap = new Map())
}
let deps = depsMap.get(key) // 根据key得到 depsSet(set类型), 里面存放了该 target-->key 对应的副作用函数
if (!deps) { // 不存在,则创建一个Set
depsMap.set(key, (deps = new Set()))
}
deps.add(activeEffect) // 将副作用函数加进去
return target[key];
},
set: (target, key, value) => {
// 设置属性值
target[key] = value;
const depsMap = bucket.get(target) // target Map
if (!depsMap) return;
const effects = depsMap.get(key) // effectFn Set
effects && effects.forEach(fn => fn())
}
})
先监听,再改变
此时我们可以往副作用函数中添加监听的数据
effect(() => {
console.log('effect run')
document.body.innerHTML = obj.text
})
然后一秒钟以后改变数据
setTimeout(() => {
obj.text = 'heeeeeeeeee'
}, 1000)
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 要建立绑定的数据
const data = {text: 'hello world'};
// 当前的副作用, 建立对应关系的时候要用
let activeEffect;
// 桶
const bucket = new WeakMap();
// 建立响应式注册函数
function effect(fn){
activeEffect = fn;
fn();
}
// 建立数据劫持
const obj = new Proxy(data, {
get: (target, key) => {
// 把数据放进桶中
let depsMap = bucket.get(target);
if(!depsMap){
bucket.set(target, depsMap = new Map());
}
let deps = depsMap.get(key);
if(!deps){
depsMap.set(key, deps = new Set());
}
deps.add(activeEffect)
// 返回要访问的值
return target[key];
},
set: (target, key, value)=>{
// 设置值
target[key] = value;
// 执行对应的副作用函数
const depsMap = bucket.get(target);
if(!depsMap) return;
const deps = depsMap.get(key);
if(!deps) return;
deps.forEach((dep)=> dep());
}
})
// 监听
effect(()=>{
document.body.innerHTML = obj.text;
})
// 改变
setTimeout(() => {
obj.text = 'heeeeeeeeee'
}, 1000)
</script>
</html>
参考
- vue.js设计与实现