这两天面试,或多或少都问些Vue响应式原理,问的我头皮发麻。虽然我手写过,懂和能回答还是两回事。所以

  • 纸上得来终觉浅,绝知此事要躬行

手写响应式的完整代码

Vue2.x

接着上篇 你这手写vue2.x/3.x的响应式原理保熟吗?⇲ 继续做下扩展。

上篇手写实现了

  • v-model
  • v-show
  • v-text
  • {{ }}

这里给下完整vue的简单手写响应式代码吧

效果

<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>Vue2.x</title><style>.show-txt {opacity: 1;transition: all .5s linear;}.show-txt.hidden {opacity: 0;color: #eee;}</style>
</head>
<body><div id="app"><input type="text" v-model="name" /><br/><input id="male" name="sex" type="radio" v-model="sex" value="男"><label for="male"> 男 </label></input><input id="female" name="sex" type="radio" v-model="sex" value="女"><label for="female"> 女 </label></input><br/><input name="show" type="checkbox" v-model="show" checked>是否展示</input><div class="show-txt" v-show="show">展示文本示例</div><b>姓名:</b><span>{{ name }}</span> <br/><b>性别:</b><span>{{ sex }}</span><hr /><div v-text="text"></div></div><script>class Dep {constructor (){this.subs = []}addSub (sub){if(sub && sub.update) this.subs.push(sub)}notify(oldValue){this.subs.forEach(sub => {sub.update(oldValue)})}}// 数据更新后调用 update 进行更新class Watcher {constructor(vm, key, cb){this.vm = vmthis.key = keythis.cb = cbDep.target = thisthis.oldVal = vm[key]Dep.target = null}update(oldValue){this.oldVal = oldValue;let newValue = this.vm.$data[this.key]if(newValue === this.oldVal) return;this.cb(newValue)}}//解析模板template 内容,class Compiler {constructor(vm) {this.vm = vm;this.el = vm.$el;this.compile(this.el)}compile(el) {let childrenNodes = [...el.childNodes]childrenNodes.forEach(node => {if(this.isTextNode(node)){this.compileText(node)}else if(this.isElementNode(node)) {this.compileElement(node)}if(node.childNodes && node.childNodes.length) this.compile(node)})}compileText(node){let reg  = /\{\{(.+?)\}\}/;let val = node.textContentif(reg.test(val)){let key = RegExp.$1.trim()const value = this.vm[key];node.textContent = val.replace(reg, value)new Watcher(this.vm, key, (newVal) => {node.textContent = newVal})}}compileElement(node) {// https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attributes![...node.attributes].forEach(attr => {let attrName = attr.nameif(this.isDirective(attrName)){attrName = attrName.substring(2);let key = attr.value;this.update(node, key, attrName)}})}update(node, key, attrName) {let updateFn = this[attrName+'Update']updateFn && updateFn.call(this, node, key, this.vm[key])}// 文本节点的值有更新就会重新渲染。本质还是利用 js修改 textContenttextUpdate(node, key, content ){node.textContent = contentnew Watcher(this.vm, key, newVal => { node.textContent = newVal })}modelUpdate(node, key, value) {const typeAttr = node.getAttribute('type')if(typeAttr == "text") {node.value = value;new Watcher(this.vm, key, newVal => { node.value = newVal})node.addEventListener('keyup', () => {this.vm.$data[key] = node.value})}else if(typeAttr === "radio") {new Watcher(this.vm, key, newVal => { node.classList.add('class-'+newVal)})const nameAttr = node.getAttribute('name')// 这里不需要 watch, node.addEventListener('change', (ev) => {this.vm.$data[key] = ev.target.value})}else if(typeAttr === 'checkbox') {node.addEventListener('change', (ev) => {this.vm.$data[key] = ev.target.checked})}}showUpdate(node, key, value){const change = (val) => { const operate = !!val ? 'remove' : 'add';node.classList[operate]('hidden') }change(value);new Watcher(this.vm, key, (newVal) => { change(newVal) })}isDirective(attr) {return attr.startsWith('v-')}isTextNode(node){return node.nodeType === 3}isElementNode(node) {return node.nodeType === 1}}/*将数据变为响应式对象*/class Observer {constructor(data) {this.walk(data);}walk(data) {if(!data || typeof data != 'object') return;Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key]);})}defineReactive(obj, key, value) {// 递归的将对象子属性变为响应式this.walk(value);const self= this;let dep = new Dep()Object.defineProperty(obj, key, {enumerable: true,configurable: true,get(){Dep.target && dep.addSub(Dep.target)return value},set (newValue) {const oldval = obj[key]if(newValue === obj[key]) return;value = newValue;self.walk(newValue)// 更新视图dep.notify(oldval)}})}}class Vue {constructor(options) {this.$options = options || {}this.$el = typeof options.el === 'string' ?document.querySelector(options.el) : options.el;this.$data = options.data;// 处理data中的属性 this._proxyData(this.$data);// 将data变为响应式new Observer(this.$data)  // 模板编译new Compiler(this)}// 将data中的属性注册到vue_proxyData(data) {Object.keys(data).forEach(key => {Object.defineProperty(this, key, {enumerable: true,configurable: true,get(){ return data[key]  },set (newValue) {if(newValue === data[key]) return;data[key] = newValue;}})})}}new Vue({el: '#app',data: {name: 'ethan',sex: '男',text: 'text',show: true,}})
</script>
</body>

Vue3.x

<body><script>// https://juejin.cn/post/7001999813344493581#heading-1f1()function f1() {const targetMap = new WeakMap()/*targetMap = WeakMap{target: Map{ key: Set[cb,..],... }, ...}这里用 WeakMap Map 主要是为了添加时自动去重*/ function track(target, key) {/* 如果此时activeEffect为null则不执行下面这里判断是为了避免例如console.log(person.name)而触发track*/if (!activeEffect.fn) return// depsMap: target 的所有 依赖(Set)let depsMap = targetMap.get(target)if (!depsMap) {targetMap.set(target, depsMap = new Map())}// dep: 数据项每个 key 值的依赖集合(Set)let dep = depsMap.get(key)if (!dep) {depsMap.set(key, dep = new Set())}dep.add(activeEffect.fn) // 把此时的activeEffect添加进去// 此时的 dep 才是后面更新的 dep 将其当做更新的 keyactiveEffect.key = dep;}function trigger(target, key, { oldValue }) {let depsMap = targetMap.get(target)console.log('depsMap: ', depsMap);if (depsMap) {const dep = depsMap.get(key)if (dep) {dep.forEach(effect => effect(oldValue, dep))}}}function reactive(target) {const handler = {get(target, key, receiver) {console.log('get: ', target, key, receiver)track(receiver, key) // 访问时收集依赖return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {const oldValue = Reflect.get(target, key, receiver)if(value === oldValue) return;const result = Reflect.set(target, key, value, receiver)trigger(receiver, key, { oldValue }) // 设值时自动通知更新return result}}return new Proxy(target, handler)}let activeEffect = {fn: null, key: null}function effect(fn) {activeEffect.fn = fnactiveEffect.fn()activeEffect = {fn: null, key: null}}// 测试一下// t00()function t00() {const data = { name: '二蛋' }const rData = reactive(data)// 有一个 data.name 的依赖effect(() => { console.log('我依赖二蛋', rData.name); })rData.name = '王二蛋'}function ref(initValue) {return reactive({value: initValue})}function watchEffect(fn) {effect(() => fn())}function computed(fn) {const result = ref()effect(() => result.value = fn())return result}/*watch 简版, 主要实现 对 ref 数据的监听, 将新值和旧值保存为Map,方便多源监听时 精确更新,注意: 本方法只是简单的实现了 对 ref 的监听, 如果是 reactive, key 会有问题*/function watch(source, fn) {let oldValues = new Map(), newValues = new Map(), isArray = false; function handleEffects(rValue){effect((oldValue, upKey = null) => {// 这里去触发get 搜集依赖const newValue = typeof rValue === 'function' ? rValue().value : rValue.value;if(activeEffect.fn) {// 遍历原始数据时,新值旧值都一样, 添加依赖的时候 增加 activeEffect.keyoldValues.set(activeEffect.key, newValue)newValues.set(activeEffect.key, newValue)}else {// 同时更新旧值与新值oldValues.set(upKey, oldValue)newValues.set(upKey, newValue)// 同源和多源做不同处理isArray ? fn([[...newValues.values()], [...oldValues.values()]]) : fn([...newValues.values()][0], [...oldValues.values()][0]);}})}// 监听多源if(Array.isArray(source)) {isArray = true;source.forEach(rValue => {handleEffects(rValue)})}// 监听单一源else handleEffects(source)}// t0()function t0(){const eData = ref(5);watchEffect(() => { console.log('effect测试: ', eData.value) })eData.value = 666}// t01()function t01(){const wRef = ref(5);watch(wRef, (value, preValue) => {console.log('watch监听单源测试:', value, preValue)})wRef.value = 66// watch监听单源测试: 666 5}t02()function t02(){const wRef = ref(1);const wRef1 = ref(2);const wRef2 = ref(3);watch([wRef, () => wRef1, wRef2], (values, preValues) => { console.log('watch监听多源测试:', values, preValues)})wRef.value = 11;// watch监听多源测试:[11, 2, 3] [1, 2, 3]wRef1.value = 22;// watch监听多源测试:[11, 22, 3] [1, 2, 3]wRef2.value = 33// watch监听多源测试:[111, 22, 33] [11, 2, 3]wRef.value = 111// watch监听多源测试:[11, 22, 33] [1, 2, 3]wRef2.value = 333// watch监听多源测试:[111, 22, 33] [11, 2, 33]}/* Computed 设计要完成如下几件事触发effect, 传入的函数需要触发get收集依赖返回一个响应式对象(Proxy)运行 effect 才会有依赖的收集(activeEffect存在),运行 Computed 时会运行 effect ,传入的 cb也会执行,此时 result.value 会执行, 触发 get ,接着收集依赖(track),此时 activeEffect 存在,那么 targetMap 为*/// t1();function t1(){const ref1 = ref(5);const cRef1 = computed(() => {console.log('computed测试: ', ref1);return ref1.value});ref1.value = 666;console.log('last: ', ref1, cRef1)}  }</script>
</body>

扩展

主要参考 Vue的MVVM实现原理 ⇲, 原文还配了视频,针不错

响应式原理(Vue2.x)下篇相关推荐

  1. vue2和vue3响应式原理

    vue2响应式原理:核心使用Object.defineProperty给属性定义get和set方法 注意:对象的多次递归,针对数组需要重写数组方法 函数劫持:把函数内部进行重写同时继续调用老的方法,在 ...

  2. 初始Vue响应式原理~~

    自从 Vue 发布以来,就受到了广大开发人员的青睐,提到 Vue,我们首先想到的就是 Vue 的响应式系统,那响应式系统到底是怎么回事呢?接下来我就给大家简单介绍一下 Vue 中的响应式原理. vue ...

  3. 面试官:说说Vue响应式原理

    前言: 经常有面试官会问"你能说说vue的响应式原理吗?很多不明就里的人会说是v-model,其实面试官想问的是vue能实现响应式使用的是JS中的什么API,而且v-model这个属于双向数 ...

  4. Vue2.0 —— 由设计模式切入,实现响应式原理

    Vue2.0 -- 由设计模式切入,实现响应式原理 <工欲善其事,必先利其器> 既然点进来了,麻烦你看下去,希望你有不一样的收获. 大家好,我是vk,好久不见,今天我们一起来盘一盘关于 V ...

  5. Vue2的响应式原理

    --------Vue2响应式原理---------- 原理:通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的 ...

  6. Day 05- Vue3 Vue2响应式原理

    Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持  --> 给对象扩展属性 -->  属性设置 实现原理: ...

  7. vue2响应式原理解析并实现一个简单响应系统

    vue2响应式原理 Object.defineProperty() 要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法.下面这些概念引自MDN. Obj ...

  8. 【Vuejs】952- 一文带你了解vue2之响应式原理

    在面试的过程中也会问到:请阐述vue2的响应式原理?,凡是出现阐述或者理解,一般都是知无不言言无不尽,知道多少说多少.接下来,我来谈谈自己的理解,切记不要去背,一定要理解之后,用自己的语言来描述出来. ...

  9. 前端清单:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨...

    前端每周清单第 25 期:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨,深入 React 动画 作者:王下邀月熊 编辑:徐川 前端每周清单专 ...

  10. vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy

    一文了解Vue3的响应式原理 一.

最新文章

  1. 【面试题】如何设计一个高并发的系统?
  2. 学好python能干嘛-python都能做什么
  3. JDBC驱动的动态加载
  4. circle函数用法 turtle_Turtle库与Time库基础知识分享(详细)
  5. e影安全智能浏览器_【启耀玻璃】智能调光玻璃有什么特点? - 调光艺术玻璃|防火防弹玻璃|LOW-E节能玻璃|隔音隔热玻璃|特种安全玻璃|夹层中空玻璃-...
  6. java泛型学习三:受限制的通配符以及泛型方法
  7. 1-7:学习shell之透过shell看世界
  8. gprs模块http mqtt_在GPRS模块(SIM800C)和STM32芯片上实现MQTT协议 | TsonTec:测量解决方案提供者...
  9. HDU1084 What Is Your Grade?【排序+水题】
  10. 获取到的数组在webview中成了字符串
  11. 软件工程基础知识--系统测试
  12. 【mcuclub】红外测温-MLX90614
  13. 【C++】右值引用、移动构造函数
  14. 关于excle表格中日期时间筛选
  15. android actionBar searchview 默认展开,并且放大镜图标在编辑框内。
  16. 1、唯交易的市场不会偏差,2、期权对冲股票市值张数和权利金计算
  17. ElasticSearch入门教程(1)
  18. 【集成】网络技术的学习-刘俊平
  19. 纸小墨ink简洁主题story爱上你的故事
  20. 杂项-Grunt:grunt build 打包和常见错误

热门文章

  1. 计算机cpu风扇不转怎么办,如果计算机启动时cpu风扇不旋转,该怎么办?解决方法[详细说明]...
  2. Leetcode个人题解714
  3. 云平台是什么、什么是云、云平台的分类、主流公有云平台有哪些、云的三种服务、PaaS、SaaS、IaaS
  4. 计算机的第一道安全防线是,网络安全的第一道防线是
  5. java画直方图_算法练习(11):Java直方图的绘制(1.1.32)
  6. Python学习,接上篇文章的50道基础入门练习题(附答案)
  7. 虚拟化bug定位神器之一 【gdbcoredump】--libvirtd hang 住
  8. 解决vscode导致电脑很卡
  9. 企业微信——定时群机器人布置
  10. RocketMQ集群(2主2从)搭建详细步骤