《Vue.js 技术与实现》第4章 响应式系统的作用和实现 阅读总结

Vue.js前端开发实战-自测卷和课后习题答案.rar 立即下载

☁️ 2022.03.20 15:30

这一章题序中上来就抛出了如下问题:

  • 什么是响应式数据和副作用函数?
  • 实现中如何避免无限递归?
  • 为什么需要嵌套的副作用函数?
  • 两个副作用函数之间会产生哪些影响?

花费一个晚上+一个下午读完了这一章,读完的第一感觉是信息量巨大。即使之前自己实现过一遍属于自己的 mini-vue,看完本章依然觉得填补了我很多知识盲区,收获很多,但一时很难消化。

此刻难消化的原因:很难在头脑中重现源码中针对哪些问题给出了哪些解决方案?

对于看完之后合上书本让人感觉虚空的问题,决定用自己方式再走一遍!整理读书笔记加手动源码实现。

梳理本章解决了哪些问题:

  • 如何实现一个完善的响应式系统
  • 分支切换和 cleanup,effects 依赖在收集时如何避免无限循环?
  • 嵌套的 effect 的如何处理
  • 如何避免无限递归循环
  • 调度执行 scheduler 是什么?解决了什么问题?
  • computed API 的实现原理?
  • watch API 的实现原理?
  • 如何处理 watch api 中的立即执行,post 异步执行
  • watch 函数中如何解决竞态问题?

如何实现一个完善的响应式系统

抛开响应式系统的细节处理,实现一个基本的响应式系统,应该是比较简单的。

ES6 proxy 尝试

const target = {
  text: "Hello World!",
};
const proxy = new Proxy(target, {
  get(target, key, receiver) {
    return target[key];
  },
  set(target, key, value, receiver) {
    target[key] = value;
  },
});

console.log(proxy.text); // Hello World!

proxy.text = "Hello Vue3!"

console.log(proxy.text); // Hello Vue3! 

可以看到 proxy 和原来 Vue2 使用的 Object.defineProperty() 差不多,都可以对属性进行劫持。

需要注意的是:

Object.defineProperty 不能监听对象属性新增和删除,在初始化阶段递归执行劫持对象属性,性能损耗较大;

而 Proxy 可以监听到对象属性的增删改,在访问对象属性时才递归执行劫持对象属性,在性能上有所提升。不过 Proxy API 属于 ES6 规范,目前 IE 尚不支持。

上面那段代码无法对如下对象进行劫持:

const target = {
  name: "Hello World!",
  get alias() {
    return this.name;
  },
};

通过 Reflect.get(target, key, receiver) 代替 target[key] 就可以解决上述问题。具体看我这篇博文:https://blog.youkuaiyun.com/hefeng6500/article/details/123610606?spm=1001.2014.3001.5502

下面是实现一个完善的响应式系统的过程,当然像 effect api 和 对象代理是硬编码,Vue3真实源码不是这样的,这里只做原理讲解,简单明了能说明问题即可!

const target = {
  text: "Hello World!",
};

let targetMap = new WeakMap();
let activeEffect;

function effect(fn) {
  activeEffect = fn;
  fn();
}

const proxy = new Proxy(target, {
  get(target, key, receiver) {
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    trigger(target, key);
  },
});

function track(target, key) {
  if (!activeEffect) {
    return;
  }

  let depsMap = targetMap.get(target);

  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  let deps = depsMap.get(key);

  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }

  deps.add(activeEffect);
}

function trigger(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }

  let effects = depsMap.get(key);

  effects && effects.forEach((fn) => fn());
}

effect(() => {
  console.log(proxy.text);
});

proxy.text = "Hello Vue3!";

运行上述代码,effect 的回调函数执行两次,分别打印 Hello World! ,Hello Vue3!

可以看到上述代码使用 ES6 的 Proxy 对 target 对象进行了代理,在访问对象 target 的属性的时候被 get 函数劫持调用 track 函数进行依赖收集,并且返回 key 对应的值。依赖收集的过程简单说就是将 activeEffect 放进特定的数据结构中

在对 target 的属性进行设置时被 set 函数劫持为 target 属性设置新的值,并且调用 trigger 设置新的值。依赖触发就是将之前收集好的依赖从特定数据结构中取出来执行

这个依赖收集和触发的过程就像是发布订阅模式。

track 依赖收集的过程

将依赖收集设置成如下图所示的数据结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8oOlXahs-1648126903905)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/64b7d539-d130-463f-9901-6486a82ee014/Untitled.png)]

  • targetMap 是一个 WeakMap 数据结构:target —> Map()
  • depsMap 是一个 Map 数据结构,其值是一个 Set 数据结构:key —> Set()

分支切换和 cleanup

很快我们就发现对应下面这种案例就会出问题

const data = { ok: true, text: 'hello world' }
const obj = new Proxy(data, { /** ... */ })

effect(() => {
  console.log('effect run')
  document.body.innerText = obj.ok ? obj.text : 'not'
})

obj.ok = false;

对于 effect 函数中三目运算分支切换问题,首次执行时,访问 obj.ok 对 ok 属性进行依赖收集,由于 ok 为 true,访问 obj.text ,再对 text 属性进行依赖收集。

如果接下来修改 ok 属性值为 false,将会触发副作用函数重新执行,由于 obj.ok 值为 false,所以 obj.text 不会读取,所以副作用函数不该再被 obj.text 所对应的依赖集合收集。

但是实际上上次收集的依赖还在,obj.ok 和 obj.text 在上次访问时收集的依赖并没有消失,即使这次只对 obj.ok 读取并进行依赖收集,实际上 obj.ok 和 obj.text 所对应的副作用函数还在收集的依赖中。所以我们把之前收集的依赖清除掉不就可以了吗?反正在触发依赖执行时会再次访问副作用函数,会再次进行依赖收集。

所以,我们定义一个 cleanup 函数清除之前收集的依赖。如下:

let targetMap = new WeakMap();
let activeEffect;

const target = { ok: true, text: 'hello world' }

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn);

    activeEffect = effectFn;
    fn();
  };

  effectFn.deps = [];

  effectFn();
}

const obj = new Proxy(target, {
  get(target, key, receiver) {
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    trigger(target, key);
  },
});

function track(target, key) {
  if (!activeEffect) {
    return;
  }

  let depsMap = targetMap.get(target);

  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  let deps = depsMap.get(key);

  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }

  deps.add(activeEffect);

  activeEffect.deps.push(deps);
}

function trigger(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }

  let effects = depsMap.get(key);

  effects && effects.forEach((fn) => fn());
}

function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}

effect(() => {
  console.log("effect run");
  document.body.innerText = obj.ok ? obj.text : "not";
});

setTimeout(() => {
  obj.ok = false;
  setTimeout(() => {
    obj.text = "hello vue3";
  }, 1000);
}, 1000);

看上去好像越来越完美了,但是运行是你会发现无限循环。

原因是:trigger 函数在执行时,会遍历 fn 进行执行,fn 执行时又会 cleanup 依赖(减少),然后调用副作用函数收集依赖(增加),一减一增,如此循环导致。具体可以查看 Set.prototype.forEach

effects 依赖在收集时如何避免无限循环?

其实这个问题很好解决!重新造一个 Set 进行遍历

function trigger(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }

  let effects = depsMap.get(key);
  
  const effectToRun = new Set(effects)

  effectToRun && effectToRun.forEach((fn) => fn());
}

嵌套的 effect 的如何处理

测试用例是这样的

const target = { count: 0, text: "Hello World!" };
const obj = new Proxy(data, { /** ... */ })

effect(() => {
  effect(() => {
    console.log("inside effect: ", obj.text);
  });
  console.log("outside effect: ", obj.count);
});

obj.count++;

按理说应该输出

inside effect:  Hello World!
outside effect:  0
inside effect:  Hello World!
outside effect:  1

实际上

inside effect:  Hello World!
outside effect:  0
inside effect:  Hello World!

原有代码分析:

当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且不会恢复到原来的值。如果再有响应式数据发生依赖收集,即使是读取外层副作用函数中的数据,收集到的副作用函数也是内层的副作用函数。

如何解决?使用栈结构

const effectStack = [];

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn);

    activeEffect = effectFn;

    effectStack.push(activeEffect);

    fn();

    effectStack.pop();

    activeEffect = effectStack[effectStack.length - 1];
  };

  effectFn.deps = [];

  effectFn();
}

不得感叹,真聪明!

还没完!上述解决嵌套是 Vue3.2 之前的,来看看 Vue3.2 版本怎么处理嵌套的

run() {
  let parent = activeEffect;

  try {
    this.parent = activeEffect;
    activeEffect = this as any;

    return this.fn();
  } finally {
    activeEffect = this.parent;
    this.parent = undefined;
  }
}

执行外层副作用函数时,parent = undefined,this.parent = undefined; 然后 activeEffect 被赋值为外层副作用函数,再执行外层副作用函数,由于 外层副作用函数中存在 effect ,于是开始执行内层的 effect,同理:parent = 外层的副作用函数,内层的实例的 parent = 外层的 activeEffect,执行内层副作用函数,完成之后 finally,将 activeEffect 复位为原有父级的 activeEffect,再将内层的 parent 赋值为 undefined。

这个步骤让人的感受是:过完河,把桥拆掉。哈哈,开个玩笑!应该叫有始有终

写到这一步是不是已经够完美了,其实还有一个功能需要适配,继续!

如何避免无限递归循环

什么场景会造成无限递归循环?

const target = { foo: 1 }
const obj = new Proxy(target, { /** ... */ })

effect(() => {
	obj.foo++
})

effect 的副作用函数中读取属性并且修改属性,换句话说,副作用函数中收集依赖并且触发依赖。如果按之前的代码,一定会无限循环下去

解决方案:修改一下 trigger 函数

function trigger(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }

  let effects = depsMap.get(key);

  const effectToRun = new Set();

  effects.forEach((effectFn) => {
    if (effectFn !== activeEffect) {
      effectToRun.add(effectFn);
    }
  });

  effectToRun && effectToRun.forEach((fn) => fn());
}

如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行

要注意:这里 副作用函数会先执行一次,只是在执行的时候发生 track,trigger,trigger 的时候发现 effectFn !== activeEffect 所以就不会往 effectToRun 中添加 activeEffect 了。但不可否认副作用函数已经执行了一次了。

effect(() => {
	obj.foo++
})

这也让我明白了为什么源码中要将 effects 循环一遍再用的原因了,为的就是防止遇到这种使用情况下无限递归循环下去!

先到这,调度执行,computed 和 watch 下次继续总结!

梳理的过程真的非常消耗时间和精力,不过我认为这也是自我消化的最好的方法!加油!!!🏃🏃🏃

调度执行 scheduler 是什么?解决了什么问题?

调度执行有能力决定副作用函数的执行的时机、次数以及方式。

const target = { foo: 1 }
const obj = new Proxy(target, { /** ... */ })

effect(() => {
	console.log(obj.foo)
})

obj.foo++

console.log("结束了")

希望输出

1
结束了
2

而不是

1
2
结束了

你可能觉得将 obj.foo++ 和 console.log("结束了") 顺序调换一下就可以了,如果不允许调换还有别的解决方案吗

解决方案:

聪明的你肯定会想到异步,那如何设计呢?

effect(() => {
	console.log(obj.foo)
}, 
{
	scheduler(fn){
		setTimeout(fn) // 进入下一次事件循环
	}
}
)
function trigger(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }

  let effects = depsMap.get(key);

  const effectToRun = new Set();

  effects &&
    effects.forEach((effectFn) => {
      if (effectFn !== activeEffect) {
        effectToRun.add(effectFn);
      }
    });

  effectToRun.forEach((effectFn) => {
+   if (effectFn.options.scheduler) {
+     effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
}

这里可以看到

const effectStack = [];

function effect(fn, options) {
  const effectFn = () => {
    cleanup(effectFn);

    activeEffect = effectFn;

    effectStack.push(activeEffect);

    fn();

    effectStack.pop();

    activeEffect = effectStack[effectStack.length - 1];
  };

+	effectFn.options = options

  effectFn.deps = [];

  effectFn();
}

这个例子就提现了任务调度的时机选择问题。

下面再讲一个任务调度控制的次数问题

const target = { foo: 1 }
const obj = new Proxy(target, { /** ... */ })

effect(() => {
	console.log(obj.foo)
})

obj.foo++
obj.foo++

希望输出

1
3

而不是

1
2
3

解决方案:还是借助 scheduler 函数

const jobQueue = new Set()
const p = Promise.resolve()

let isFlushing = false
function flushJob() {
  if (isFlushing) return
  isFlushing = true
  p.then(() => {
    jobQueue.forEach(job => job())
  }).finally(() => {
    isFlushing = false
  })
}

effect(() => {
  console.log(obj.foo)
}, {
  scheduler(fn) {
    jobQueue.add(fn)
    flushJob()
  }
})

obj.foo++
obj.foo++

连续运行 obj.foo++ 时,借助 Set 数据结构的去重能力,Set 结构里面只会保存一个副作用函数。

obj.foo++ 时会触发 trigger 函数,trigger 执行时调用 scheduler,将 fn 放进 Set,flushJob刷新任务队列,任务被挂起到微任务队列,再次 obj.foo++ 时会触发 trigger 函数,trigger 执行时调用 scheduler,由于Set的去重能力,就导致 Set 中只有一个副作用函数,flushJob刷新任务队列,任务被挂起到微任务队列。

接下来,执行微任务,由于 设置了 isFlushing 标志导致第二次 trigger 时调用的 flushJob 无法执行,第一次是执行了。但是 obj.foo++ 连续两次增加值在 Proxy set 中已经是被设置了。所以值是修改了两次,但是副作用函数只执行了一次!

需要明确一点的是:scheduler 调度函数是在 trigger 后才开始执行的

computed API 的实现原理?

function computed(getter) {
  let value
  let dirty = true

  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      if (!dirty) {
        dirty = true
        trigger(obj, 'value')
      }
    }
  })
  
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value')
      return value
    }
  }

  return obj
}

还是利用了 effect 函数的功能,在对象 get 时进行依赖收集,在 set 时触发副作用函数更新。采用了 dirty 标识,在值未更新的时候继续使用之前的值。

function effect(fn, options) {
  const effectFn = () => {
    cleanup(effectFn);

    activeEffect = effectFn;

    effectStack.push(activeEffect);

    fn();

    effectStack.pop();

    activeEffect = effectStack[effectStack.length - 1];
  };

	effectFn.options = options

  effectFn.deps = [];

+  if (!options.lazy) {
+    effectFn()
+  }

+  return effectFn
}

watch API 的实现原理?

希望通过如下方式运行:

watch(() => obj.foo, (newVal, oldVal) => {
  console.log(newVal, oldVal)
}, {
  immediate: true,
  flush: 'post'
})

解决方案:

function traverse(value, seen = new Set()) {
  if (typeof value !== 'object' || value === null || seen.has(value)) return
	// 代表已经读取过了,避免循环引用引起的死循环,例如: a.b = a
  seen.add(value)
  for (const k in value) {
		// 访问属性值,收集依赖
    traverse(value[k], seen)
  }

  return value
}

function watch(source, cb, options = {}) {
  let getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }

  let oldValue, newValue

	const job = () => {
    newValue = effectFn()
		// watch 设置 immediate 首次执行时,oldValue 值为 undefined
    cb(oldValue, newValue)
    oldValue = newValue
  }

  const effectFn = effect(
    // 执行 getter
    () => getter(),
    {
      lazy: true,
      scheduler: () => {
        if (options.flush === 'post') {
          const p = Promise.resolve()
          p.then(job)
        } else {
          job()
        }
      }
    }
  )
  
  if (options.immediate) {
    job()
  } else {
    oldValue = effectFn()
  }
}

就目前 watch api 的实现来看,traverse 深度遍历了 value,进行依赖收集,其实调用了 traverse 等于就是深度监听了。

这里需要明确的是:

effect 中的第一个参数,() => getter() 它是副作用函数,oldValue = effectFn() 时会执行副作用函数。scheduler 函数是在 trigger 时才执行的

watch 函数中如何解决竞态问题?

试想如下代码,obj.foo 变化两次,触发两次回调函数,由于请求先后的不可确定性,finallyData 就存在这种竞态问题

let finallyData

watch(() => obj.foo, async (newVal, oldVal, onInvalidate) => {
  const res = await fetch()

  finallyData = res
})

为保证值的确定性 watch 做如下修改

function watch(source, cb, options = {}) {
  let getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }

  let oldValue, newValue

  let cleanup
  function onInvalidate(fn) {
    cleanup = fn
  }

  const job = () => {
    newValue = effectFn()
    if (cleanup) {
      cleanup()
    }
    cb(oldValue, newValue, onInvalidate)
    oldValue = newValue
  }
 
  const effectFn = effect(
    // 执行 getter
    () => getter(),
    {
      lazy: true,
      scheduler: () => {
        if (options.flush === 'post') {
          const p = Promise.resolve()
          p.then(job)
        } else {
          job()
        }
      }
    }
  )
  
  if (options.immediate) {
    job()
  } else {
    oldValue = effectFn()
  }
}

watch api 可以这样使用

watch(() => obj.foo, async (newVal, oldVal, onInvalidate) => {
  let valid = true
  onInvalidate(() => {
    valid = false
  })
  const res = await fetch()

  if (!valid) return

  finallyData = res
  console.log(finallyData)
})

obj.foo++
obj.foo++

可以看到 watch api,第二个参数回调函数提供了第三个参数 onInvalidate ,在失效时修改 valid 值,如果失效函数直接 return,不再赋值。

再把目光投向 watch 函数的实现,函数内部添加了 cleanup 标识,在执行 job 时如果存在 cleanup 将会调用。再回到上述 watch 示例,如果连续两次修改 obj.foo++ ,第一次调用watch 的回调函数 onInvalidate,onInvalidate 传入的回调函数被注册为 cleanup ,当第二次再次调用 watch 的回调时,存在 cleanup ,将会 cleanup(),示例中的 valid 值就设置为了 false。

于是,即便是两次修改 obj.foo 触发 watch 的回调,只要第二次回调被触发,第一次的 valid 将被设置为 false,就算第一次请求返回了结果,依然不可以赋值给 finallyData

GEO优化实战:用AnswerBit量化AI搜索可见度与品牌监控体系 生成式引擎优化(GEO)正取代传统SEO,成为品牌在AI搜索生态中获取可见度的关键。当用户习惯从“翻列表”转向“直接问AI”,品牌能否被生成式引擎识别、引用并正向推荐,直接决定了其在用户决策链条中的存在感。AI搜索可见度因此从模糊概念演变为可度量的运营指标,涵盖提及率、引文率、情感分与回源质量等维度。腾讯AnswerBit作为答案侧分析工具,帮助运营者拆解AI回复中的品牌语境与引用来源,让优化动作有据可循。从内容结构化改造、品牌实体建设到第三方权威背书,GEO要求团队建立“度量-优化-再度量”的闭环。文章结 阅读详情

相关推荐

LS-DYNA壳单元冲击仿真中的5个关键设置:以方管碰撞为例(含沙漏控制参数)

本文以方管碰撞为例,深入解析了LS-DYNA壳单元冲击仿真中的五个关键设置。重点探讨了沙漏控制参数(如IHQ=4)的选择逻辑、自接触算法优化、能量监控方法、材料模型(MAT_24)的真实应力-应变曲线与应变率效应设置,以及单元公式的选取。这些核心设置共同决定了仿真的精度与效率,是确保方管冲击仿真结果可靠性的基石。

salt的博客 625

Vue课后练习题及答案解析

Vue课后练习题 第一章 填空题 Vue是一套构建________的渐进式框架。 用户界面 MVVM主要包含3个部分,分别是Model、View和________。 ViewModel Vue中通过________属性获取相应的DOM元素。 refs 在进行Vue调试时,通过使用________工具来完成项目开发。 vue-devtools Vue中页面结构以________形式存在。 组件 判断题 Vue与Angular和React框架不同的是,Vue设计为自下而上逐层应

Lvruoyu的博客 3万+

麦克风声源定位原理_声源定位—如何定位到噪声根源?

前言TPA(传递路径分析)专题系列已经开篇。其遵循方法论:源(Source)——路径(Path)——响应(Receiver)有人说TPA好,它可以识别出路径处甚至源的载荷特征,可以量化各个路径的贡献量。有人说声源定位好,不管东西南北中,“源”自其中。但是,声源定位识别出来色彩丰富的一个结果,这结果被您拿来应付工作了,还是真正帮您解决了问题?声源定位定位到了噪声的根源了么?对于一些声源现,...

weixin_39943678的博客 3973

《Vue.js前端开发实战》课后习题答案

《Vue.js前端开发实战》课后习题参考答案

m0_51622621的博客 3万+

网易数智CoreAgent实战:5步教你打造企业级AI数字员工(附避坑指南)

本文以网易数智CoreAgent智能体协作中枢为核心,提供了一份企业级AI数字员工的实战部署指南。文章详细拆解了从环境准备、智能体配置、业务集成、安全合规到效果评估的五个关键步骤,并分享了部署过程中的常见“避坑”经验,旨在帮助企业技术团队高效、安全地将AI智能体落地于实际业务场景,解决AI落地难题。

rice5的博客 952

点击率预测的贝叶斯平滑

概述 电商领域中经常需要计算或预测一些转化率指标,如最典型的CTR(点击率,Click-Through Rate)。这些转化率可以是模型的预测值,也可以作为模型的特征(feature)使用。以商品点击率预测为例,CTR的值等于点击量(Click)除以曝光量(Impression或Exposure)。以rr表示点击率, r=CI(1)r=\frac{C}{I} \tag{1}. 但在实际应

小平子的专栏 3万+

(Vue)Vue课后题

Vue课后题 文章目录 Vue课后题 第1章 Vue.js 基础入门 一、填空题 二、判断题 三、选择题 四、简答题 五、编程题 第2章 Vue 开发基础(上) 一、填空题 二、判断题 三、选择题 四、简答题 五、编程题 第3章 Vue 开发基础(下) 一、填空题 二、判断题 三、选择题 四、简答题 五、编程题 第4章 过渡和动画 一、填空题 二、判断题 三、选择题 四、简答题 五、编程题 第5章 Vue 路由 一、填空题 二、判断题 三、选择题 四、简答题 五、编

m0_67390773的博客 1万+

告别示教器!用C#和ABB PC SDK 6.08打造你的第一个Windows机器人控制台(附完整连接代码)

本文详细介绍了如何使用C#和ABB PC SDK 6.08开发Windows机器人控制台应用,实现无示教器的机器人控制。从环境配置、SDK引用到机器人发现、连接及状态监控,提供了完整的代码示例和架构设计建议,帮助开发者快速掌握上位机控制技术。

weixin_29758911的博客 321

Vue第四章课后习题【哈哈】

课后习题 文章目录课后习题一、填空题二、判断题三、选择题四、简答题 一、填空题 Vue提供的内置过渡封装组件是_ transition__。 2.在过渡封装组件中使用__name__属性可以重置过渡中切换类名的前缀。 3.通过___appear_特性设置节点在初始渲染的过渡。 4.在离开的过渡中有__v-leave,v-leave-active,v-leave-to____3个class切换。 5.__自定义过渡__的类名优先级要高于普通的类名。 二、判断题 1.函数式组件中的render0函数用来创建

qq_46556714的博客 1万+

虚拟机VMware Horizon Client与主机之间建立共享文件夹

因经常需要在虚拟机和主机之间传递一些日常文件,所以建立共享文件夹。 虚拟机中安装的系统为win10,主机中的系统为win10。 步骤: 第一步:先在虚拟机win10中新建文件夹(我重命名为“共享文件夹”),然后右键属性->共享->共享 进去之后点击自己的账户名 然后点共享。操作完成后,再返回属性界面就能看到该文件夹已经共享而且出现了共享链接,记下此链接。 第二步:回到主机桌面->此电脑->最上面计算机->添加一个网络位置- 点击下一步 然后自定义网络位置,下一步,如下图

AnChenliang_1002的博客 1万+

《手把手教你修复过期K8s集群证书:记一次K8s集群起死回生实录》

今天本想打开半年前部署的K8s测试集群跑个Demo,结果`kubectl`直接报错`certificate has expired`!作为一名云原生老司机,我决定把这次'抢救'过程记录下来,手把手带大家解决这个运维常见问题。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 3778

深入浅出Vue.js:第4章 (第①篇 变化侦测)变化侦测相关的API实现原理

#几个与变化侦测相关的常用API的内容原理 vm.$watch vm.$watch(expOrFn,callback,[options]) 【参数】: 1.{string|Function} expOrFn 2.{Function|Object} callback 3.{Object} [options] =>{boolean} deep =>{boolean} immediate 【返回值】:{Function} unwatch 【用法】:用于观察一个表达式或computed函数在Vue.js

SevenLee的个人博客 204

在Simulink中构建永磁同步电机的网络化控制模型,并分析通信网络对系统性能的影响

核心优势通过仿真验证了网络延迟和丢包对PMSM控制性能的影响。提供了改进PI参数或引入自适应控制的解决方案。扩展方向抗干扰控制:结合卡尔曼滤波或滑模控制抑制网络噪声。时间敏感网络(TSN)建模:参考知识库[2]的中断触发方式,实现确定性通信。网络安全仿真:模拟恶意攻击(如数据篡改)对控制系统的威胁。

xiaoheshang_123的博客 168

【Vue.js设计与实现】第二篇第4章:响应系统-响应系统的作用与实现-阅读笔记

从高层设计的角度去探讨框架需要关注的问题。第二篇 响应系统。

karshey的博客 1673

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Apache DataFusion 27.0.0 版本发布解析:窗口函数扩展、外部表写入与核心模块重构

> 本文基于仓库内 [dev/changelog/27.0.0.md](https://link.gitcode.com/i/e1a0788292a5fde7f0c0879d4875cd98) 发布说明,结合当前仓库源码与示例,系统梳理 Apache DataFusion 27.0.0(2023-06-26 发布)的核心变更:破坏性改动、新功能、性能优化与 Bug 修复。读完本文,你将掌握 27.

gitblog_00528的博客 962

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

《Vue.js》阅读之响应式数据与副作用函数

《Vue.js设计与实现》一书深入探讨了Vue3的响应式系统,重点介绍了如何通过Proxy实现数据的响应式更新。书中详细讲解了副作用函数和响应式数据的概念,并通过代码示例展示了如何拦截数据的读取和设置操作,以实现自动更新。具体实现包括使用Proxy拦截对象的读取和设置操作,并通过WeakMap和Set来管理副作用函数。书中还提供了封装函数的方法,如track和trigger,以简化代码结构。通过这些技术,开发者可以更好地理解Vue3的响应式原理,并应用于实际开发中。

2403_88913721的博客 465

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

《Vue.js 设计与实现》阅读笔记

《Vue.js 设计与实现》是一本深入探讨Vue.js技术实现原理和架构设计的经典著作。本书作者尤雨溪是Vue.js的发明者,通过对Vue.js源码的分析和解读,系统地介绍了Vue.js的核心思想和实现原理,并提供了大量实用的案例和技巧,帮助读者更好地理解和应用Vue.js。本书内容包含Vue.js的响应式数据系统、虚拟DOM、组件化开发、异步更新机制、插件机制等诸多方面,适合对Vue.js感兴趣的开发者学习和研究。无论是初学者还是有一定经验的开发者,本书都能够为您提供丰富的知识和实践经验。

starry-sea1412的弃坑之路 1412

【Vuejs】探索 Vue.js 响应式原理

提到“响应式”三个字,大家立刻想到啥?响应式布局?响应式编程?从字面意思可以看出,具有“响应式”特征的事物会根据条件变化,使得目标自动作出对应变化。比如在“响应式布局”中,页面根据不同设...

奇舞周刊 629

读《Vue.js设计与实现》- 第四章(上)

此文章为了记录自己对读这本书的感悟和对书中所提的内容的个人理解,文章将分为多次发布,流程是先仔细阅读其中的每一章内容,然后再根据自己的理解手写代码,最后结合文章内容和个人理解形成该文章,以便加深印象和提供给有需要的人。

god_hands1的博客 1353

【阅读】1235- 推荐2022前端必看的新书 《Vue.js设计与实现》

本文浮夸班标题《废物!Vue3源码都不会,还敢去头条面试前端?》今天推荐一本我认为2022年前端进阶需要看的一本书,来自Vue官方团队成员霍春阳的新书《Vue.js设计与实现》很久之前我...

pingan8787 1783

深入讲解一下 Vue 3 的核心源码库 vuejs/core

track 负责“依赖收集”,trigger 负责“派发更新”。

u011091936的博客 1392

Vue响应式系统分支切换与cleanup - 清除遗留的副作用函数

本篇文章代码思路来自 Vue3.0 源码, 部分理解来源于霍春阳 《Vue.js设计与实现》这本书的理解, 感兴趣的小伙伴可以自行购买阅读。可以非常明确的感受到作者对 Vue 的深刻理解以及用心, 富含非常全面的 Vue 的知识点, 强烈推荐给大家。

m0_71485750的博客 1452

深入理解Vue3.js响应式系统基础逻辑

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来,对Vue3响应式系统的基础逻辑进行了解析,适合深入理解vue3的同学学习

海风依旧 854

Vue响应式系统分支切换与cleanup | 清除遗留的副作用函数

本篇文章代码思路来自 Vue3.0 源码, 部分理解来源于霍春阳 《Vue.js设计与实现》这本书的理解, 感兴趣的小伙伴可以自行购买阅读。可以非常明确的感受到作者对 Vue 的深刻理解以及用心, 富含非常全面的 Vue 的知识点, 强烈推荐给大家。接上文Vue响应式原理和本质 - 实现一个完善的响应式系统。

m0_71485750的博客 1205

Nodejs和vue框架的基于的书城阅读器系统的设计与实现

该系统基于Node.js和Vue.js框架,设计并实现了一个功能完善的在线书城阅读器平台。Node.js作为后端服务器,提供高性能的异步I/O处理能力,结合Express框架实现RESTful API接口,支持用户认证、书籍管理、订单处理等核心功能。数据库采用MongoDB存储用户信息、书籍数据和交易记录,利用其灵活的文档结构适应动态数据需求。前端采用Vue.js框架构建单页面应用(SPA),通过组件化开发实现模块复用,提升开发效率和可维护性。

QQ58850198的博客 1029

Vue响应式原理和本质 - 实现一个完善的响应式系统

本篇文章代码思路来自Vue3.0源码, 部分理解来源于霍春阳 《Vue.js设计与实现》这本书的理解, 感兴趣的小伙伴可以自行购买阅读。可以非常明确的感受到作者对 Vue 的深刻理解以及用心, 富含非常全面的 Vue 的知识点, 强烈推荐给大家。

m0_71485750的博客 822
上一篇: Vue3 Proxy Reflect
下一篇: 《Vue.js 技术与实现》第9章 简单 diff 阅读总结
兰亭古墨
博客等级 码龄10年 163粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值