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

《工欲善其事,必先利其器》

既然点进来了,麻烦你看下去,希望你有不一样的收获。

大家好,我是vk,好久不见,今天我们一起来盘一盘关于 Vue2.0 的响应式原理。注意,响应式才是 Vue 的核心,而双向绑定则是指 v-model 指令。所以一般面试时候面试官都会问你,能否讲讲响应式原理,或者简单实现一个数据的双向绑定。而不是让你实现一个双向绑定原理。

所以这时候,如果面试官问你,能否简单实现一下 Vue 的响应式?

你应该答:好的,等我10分钟,我先去看一下 vk 哥写的文章。(狗头)

所谓MVVM框架,即是数据驱动视图模型,分为 ModelViewViewModel。目前市场上三大框架,只有 Vue 是百分之百应用了 MVVM 框架,所以它的核心实现和思想值得我们学习。

有的小年轻这时候就要肝了,你个辣鸡,Vue3.0 都出了那么久了,现在才来讲 Vue2.0 ,有个屁用?出来混,是要讲…
我只能说,小年轻,你还小,有些事,你不懂…

好的,其实一方面是由于自己个人原因,没办法经常写文章。其次,不管是 Vue2.0 还是 3.0,只要是源码,它就有学习的价值。不是说,你用什么,就学什么;而是,我想学什么,我就去研究什么。这是一个主观的意向,我的建议就是不要被动的去学习,那样成长是很缓慢的,而且很容易记不住。

另外,更完这篇 2.0 的原理,下一篇就研究更新 3.0 的原理,这样一有对比,岂不美哉?(狗头)

一、分析

我们都知道,Vue 在改变数据时,会自动刷新页面的 DOM。同样,我们在页面输入数据,VueModel 层数据也会随之变化,这就是 Vue 的特性 —— 响应式原理。

最经典的例子就是输入框输入数据和其数据回显,任何一个地方改变数据,对应的数据都会发生改变,实现了数据的双向绑定。那么这到底是怎么做到的呢?我们来看一张图:

通过上面官方的图解我们可以理解并得到以下几点结论,我先把结论给你放出来了,尝试理解一下。如果不懂,没关系,我们后面会继续剖析它的原理乃至实现它:

  1. 组件实例化虚拟 DOM 时,如果需要访问我们 Data 中的数据 a,那么我们就会先 new Watcher 一个实例,在 Watcher 实例中获取这个 a 属性的值,并进行观察,这个过程就叫 Touch 我们的 getter 以获取数据。此时设置 Dep.target = this,即指向该 Watcher,保持全局唯一性。
  2. 根据 Dep.target = this 的全局唯一性,我们使用 Object.defineProperty 对数据进行拦截,设置我们的 gettersetter ,如上图。此时 getter 里面,若 Dep.targettrue ,我们通知收集器 Dep 把当前 this (即当前 Watcher )收集起来,以通知更新备用,同时返回该属性的值。
  3. 这时候再回到 Watcher,值返回以后,为防止其他依赖(即其他 Watcher)触发 getter的同时把我们这个 Watcher 又收集回去,我们需要把 Dep.target 设置为 null 。这就避免了不停的绑定 WatcherDep,造成代码死循环。

鉴于 DepWatcher 两者之间这种微妙的关系,其实我们可以发现,这就是典型的应用了 —— 发布/订阅者设计模式

设计模式的本质就是使代码解耦,实现低耦合,形成代码的高可读性、高可重用性和高度扩展性;
这些特点对于一个库或者框架来说显得尤为重要。

以上就是响应式的收集依赖的过程了,这时候你千万不要懵,好戏才刚刚开始,我们开始剖析 —— 响应式

二、理解 Object.defineProperty

先通过几段了解一下 Object.defineProperty 这个 API:

const data = {}
cosnole.log(data)
// 输出 {Prototype: object}

我们根据输出的结果,可以看到 data 里面现在只有一个原型对象。当我们为 data 添加或修改属性时:

let name = '张三'
data.name = name
console.log(data)
// 输出 {name: '张三', Prototype: object},这个也是很明显就可以理解的

对于添加或修改对象属性,有时候我们也可以用到 Object.defineProperty 这个 API。先看一下官方的定义:

那我们按照 MDN 文档,应用一下:

const data = {}
let person = '张三'
Object.defineProperty(data, "name", {get: function() {console.log("get")return person},set: function(newValue) {console.log("set")value = newValue}
})
console.log(data)
// 输出 {name: '张三', get: function() {}, set: function(newValue) {}, Prototype: object}

通过该 API 新增对象属性,我们可以观察到,跟直接添加对象属性相比较,多了 gettersetter 两个内置函数,分别用来拦截调用属性值和修改操作属性。

// 根据上面的 API,这时我们来修改 data 的属性
data.name = "李四"
console.log(data.name)
// 输出 set,说明修改属性值触发了 set 方法
// 输出 get,说明调用属性值触发了 get 方法
// 输出 李四,说明属性值已被修改

这说明,Object 的可能性一下子就被打开了,利用这个 API 可以达到我们前面提到的设计模式的特点。

插一句题外话,在 Vue2.0 发布的时候,Proxy 其实已经诞生了。很多人疑惑为什么尤大大不使用 Proxy?其实是因为当时的前端环境还并没有完全支持这个 API。很多浏览器除了几个主流的,基本上都还没有适配上。所以,尤大大为了用户群体考虑,选择了 Object.defineProperty,而放弃了 Proxy。直到今天,前端环境对 Proxy 友好了,Vue3.0 也就天然适配了 Proxy。

由此可见,我们可以往 Object.defineProperty 里面添加很多东西,例如:数据的监听、数据的加工、数据的计算、数据的判断等等非常非常多的工作。这也被业界称之为非常经典的 —— 《Vue的数据劫持》。

但是,该API有弊端。
对于已进行数据劫持的对象,他在新增属性时候,并不会为新属性绑定 settergetter
对于已进行数据劫持的对象,他在删除属性的时候,并不会触发 setter

三、浅析 Vue2.0 的响应式

通过上面的分析我们大概了解到,Vue 的数据劫持是怎么实现的。

这个时候其实很重要昂,数据劫持只是响应式的其中一环罢了。不过现在需要继续摸索,层层递进,我带你模拟一下响应式的简易的过程(由于篇幅原因,就不做太多的引导了,直接全部代码展示,希望你多跟着敲几遍,把它理解透):

  1. 我们需要一个入口,来传入以及分析数据类型,创建 observe.js 文件:
import Observer from "Observer.js";
// 监听对象属性
export default function observe(value) {// 判断是基本数据类型或者引用数据类型 objectif (typeof value != "object") return;let ob;// 判断这个对象或属性是否携带有响应式的标识if (typeof value.__ob__ != "undefined") {ob = value.__ob__;} else {ob = new Observer(value);}return ob;
}
  1. 再次细化颗粒度,精确到每个对象属性或其子属性,给属性赋予拦截操作,类(class)是不二之选。不过在这之前,我们需要新增一个工具函数文件,用来给对象或其属性添加 __ob__ 响应式标识。创建 utils.js 文件:
/*** @param {Object} obj 需要绑定的对象* @param {String} key 需要绑定的属性名* @param {Any} value 需要绑定的属性值* @param {Boolean} enumerable 绑定的属性是否可枚举
*/
export default function def(obj, key, value, enumerable) {Object.defineProperty(obj, key, {value,enumerable,writable: true,configurable: true})
}
  1. 创建 Observer.js 文件,设置颗粒度拦截:
import { def } from "utils.js";
import observe from "observe.js";
import defineReactive$$1 from "defineReactive.js";
export default class Observer {constructor(value) {// 绑定响应式标识 __ob__ 属性def(value, "__ob__", this., false);// 实现拦截this.walk(value);}walk(data) {for (let k in data) {defineReactive$$1(data, k);}}
}
  1. 创建 defineReactive.js 文件,实现数据的拦截:
/*** defineReactive.js* 该函数用于实现数据劫持* @param {Object} data 需要实现数据劫持的对象* @param {String} key 被劫持对象的属性名* @param {Any} value 被劫持对象的属性值* @return {Any} value 被劫持对象添加监听后的属性值
*/
export default function defineReactive$$1(target, key, value) {if (arguments.length == 2) value = target[key]; // 深度遍历监听,因为对象的子属性也可能是一个 objectobserver(value);// 调用核心 APIObject.defineProperty(target, key, {enumerable: true,configurable: true,get() {return value;},set(newValue) {if (newValue !== value) {value = newValue;// 同样的也是深度遍历监听,判断新的属性值是否也是 objectobserver(newValue);// 打印一下,方便我们后面监听数组console.log("视图更新");}}})}

然后,利用 observer 测试监听一个对象:

const obj = {name: '张三',age: 20
}
observer(obj);
console.log(obj);
// 输出 {name: '张三', age: 20, get age fn(), set age fn(), get name fn(), set name fn(), Prototype: object}


我们观察到对象分别添加了 age > getterage > settername > gettername > setter,说明我们针对 obj 的数据劫持已经成功监听到了。

不过,接下来,我们还需要测试一下,当我们分别新增属性、修改属性和删除属性,是否会触发视图刷新函数:

// 测试新增属性
obj.idcard = 123456// 测试修改属性
obj.age = 18// 测试删除属性
delete obj.age
console.log(obj)
// 输出 视图更新
// 输出 {name: '张三', idcard: 123456, get name fn(), set name fn(), Prototype: object}

现在我们可以观察到,执行完语句的对象 obj,只剩下了 name > gettername > setter。而且,触发视图刷新的,是我们在修改 age 属性的过程中触发的。这就说明,新增的属性,并不会触发视图刷新,也不会被劫持数据监听。删除属性,也不会触发视图刷新。

看到这里,我相信你应该已经挺兴奋的了。因为你已经距离能自己手动实现一个双向绑定不远了。但,我希望细心的朋友可以发现,整个数据劫持的过程中,利用 setter 来触发视图刷新,这种设计手法相当于什么?
没错,它就是我们平常所了解的 —— 观察者模式

这时候,有人问了:你这写的不严谨。你的属性都是基本数据类型,根本没提到引用数据类型数组要怎么处理啊!你个辣鸡!!!

小伙子,我很佩服你的勇气。

紧接着,我们继续测试,如果对象属性的值是引用类型的情况下,observe 的表现如何:

const obj = {name: '张三',age: 20,hobby: ['唱', '跳', 'rap'],address: {province: '广东省',city: '深圳市',district: '福田区'}
}observe(obj);obj.address.district = '南山区'
obj.hobby.push('篮球')console.log(obj);
// 输出 视图更新

咦?!为什么只输出了一个视图刷新???明明 hobby 也生成了 settergetter 啊!不应该是刷新两次吗???

原因就是,虽然我们封装的 defineReactive$$1 可以监听到这个属性值,但是,并不具备监听数组更新的能力。

得,又是一个坑。

其实,Vue2.0 通过这个API实现响应式,还是不尽如人意的。
但是,尤大大还是为我们提供了 Vue.setVue.delete ,供我们新增属性,删除属性。

那咋办呢?总不能写一半去跟面试官说,剩下的你来?

我们可以通过改写 Object.defineProperty 来拦截数组及其属性,但是我们并不知道数组一开始的长度是多少。因此,为了性能着想,尤大大可以说是另辟蹊径,开辟了一个新思路。

这时候就大胆一点啦,我们的思维不妨狂野一点。都自己手动实现响应式原理了,不如再动动脑筋,发散一下思维,接着处理一下 Array的原型 :

// 重新定义数组原型
import { def } from "utils.js";
// 复制 Array 的原型
const arrayPrototype = Array.prototype;
// 重塑新的 Array 原型
const arrayMethods = Object.create(arrayPrototype);
// 列举出影响属性变化以及需要改写原型的方法名
const methodsNeedChange = ["push","pop","shift","unshift","splice","reverse","sort"];
// 遍历方法名
methodsNeedChange.forEach(methodName => {let original = arrayPrototype[methodName];def(arrayMethods, methodName, function() {//这个this是指调用该方法的实例对象,即数组对象arrconst result = original.apply(this, arguments);//把类数组对象变为数组,从而在Observe中判断为数组,从而实现对元素的监视const args = [...arguments];//push , unshift, splice能增加新项,故也要变为observeconst ob = this.__ob__;let inserted = []; // 保存新增的数组元素,用于设置响应式switch(methodName) {case 'push':case 'unshift':inserted = args; //指def形参的第三个function的参数break;case 'splice':inserted = args.slice(2); //slice(start,end,newvalue) 开始结束时全闭区间break;}//判断inserted是否为空,让新增的项也成为响应式if (inserted) {//  ob就是OBserve类的实例对象 ob.arrayOberver(inserted)}// 工具函数的第三个参数是值,所以我们把方法处理完的结果返回即可return result;}, false)
})// 修改Observer类
export default class Observer {constructor(value) {// 绑定响应式标识 __ob__ 属性def(value, "__ob__", this, false);// 判断是否为数组if (Array.isArray(value)) {// 实现拦截Object.setPrototypeOf(value, arrayMethods);// 数组的子项也可能是数组, 故也要调用 observethis.arrayOberver(value);} else {this.walk(value);}}walk(data) {for (let k in data) {defineReactive$$1(data, k);}}arrayOberver(arr) {for(let i = 0, l = arr.length; i < l; i++) {observe(arr[i]);}}
}

这样,我们就能实现,既能劫持属性值为数组的变化,又不影响原来对数组的响应式的监听。

是吧,看到这里,你就会发现该API其实很拉垮,需要不断完善,才能勉强实现响应式。反之你也可以理解的尤大大的思维是有多么狂野。
但在 Vue3.0 中,改用了 proxy 处理响应式,实现了更完美的响应式。

四、实现 Vue2.0 的双向数据绑定

经过前面的分析,我相信你现在应该对实现2.0版本的双向数据绑定应该有思路了,让我们梳理一下:

  1. 组件挂载的时候,必须先遍历 data 对属性进行数据劫持;
  2. 生成 Dep 调度中心,准备收集观察者依赖;
  3. 利用 getter 函数埋入观察者;
  4. 利用 setter 函数通知 Watcher 更新数据;
  5. 如果 Model 层数据变动,利用调度中心通知观察者更新视图;
  6. 如果 View 层操控数据,利用调度中心通知观察者更新 data 对应的属性。

这里插一句,这篇文章代码部分是在 node 环境下示例的,也就是我可以使用 import 和 export 的关键。因为在这种开发环境下我的工作模式可以很单一,每一个文件都有它们自己的职责,而且每一个文件也只会注重自己需要做的事情。当然 Vue 源码也是这么做的,除了几份编译版的代码,但也是使用 rollup 打包出来的。

OK,现在我们从头到尾,循序渐进的,完整的实现一下整个响应式的过程:

  1. 实现数据劫持
  2. 处理数组原型
  3. DepWatcher 收集依赖
  4. 修改数据更新视图
  • 第一,需要一个入口判断数据类型:
import Observer from "Observer.js";
/*** observe.js* 此方法用于判断数据是否为对象以及挂载拦截* @param {Object} value 需要监听的对象* @return {Object} ob 响应式的标识
*/export default function observe(value) {// 判断是基本数据类型或者引用数据类型 objectif (typeof value != "object") return;let ob;// 判断这个对象或属性是否携带有响应式的标识if (typeof value.__ob__ != "undefined") {ob = value.__ob__;} else {ob = new Observer(value);}return ob;
}
  • 第二,实现工具函数,添加响应式标识:
/*** utils.js* 该工具函数用于给对象属性绑定属性* @param {Object} obj 需要绑定的对象* @param {String} key 需要绑定的属性名* @param {Any} value 需要绑定的属性值* @param {Boolean} enumerable 绑定的属性是否可枚举
*/
export const function def(obj, key, value, enumerable) {Object.defineProperty(obj, key, {value,enumerable,writable: true,configurable: true});
}
  • 第三,细化颗粒度,为数据挂载数据拦截:
// Observer.js
import { def } from "utils.js";
import Dep from "Dep.js";
import { arrayMethods } from "array.js";
import observe from "observe.js";
import defineReactive$$1 from "defineReactive.js";export default class Observer {constructor(value) {// 绑定响应式标识 __ob__ 属性def(value, "__ob__", this, false);// 埋入该属性的收集者this.dep = new Dep();// 判断是否为数组if (Array.isArray(value)) {// 实现拦截Object.setPrototypeOf(value, arrayMethods);// 数组的子项也可能是数组, 故也要调用 observethis.arrayOberver(value);} else {this.walk(value);}}walk(data) {for (let k in data) {defineReactive$$1(data, k);}}arrayOberver(arr) {for(let i = 0, l = arr.length; i < l; i++) {observe(arr[i]);}}
}
  • 第四,实现数据拦截:
/*** defineReactive.js* 该函数用于实现数据劫持* @param {Object} data 需要实现数据劫持的对象* @param {String} key 被劫持对象的属性名* @param {Any} value 被劫持对象的属性值* @return {Any} value 被劫持对象添加监听后的属性值
*/
import Dep from "Dep.js";
import observe from "observe.js";export default function defineReactive$$1(data, key, value) {// 判断参数有没有传入 value,如果没有传入则需要给其赋值,否则报错if (arguments.length == 2) value = data[key];// 埋入收集依赖执行过程的收集者,用于触发收集或触发更新const dep = new Dep();// 其子属性也有可能是对象,故也要监听let childOb = observe(value);Object.defineProperty(data, key {enumerable: true,configurable: true,get() {// Watcher 获取属性值的时候判断 Dep.targetif (Dep.target) {// 添加到需要通知更新的数组里面dep.depend();if (childOb) {// 如果子属性有响应式也要添加childOb.dep.depend();}}return value;},set(newValue) {if (newValue == value) return;value = newValue;// 新值子属性也可能是对象,故也要监听childOb = observe(newValue); // 更新值的时候,通知 Watcher 更新dep.notify();}})
}
  • 第五,处理数组原型:
// 重新定义数组原型
import { def } from "utils.js";// 复制 Array 的原型
const arrayPrototype = Array.prototype;
// 重塑新的 Array 原型
export const arrayMethods = Object.create(arrayPrototype);
// 列举出影响属性变化以及需要改写原型的方法名
const methodsNeedChange = ["push","pop","shift","unshift","splice","reverse","sort"];
// 遍历方法名
methodsNeedChange.forEach(methodName => {let original = arrayPrototype[methodName];def(arrayMethods, methodName, function() {// 这个this是指调用该方法的实例对象,即数组对象arrconst result = original.apply(this, arguments);// 把类数组对象变为数组,从而在Observe中判断为数组,从而实现对元素的监视const args = [...arguments];// push , unshift, splice能增加新项,故也要变为observeconst ob = this.__ob__;let inserted = []; // 保存新增的数组元素,用于设置响应式switch(methodName) {case 'push':case 'unshift':inserted = args; //指def形参的第三个function的参数break;case 'splice':inserted = args.slice(2); //slice(start,end,newvalue) 开始结束时全闭区间break;}// 判断inserted是否为空,让新增的项也成为响应式if (inserted) {//  ob就是OBserve类的实例对象 ob.arrayOberver(inserted)}// push , unshift, splice能增加新项,故需要通知 Watcher 更新ob.dep.notify();// 工具函数的第三个参数是值,所以我们把方法处理完的结果返回即可return result;}, false)
})
  • 第六,实现 Dep 类:
// 定义起始ID
let depid = 0;export default class Dep {constructor() {// 自增IDthis.id = depid++;this.subs = [];}addSubs(sub) {this.subs.push(sub);}depend() {if (Dep.target) {this.addSubs(Dep.target);}}notify() {const subs = this.subs.slice();for(let i = 0, l = subs.length; i < l; i++) {// 循环通知 Watcher 更新subs[i].update();}}
}
  • 第七,实现 Watcher 类:
import Dep from "Dep.js";
/*** Watcher.js* 该类用于实例观察者依赖* @param {Node} node 依赖的对象属性* @param {String} key 对象数据的属性名* @param {Object} vm 实例化的 Vue 数据对象
*/
// 定义起始ID
let watchId = 0;export default class Watcher {constructor(node, key, vm) {// 自增IDthis.id = watchId++;// 实例化的时候指向自己,方便 Dep 收集依赖Dep.target = this;// 依赖的对象属性,用于判断数据以什么方式更新this.node = node;this.key = key;// vm 实例化的 Vue 对象,包含 datathis.vm = vm;// 获取实例化的属性值,驱动 Dep 收集依赖this.getValue();}getValue() {try {this.value = this.vm.$data[this.key];} finally {// 设置为 null,防止死循环Dep.target = null;}}update() {this.getAndInvoke();}getAndInvoke() {this.getValue();if (this.node.nodeType === 1) {this.node.value = this.value;} else if (this.node.nodeType === 3) {this.node.textContent = this.value;}}
}
  • 第八,模拟虚拟 DOM
import Watcher from "Watcher.js";/*** render.js* 该方法用于实现虚拟 DOM 和挂载节点* @param {DOM} el 需要挂载的节点* @param {Object} vm 实例化的 Vue 数据对象
*/
export default function nodeToFragment(el, vm) {// 创建文档碎片let fragment = document.createDocumentFragment();let child;// 循环生成文档碎片while(child = el.firstChild) {compiler(child, vm); // 模板编译fragment.appendChild(child); // 将节点添加到文档碎片中}// 挂载文档el.appendChild(fragment);
}function compiler(node, vm) {// 每个节点都有个节点类型属性 nodeType 对应的值分别是 1.元素 2.文本 8.注释 9.根节点if (node.nodeType === 1) {// 如果是元素节点// 遍历所有的属性,判断是否有 v-model 指令[...node.attributes].forEach(item => {if (/^v-/.test(item.nodeName)) {new Watcher(node, item.nodeValue, vm);// nodeName 就是属性名node.value = vm.$data[item.nodeValue];node.addEventListener('input', () => {console.log(vm.$data[item.nodeValue]);vm.$data[item.nodeValue] = node.value;})}});// 元素节点还可能有很多子节点或孙子节点,因此需要递归处理[...node.childNodes].forEach(item => {compiler(item, vm);})} else if (node.nodeType === 3) {// 如果是文本节点// 检测该文本中是否包含胡须语法if (/\{\{\w+\}\}/.test(node.textContent)) {// 将胡须语法换为数据node.textContent = node.textContent.replace(/\{\{(\w+)\}\}/, function(a, b) {new Watcher(node, b, vm);return vm.$data[b];})}}
}
  • 第九,封装 Vue 函数
import observe from "observe.js";
import nodeToFragment from "render.js";/*** core.js* 该方法用于生成 Vue 实例* @param {Object} options 实例化所需的参数
*/
export default function Vue(options) {this.$data = options.data;this.$el = document.querySelector(options.el);observe(this.$data);nodeToFragment(this.$el, this);
}
  • 第十,例子:
<!DOCTYPE html>
<html><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实现双向绑定</title></head><body><div id="app"><input type="text" v-model="name"><h2>{{name}}</h2></div><script src="xuni/bundle.js"></script><script>const vm = new Vue({data: { name: 'vk是铁憨憨', hobby: ['唱','跳','rap'] },el: '#app'})</script></body>
</html>

看一下效果:

  • 搞个定时器,修改 data
<!DOCTYPE html>
<html><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实现双向绑定</title></head><body><div id="app"><input type="text" v-model="name"><h2>{{name}}</h2></div><script src="xuni/bundle.js"></script><script>const vm = new Vue({data: { name: 'vk是铁憨憨', hobby: ['唱','跳','rap'] },el: '#app'})setTimeout(() => {vm.$data.name = "vk是大帅逼";}, 3000)</script></body>
</html>
  • 看下效果如何:


最后,感谢你的阅读,码字真的很辛苦,给个三连吧!!!

代码已上传至码云,有需要的小伙伴自行下载吧 —— 《下载地址》

参考文献

  • B站尚硅谷 Vue 响应式视频
  • 掘金社区-西瓜watermelon的响应式文章

Vue2.0 —— 由设计模式切入,实现响应式原理相关推荐

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

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

  2. arduinowifi.send怎么获取响应_Vue3.0 响应式原理 (一)

    前几天,回顾整理下关于vue2.0的响应式原理.温故而知新么,那么今天,整理了一下关于vue3.0的响应式原理,利用 JavaScript 来写的.本着尽可能的清晰易懂的原则,所以,可能会分几篇文章来 ...

  3. matlabeig函数根据什么原理_vue3.0 源码解析二 :响应式原理(下)

    一 回顾上文 上节我们讲了数据绑定proxy原理,vue3.0用到的基本的拦截器,以及reactive入口等等.调用reactive建立响应式,首先通过判断数据类型来确定使用的hander,然后创建p ...

  4. Vue设计模式,发布订阅,响应式原理(简略版)

    Vue mvvm框架是什么? mvvm框架(model-view-viewMode),本质是mvc框架的改进版,mvc框架一旦项目复杂度越来越高,代码量大,维护起来很难,尤其管理层,controlle ...

  5. vue2和vue3响应式原理

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

  6. Vue2的响应式原理

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

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

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

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

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

  9. vue2 Object.definProperty响应式原理(面试题)

    注意: 响应式原理和双向数据绑定原理是两回事,一般面试官会先问响应式原理再问双向数据绑定的原理 详细文章 1.响应式原理 核心是数据劫持和依赖收集,是通过数据劫持结合发布者-订阅者模式的方式来实现的. ...

最新文章

  1. 远程控制软件VNC教程和对内网机器控制的实现
  2. ASP .NET Core MVC 控制器中获取当前登录用户
  3. matlab中 intval函数,经常用到取整的函数,今天小小的总结一下!其实很简单,就是几个函数而已~~主要是:ceil,floor,round,intval...
  4. MySQL安装错误:/usr/local/mysql/libexec/mysqld: unknown option '--skip-federated'
  5. 看了豆瓣的差评以后,我不建议你买我的书了。
  6. Java的面试汇总,有这些还担心不通过?
  7. 具体数学:计算机科学基础:第2版
  8. erlang---启动参数学习/研究
  9. Python:pathlib库使用方法
  10. 若想活得洒脱,就要学会看开
  11. 闭包、作用域链、函数
  12. 《高质量C++/C编程指南(林锐)》学习笔记
  13. 暴风影音CEO冯鑫的人生解读
  14. 腾讯邱跃鹏:解密腾讯亿级产品背后网络架构故事
  15. MATLAB绘制海面风场图像(海面风场反演)
  16. python获取鼠标选取的内容_Python三维可视化:鼠标选取交互操作
  17. Java进阶(四)多态
  18. python京东抢购手机攻略_Python实现自动上京东抢手机
  19. (SCA)正弦余弦算法SCA: A Sine Cosine Algorithm(代码可复制粘贴)
  20. 小白写了一堆if-else,大神实在看不下去了,竟然用策略模式直接摆平了

热门文章

  1. 4行代码实现微信送祝福,这个新年有点不一样
  2. AVL树实现对英文字典的查找
  3. 想在社会上混 就记住这20句
  4. 英飞凌电动汽车参考方案,包含原理图,和Bom清单
  5. 一种基于时间滑动窗口的黑产团伙挖掘算法
  6. idea svn IP地址更换方法
  7. 云脉文档管理小程序轻松解决文档管理难题
  8. luogu1091合唱队形
  9. shp文件显示 c语言,上传并在地图中显示Shp文件
  10. Sikuli+Selenium查询百度地图线路