命令式框架关注过程
声明式框架关注结果(底层对命令式的DOM获取和修改进行了封装)
- <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>
- // 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 = "你好"
- }
响应式数据的关键是拦截对象属性的设置和读取操作
- const data = { text: '' }
- function effect () {
- document.body.innerText = data.text
- }
- // 初始数据
- 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)
思考一下 这一段代码的问题。
名称写死问题:真实情况下副作用函数不可能只有一个,那如果有多个函数时,每个函数执行都会调用一个副作用函数。比如,设置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对应的函数
解决:

- 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
- })
- 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
- })