1.命令式和声明式框架
命令式框架关注过程
声明式框架关注结果(底层对命令式的DOM获取和修改进行了封装)
2.vue2 Object.defineProperty()双向绑定的实现
<body>
<div id="app">
<input type="text" />
<h1></h1>
<button>按钮</button>
</div>
</body>
<script>
// vue2实现双绑
const input = document.getElementsByTagName('input')[0]
const h1 = document.getElementsByTagName('h1')[0]
const btn = document.getElementsByTagName('button')[0]
let data = { text: '' }
// input框输入数据,h1数据和text一致;实现点击按钮时,h1 标签数据和input框数据同时更改
Object.defineProperty(data, 'text', {
get() {
return data['text'];
},
set(value) {
// 获取到值后将h1后的内容设置为text
h1.innerText = value;
input.value = value;
return true;
}
});
input.oninput = function (e) {
data.text = e.target.value;
}
btn.onclick = function () {
data.text = "你好"
}
</script>
3.同样页面Vu3实现 new Proxy()
// vue3实现双绑
const input = document.getElementsByTagName('input')[0]
const h1 = document.getElementsByTagName('h1')[0]
const btn = document.getElementsByTagName('button')[0]
let data = { text: '' }
let obj = new Proxy(data, {
get(target, property) {
return target[property]
},
set(target, property, value) {
h1.innerText = value;
input.value = value;
return true;
}
})
input.oninput = function (e) {
obj.text = e.target.value;
}
btn.onclick = function () {
obj.text = "你好"
}
4.响应式数据的基本实现
响应式数据的关键是拦截对象属性的设置和读取操作
const data = { text: '' }
function effect () {
document.body.innerText = data.text
}
5. vue2与vue3响应式数据实现区别
- vue2的实现:当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用Object.defineProperty() 把这些 property 全部转为 getter/setter。
- vue3的实现:当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get 和 set 处理程序的Proxy 中
6.vue3 proxy的简单实现响应式数据拦截
// 初始数据
const data = { text: '' }
// 存储副作用函数的桶
const bucket = new Set()
// 对数据进行代理
const obj = new Proxy(data, {
get(target, key) {
bucket.add(effect)
return target[key]
},
set(target, key, newVal) {
target[key] = newVal
bucket.forEach(fn => fn())
return true
}
})
function effect () {
document.body.innerText = obj.text
}
effect()
setTimeout(() => {
obj.text = '你好'
}, 1000)
7.简单实现中出现的问题
思考一下 这一段代码的问题。
- 1. 副作用函数的名称被写死
- 2. 没有建立副作用函数和目标字段之前明确的关系
副作用函数的名称被写死——解决:
名称写死问题:真实情况下副作用函数不可能只有一个,那如果有多个函数时,每个函数执行都会调用一个副作用函数。比如,设置obj.a = 2,同样会调用set方法中的bucket.forEach(fn => fn())方法。
通用一个副作用作用函数,将执行DOM修改的函数以闭包形式(回调函数)传入副作用函数,这样每次返回的都不是同一个函数
let activeEffect
function effect(fn) {
activeEffect = fn
fn()
}
effect(() => {
document.body.innerText = obj.text
})
const obj = new Proxy(data, {
get(target, key) {
if (activeEffect) {
bucket.add(activeEffect)
}
return target[key]
},
set(target, key, newVal) {
target[key] = newVal
bucket.forEach(fn => fn())
return true
}
})
没有建立副作用函数和目标字段之前明确的关系——解决:
以上代码对每个属性都绑定了同一个副作用函数,实际上真实需要的时,修改text时,调用他自己的函数,修改a时又调用a对应的函数
解决:
- 使用Map键值数据结构(分两层)进行存储副作用函数,将每个数据对象对应一个map的键;
- 一个数据对象下不同属性又存储到一个Map数据下,这个map的值则存储的副作用函数(Set形式存)
- 使用时通过获取对象的map(数据对象)下的对应属性key(每个对象的key)的值的set数据(针对每个属性操作的副作用函数)并进行遍历执行即可
const obj = new Proxy(data, {
get(target, key) {
console.log(activeEffect, 'activeEffect')
// 没有副作用函数,容错处理 直接return
if (!activeEffect) return target[key]
// 判断桶中是否已经存在key与target的关联关系
let depsMap = bucket.get(target)
// 创建一个新的Map结构与 target 关联
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
// 判断当前Map数据中是否存在key与effect的关联关系
let deps = depsMap.get(key)
// 不存在的话 则新建一个Set 与 key关联
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 最后将当前激活的副作用函数添加到bucket中
deps.add(activeEffect)
return target[key]
},
set (target, key, newVal) {
target[key] = newVal
// 获取bucket下对应的数据
const depsMap = bucket.get(target)
if (!depsMap) return
// 根据key 获取副作用的执行函数
const effects = depsMap.get(key)
// 执行副作用函数
effects && effects.forEach(fn => fn())
}
})
effect(() => {
document.body.innerText = obj.text
})
effect(() => {
document.title = obj.a
})
8.完全实现——我的测试
const data = { text: '这是obj.title', a:'vue响应式数据的实现原理' }
// 存储副作用函数的桶
const bucket = new Map()
// 存储副作用函数的变量
let activeEffect;
// 对数据进行代理
const obj = new Proxy(data, {
get(target, key) {
// 设置副作用函数到map数据的桶中
// 判断不存在activeEffect直接返回
if (!activeEffect) return target[key];
// 存在activeEffect时将副作用函数设置到桶中
let targetMap = bucket.get(target); //target对象存在才能判断key是否存在(targetMap既是bucket的值targetMap = new Map()又是keyMap的键的定义)
if (!targetMap) {
bucket.set(target, (targetMap = new Map()));
}
let keyMap = targetMap.get(key); // keyMap既是targetMap的值又是
if (!keyMap) {
targetMap.set(key, (keyMap = new Set()))
}
keyMap.add(activeEffect); //副作用函数最终是存在Set结构里面的
return target[key]
},
set(target, key, newVal) {
target[key] = newVal;
//获取桶里面的对象Map的key的map集合的值,即所有的副作用函数进行循环执行
let targetMap = bucket.get(target);
if (!targetMap) return;
let effects = targetMap.get(key);
effects.forEach(fn => fn())
return true
}
})
function effect(fn) {
// 将函数赋值给activeEffect,数据劫持到activeEffect有值时,会将其设置到bucket存储副作用的桶中,当拦截到数据更改时再获取对应函数并执行
activeEffect = fn;
fn();
}
// 副作用函数执行一次
effect(() => {
document.body.innerText = obj.text
})
effect(() => {
document.title = obj.a
})