Vue2.0 —— 由设计模式切入,实现响应式原理
Vue2.0 —— 由设计模式切入,实现响应式原理
《工欲善其事,必先利其器》
既然点进来了,麻烦你看下去,希望你有不一样的收获。
大家好,我是vk,好久不见,今天我们一起来盘一盘关于 Vue2.0 的响应式原理。注意,响应式才是 Vue 的核心,而双向绑定则是指 v-model 指令。所以一般面试时候面试官都会问你,能否讲讲响应式原理,或者简单实现一个数据的双向绑定。而不是让你实现一个双向绑定原理。
所以这时候,如果面试官问你,能否简单实现一下 Vue 的响应式?
你应该答:好的,等我10分钟,我先去看一下 vk 哥写的文章。(狗头)
所谓
MVVM
框架,即是数据驱动视图模型,分为Model
、View
和ViewModel
。目前市场上三大框架,只有Vue
是百分之百应用了MVVM
框架,所以它的核心实现和思想值得我们学习。
有的小年轻这时候就要肝了,你个辣鸡,Vue3.0 都出了那么久了,现在才来讲 Vue2.0 ,有个屁用?出来混,是要讲…
我只能说,小年轻,你还小,有些事,你不懂…
好的,其实一方面是由于自己个人原因,没办法经常写文章。其次,不管是 Vue2.0 还是 3.0,只要是源码,它就有学习的价值。不是说,你用什么,就学什么;而是,我想学什么,我就去研究什么。这是一个主观的意向,我的建议就是不要被动的去学习,那样成长是很缓慢的,而且很容易记不住。
另外,更完这篇 2.0 的原理,下一篇就研究更新 3.0 的原理,这样一有对比,岂不美哉?(狗头)
一、分析
我们都知道,Vue
在改变数据时,会自动刷新页面的 DOM
。同样,我们在页面输入数据,Vue
的 Model
层数据也会随之变化,这就是 Vue
的特性 —— 响应式原理。
最经典的例子就是输入框输入数据和其数据回显,任何一个地方改变数据,对应的数据都会发生改变,实现了数据的双向绑定。那么这到底是怎么做到的呢?我们来看一张图:
通过上面官方的图解我们可以理解并得到以下几点结论,我先把结论给你放出来了,尝试理解一下。如果不懂,没关系,我们后面会继续剖析它的原理乃至实现它:
- 组件实例化虚拟
DOM
时,如果需要访问我们Data
中的数据a
,那么我们就会先new Watcher
一个实例,在Watcher
实例中获取这个a
属性的值,并进行观察,这个过程就叫Touch
我们的getter
以获取数据。此时设置Dep.target = this
,即指向该Watcher
,保持全局唯一性。 - 根据
Dep.target = this
的全局唯一性,我们使用Object.defineProperty
对数据进行拦截,设置我们的getter
和setter
,如上图。此时getter
里面,若Dep.target
为true
,我们通知收集器Dep
把当前this
(即当前Watcher
)收集起来,以通知更新备用,同时返回该属性的值。 - 这时候再回到
Watcher
,值返回以后,为防止其他依赖(即其他Watcher
)触发getter
的同时把我们这个Watcher
又收集回去,我们需要把Dep.target
设置为null
。这就避免了不停的绑定Watcher
与Dep
,造成代码死循环。
鉴于 Dep
和 Watcher
两者之间这种微妙的关系,其实我们可以发现,这就是典型的应用了 —— 发布/订阅者设计模式
。
设计模式的本质就是使代码解耦,实现低耦合,形成代码的高可读性、高可重用性和高度扩展性;
这些特点对于一个库或者框架来说显得尤为重要。
以上就是响应式的收集依赖的过程了,这时候你千万不要懵,好戏才刚刚开始,我们开始剖析 —— 响应式。
二、理解 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 新增对象属性,我们可以观察到,跟直接添加对象属性相比较,多了 getter
和 setter
两个内置函数,分别用来拦截调用属性值和修改操作属性。
// 根据上面的 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有弊端。
对于已进行数据劫持的对象,他在新增属性时候,并不会为新属性绑定setter
和getter
。
对于已进行数据劫持的对象,他在删除属性的时候,并不会触发setter
三、浅析 Vue2.0 的响应式
通过上面的分析我们大概了解到,Vue
的数据劫持是怎么实现的。
这个时候其实很重要昂,数据劫持只是响应式的其中一环罢了。不过现在需要继续摸索,层层递进,我带你模拟一下响应式的简易的过程(由于篇幅原因,就不做太多的引导了,直接全部代码展示,希望你多跟着敲几遍,把它理解透):
- 我们需要一个入口,来传入以及分析数据类型,创建
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;
}
- 再次细化颗粒度,精确到每个对象属性或其子属性,给属性赋予拦截操作,
类(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})
}
- 创建
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);}}
}
- 创建
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 > getter
、age > setter
、name > getter
和 name > 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 > getter
和 name > 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
也生成了 setter
和 getter
啊!不应该是刷新两次吗???
原因就是,虽然我们封装的 defineReactive$$1
可以监听到这个属性值,但是,并不具备监听数组更新的能力。
得,又是一个坑。
其实,
Vue2.0
通过这个API实现响应式,还是不尽如人意的。
但是,尤大大还是为我们提供了Vue.set
和Vue.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版本的双向数据绑定应该有思路了,让我们梳理一下:
- 组件挂载的时候,必须先遍历
data
对属性进行数据劫持; - 生成
Dep
调度中心,准备收集观察者依赖; - 利用
getter
函数埋入观察者; - 利用
setter
函数通知Watcher
更新数据; - 如果
Model
层数据变动,利用调度中心通知观察者更新视图; - 如果
View
层操控数据,利用调度中心通知观察者更新data
对应的属性。
这里插一句,这篇文章代码部分是在 node 环境下示例的,也就是我可以使用 import 和 export 的关键。因为在这种开发环境下我的工作模式可以很单一,每一个文件都有它们自己的职责,而且每一个文件也只会注重自己需要做的事情。当然
Vue
源码也是这么做的,除了几份编译版的代码,但也是使用rollup
打包出来的。
OK,现在我们从头到尾,循序渐进的,完整的实现一下整个响应式的过程:
- 实现数据劫持
- 处理数组原型
Dep
和Watcher
收集依赖- 修改数据更新视图
- 第一,需要一个入口判断数据类型:
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 —— 由设计模式切入,实现响应式原理相关推荐
- 前端清单:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨...
前端每周清单第 25 期:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨,深入 React 动画 作者:王下邀月熊 编辑:徐川 前端每周清单专 ...
- arduinowifi.send怎么获取响应_Vue3.0 响应式原理 (一)
前几天,回顾整理下关于vue2.0的响应式原理.温故而知新么,那么今天,整理了一下关于vue3.0的响应式原理,利用 JavaScript 来写的.本着尽可能的清晰易懂的原则,所以,可能会分几篇文章来 ...
- matlabeig函数根据什么原理_vue3.0 源码解析二 :响应式原理(下)
一 回顾上文 上节我们讲了数据绑定proxy原理,vue3.0用到的基本的拦截器,以及reactive入口等等.调用reactive建立响应式,首先通过判断数据类型来确定使用的hander,然后创建p ...
- Vue设计模式,发布订阅,响应式原理(简略版)
Vue mvvm框架是什么? mvvm框架(model-view-viewMode),本质是mvc框架的改进版,mvc框架一旦项目复杂度越来越高,代码量大,维护起来很难,尤其管理层,controlle ...
- vue2和vue3响应式原理
vue2响应式原理:核心使用Object.defineProperty给属性定义get和set方法 注意:对象的多次递归,针对数组需要重写数组方法 函数劫持:把函数内部进行重写同时继续调用老的方法,在 ...
- Vue2的响应式原理
--------Vue2响应式原理---------- 原理:通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的 ...
- Day 05- Vue3 Vue2响应式原理
Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持 --> 给对象扩展属性 --> 属性设置 实现原理: ...
- vue2响应式原理解析并实现一个简单响应系统
vue2响应式原理 Object.defineProperty() 要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法.下面这些概念引自MDN. Obj ...
- vue2 Object.definProperty响应式原理(面试题)
注意: 响应式原理和双向数据绑定原理是两回事,一般面试官会先问响应式原理再问双向数据绑定的原理 详细文章 1.响应式原理 核心是数据劫持和依赖收集,是通过数据劫持结合发布者-订阅者模式的方式来实现的. ...
最新文章
- 远程控制软件VNC教程和对内网机器控制的实现
- ASP .NET Core MVC 控制器中获取当前登录用户
- matlab中 intval函数,经常用到取整的函数,今天小小的总结一下!其实很简单,就是几个函数而已~~主要是:ceil,floor,round,intval...
- MySQL安装错误:/usr/local/mysql/libexec/mysqld: unknown option '--skip-federated'
- 看了豆瓣的差评以后,我不建议你买我的书了。
- Java的面试汇总,有这些还担心不通过?
- 具体数学:计算机科学基础:第2版
- erlang---启动参数学习/研究
- Python:pathlib库使用方法
- 若想活得洒脱,就要学会看开
- 闭包、作用域链、函数
- 《高质量C++/C编程指南(林锐)》学习笔记
- 暴风影音CEO冯鑫的人生解读
- 腾讯邱跃鹏:解密腾讯亿级产品背后网络架构故事
- MATLAB绘制海面风场图像(海面风场反演)
- python获取鼠标选取的内容_Python三维可视化:鼠标选取交互操作
- Java进阶(四)多态
- python京东抢购手机攻略_Python实现自动上京东抢手机
- (SCA)正弦余弦算法SCA: A Sine Cosine Algorithm(代码可复制粘贴)
- 小白写了一堆if-else,大神实在看不下去了,竟然用策略模式直接摆平了