Vue2的响应式原理
--------Vue2响应式原理----------
原理:通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的每一个属性进行劫持并通过 defineProperty() 设置值后在 get() 中向发布者添加该属性的订阅者,这里在编译模板时就会初始化每一属性的 watcher,在数据发生更新后调用 set 时会通知发布者 notify 通知对应的订阅者做出数据更新,同时将新的数据根性到视图上显示。
缺陷:只能够监听初始化实例中的 data 数据,动态添加值不能响应,要使用对应的 Vue.set()。
1.第一步:对数据做数据劫持:
1.1对象类型做数据劫持
使用 Object.defineProperty 方法添加对象,重写了原有的 get 和 set 方法,这就是数据劫持。
defineRective 文件通过封装 Object.defineProperty 方法,把浅层次的对象中的某个数据变成具有 get 和 set 方法的属性。因为有闭包的存在,所以不需要临时变量进行周转了。
接下来是深层次:用递归侦测对象的全部属性
八个 JS 文件互相调用(文件代码,放在文章下方):
index.js:入口文件
observe.js:用于判断某一属性是否为对象或者数组,因为 typeof(array) 返回的也是 object,算是一个局限性。普通数据就直接 return,对象(数组)就给它调用 new Observer
Observer.js:对传入的属性做类型判断,然后分别转化为可被监测的属性。
def.js:为属性添加 ob 属性,做标记,而且可以通过 value.ob 来访问 Observer 的实例
defineRective.js:给传入的属性做数据劫持(即添加 set/get 方法),因为是对属性进行操作的,不做类型判断,因此不论这个传入过来的属性是数组还是对象,都会有 get/set 方法。
array.js:该文件将 JS 中能改变数组的 7 个方法重写,并在进行数据劫持的时候将,数组的原型指向该文件加工后的新原型。
Dep.js:在依赖收集阶段,Dep 对象是 Watcher 对象和 Observer 对象之间纽带,每一个 Observer 都有一个 Dep 实例,用来存储订阅者 Watcher
Watcher.js:当解析到模板字符串 {{ }} 时,会默认去 new Watcher 实例。
过程:
- 首先将需要做监听的对象传入 observe 方法内,如果传进去的不是对象(第一次传入的数据毫无疑问是对象,但是后续的子属性还会再次调用 observe 函数,子属性的类型就很复杂了,因此需要有这层判断),就会直接return。如果是对象(或者数组因为 typeof(array) 返回的也是 object),就往下走。
- 此时已经确定了,传入的是个对象(数组),紧接着会判断这个对象(数组)有没有 ob 属性,有则代表已做过监视了,如果没有,就用它 new 一个 Observer 实例。
- 在new Observer 实例的过程中,会调用 def 方法给该实例添加一个 ob 属性(做个标记)。然后如果是对象则调用 walk 方法,walk 会遍历该对象中的每一项并用 defineReactive 方法加工。如果是数组则修改它的原型(里边有重写好的 API)为 arrayMethods,随即调用 observeArray 方法。
- defineReactive 方法用于对传入的属性做数据劫持(重写 get/set 方法)。因为是对属性进行操作的,因此即使传入的是数组,它也一定有 get/set 方法。
- 在 defineReactive 做数据劫持前,仍需再调用一次 observe 方法,去判断当前属性是否还是一个对象,如果是,就会再重复 1-3 的过程。此时这几个文件就形成了递归,直到某一次传入的属性不再是一个对象时,结束递归。当递归结束时,这个对象内的所有属性就都做好了数据劫持。
- 其中在 defineReactive 中还需要在 set 方法中将获取到的新值再一次使用 observe 方法,变成可监视的,因为这个新值也有可能是对象(或数组),如果不是,那么就会在 observe.js 文件中中直接 return 了。
图解:
几个文件的调用关系:
1.2.数组类型做数据劫持:
思路:
- 在 array.js 文件中以 Array.prototype 为原型,复制出一个新原型 arrayMethods,再把这个新原型上的 7 个可以改变数组的 API 方法全部重写,分别是:push、pop、shift、unshift、splice、sort、reverse。
- 这 7 个方法在保留原功能的基础上增加一些数据劫持的代码(也就是将数据变为可监控的),最后把 arrayMethods 暴露出去。
- 当在 new Observer 时判断传入的数据为数组后,使用 Object.setPrototypeOf() 方法强制让传入的那个数组的原型指向 arrayMthods,这样一来,这个数组以后就会使用我们重写好的 API,就达成响应式的目的。
- 随后要再一次对数组内的数据进行遍历,因为数组内部,或许还会有对象类型(或者数组类型)的数据。这就相当于用一个拦截器覆盖 Array.prototype,每当使用 Array 原型上的方法操作数组时,先执行 arrayMthods 拦截器中的数据劫持方法,再执行原生 Array 的方法去操作数组。
和上述对象的文件嵌套相比,增加了一个 array.js 文件。
2.第二步:依赖收集
需要用到数据的地方,称为依赖
Vue1.x, 细粒度依赖, 用到数据的 D0M 都是依赖;
Vue2.x, 中等粒度依赖, 用到数据的 组件 是依赖;
之所以要劫持数据,目的是当数据的属性发生变化时,可以通知那些曾经用到的该数据的地方。所以要先收集依赖,把用到这个数据的地方收集起来,等属性改变后,在之前收集好的依赖中循环触发一遍就好了,达到响应式的目的。
针对不同的类型:
在 getter() 中收集依赖,在 setter() 中触发依赖 // 对象类型
在 getter() 中收集依赖,在 拦截器 中触发依赖 // 数组类型
2.1前提:
此时已经进行过数据劫持了。
把 new Watcher 这个过程看作是 Vue 解析到了 {{ }} 模板的时候。
Dep.target 的值存在时,表示正处于依赖收集阶段。
Vue 在模板编译过程中遇到的指令和数据绑定都会生成 Watcher 实例,实例中的 Watch 属性也会成生成 Watcher 实例。
2.2过程:
- 在创建 Observer 实例的同时还会创建 Dep 实例,用于保存依赖项。因此每个数据都有 Observer 的实例,每个 Observer 实例中又都有一个 Dep 的实例。
- 当 Vue 解析到 {{ }} 中数据时,就会去创建 Watcher 实例,在 constructor 时会调用自身的 get 方法,该方法不仅将当前的 Watcher 实例赋值给了 Dep.target(表示此时处于依赖收集阶段),还让这个新实例去读取一下 {{ }} 中的数据,一旦读取,就会触发这个数据的 getter 方法。因为此时正在进行收集依赖,Dep.target 一定是为 true 的,于是顺利地把当前的这个 Watcher 实例记录到了 dep 中的 subs 数组里。再然后将 Dep.target 的值重新赋值为 null,表示退出依赖收集阶段。
- 为什么能记录到 subs 数组呢?因为在 defineReactive 文件的 17 行新 new 了一个 Dep 实例,这个实例只是一个工具人,通过调用工具人身上的 depend 函数,就将当前时刻的 Watcher 实例添加进去。这样一来当模板解析完毕,dep 实例就掌握这个数据的所有订阅者。
- 当数据的 set 方法被调用时,就执工具人的 dep.notify 方法,他会遍历 dep 实例身上的 subs 数组,这个数组存放了当前数据的所有订阅者,即许多 Watcher 实例,调用每一个 Watcher 实例身上的 update 方法,执行传入过来的回调函数,然后 Vue 接下来通过这个回调函数去进行 diff 算法,对比新旧模板,然后重新渲染页面,至此算是达到了响应式的目的。
因此,这个 Watcher 实际上是 Vue 的主程序在用。更新视图的代码应该是要写在传入过去的回调函数里。
3.最后总结:
3.1前置知识:
首先要了解三个最重要的对象:
Observer 对象:将 Vue 中的数据对象在初始化过程中转换为 Observer 对象。
Watcher 对象:将模板和 Observer 对象结合在一起生成 Watcher 实例,Watcher 是订阅者中的订阅者。
Dep对象:Watcher 对象和 Observer 对象之间纽带,每一个 Observe r都有一个 Dep 实例,用来存储订阅者 Watcher。
3.2过程:
- 在生命周期的 initState 方法中将 data,prop,method,computed,watch等所有数据全部进行数据劫持,将所有数据变为 Observer 实例,并且每个数据身上还有 Dep 实例。
- 然后在 initRender 方法中也就是模板编译过程,遇到的指令和数据绑定都会生成 Watcher 实例,并且把这个实例存入对应数据的 Dep 实例中的 subs 数组里。这样每一个数据的 Dep 实例里就都存放了依赖关系。
- 当数据变化时,数据的 setter 方法被调用,触发 dep.notify 方法,就会通知 Dep 实例依赖列表,执行 update 方法通知 Watcher,Watcher 会执行 run 方法去更新视图。
- 更新视图的过程,我猜是 Vue 接下来要进行 diff 算法,对比新旧模板,然后重新渲染页面。
Vue 是无法检测到对象属性的添加和删除,但是可以使用全局 Vue.set 方法(或 vm.$set 实例方法)。
Vue 无法检测利用索引设置数组,但是可以使用全局 Vue.set方法(或 vm.$set 实例方法)。
无法检测直接修改数组长度,但是可以使用 splice。
代码文件:
1.index.js:入口文件
import observe from './observe';
import Watcher from './Watcher'
// 因为 Vue 会把所有的数据都存放在 data 对象中,所以一切数据的最外层都是一个对象
let obj = {a: {m: {n: 5}},c: {d: {e: {f: 6666}}},g: [22, 33, 44, 55]
}
observe(obj)// new Watcher 的过程,看作 Vue 在解析到了 {{}} 的时候, new 一次,subs 数组里就多一个数据,表示对有三处模板(可以看到每一个 watcher 的id 是不同的)用到了 a.m.n 属性
// 因此当第二次修改数据时,有两个 watcher 实例在监视它,就会输出两次值,就代表这需要重新渲染两次
// 同理第三次修改,三个 watcher 实例,渲染3次
new Watcher(obj, 'a.m.n', (val,oldValue) => {console.log('#######', val,oldValue);
})
console.log(obj);// 2s后修改值
setTimeout(() => {obj.a.m.n = 88
}, 2000);
2.observe.js
用于判断某一属性是否为对象或者数组,因为 typeof(array) 返回的也是 object,算是一个局限性。普通数据就直接 return,对象(数组)就给它调用 new Observer
import Observer from "./Observer";export default function (value){// 如果 value 不是对象或者数组,就直接返回。此处因为 typeof 的局限性,typeof(数组) 仍会返回 object// 因为 Vue 中不会单独存放 int、float 等类型的数据,毕竟它们没法调用 Object.defineProperty// 况且在 Vue 中数据都是存放在对象中的,所以根本不考虑其他数据类型if(typeof (value) !== 'object') return;// 定义ob,存储 observe 实例var ob;if(typeof value.__ob__ !== 'undefined') {ob = value.__ob__;} else {ob = new Observer(value)}return ob;
}
3.Observer.js
对传入的属性做类型判断,然后分别转化为可被监测的属性。
import { def } from './def'
import defineRective from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
import Dep from './Dep'/*** 该类的作用:将一个正常的 object 的每个层级的属性都转化为可以被侦测的属性*/export default class Observer {constructor(value) {this.dep = new Dep();// 构造函数的this不是类本身,而是表示实例。// 添加 __ob__ 属性,值是这次 new 的 Observer 的实例,不可枚举// _ob__的作用可以用来标记当前value是否已经被Observer转换成了响应式数据了;而且可以通过value.__ob__来访问Observer的实例console.log('我是 Observer 构造器,接下来要用 def 方法去给传入的对象值添加 __ob__ 属性', value)def(value, '__ob__', this, false)// 检查这个数据是数组还是对象if (Array.isArray(value)) {console.log('传入的是数组,我将改变它的原型为 arrayMethods ')// 是数组,就让他的原型指向 arrayMethods。到此,数组的监控已经加工完毕Object.setPrototypeOf(value, arrayMethods)// 随后再遍历这个数组,因为数组内部,或许还会有对象类型的数据,肯定也要变为可监控的this.observeArray(value)} else {console.log('def 方法执行完毕,接下来要用 walk 方法去遍历这个对象', value)// 让对象数据变为可监控的this.walk(value)}}// 对象的特殊遍历walk(value) {for (let key in value) {console.log(`我是 walk 方法,这次遍历了对象中的 ${key} 属性,并用 defineReactive 方法给它加工一下 `);defineRective(value, key)}}// 数组的特殊遍历observeArray(arr) {// 逐项进行 observe,因为数组内部,或许还会有对象类型的数据,肯定也要变为可监控的for (let i = 0, l = arr.length; i < l; i++) {// console.log(arr[i]);observe(arr[i])}}
}
4.def.js
为属性添加 ob 属性,做标记,而且可以通过 value.ob 来访问 Observer 的实例
// 对传入过来的数据添加指定的属性
/*** 定义一个对象属性* @param {*} obj * @param {*} key * @param {*} value * @param {*} enumerable */
export const def = function (obj, key, value, enumerable){Object.defineProperty(obj, key,{value,enumerable,writable: true,configurable: true})
}
5.defineRective.js
给传入的属性做数据劫持(即添加 set/get 方法),因为是对属性进行操作的,不做类型判断,因此不论这个传入过来的属性是数组还是对象,都会有 get/set 方法。
// 该文件将传入过来的属性加工成具有 get 和 set 方法的响应式数据
// 因为是对属性进行操作的,不做类型判断,因此不论这个传入过来的属性是数组还是对象,都会有 get/set 方法/*** 给对象data的属性key定义监听* @param {*} data 传入的数据* @param {*} key 监听的属性* @param {*} val 闭包环境提供的周转变量*/import observe from "./observe"
import Dep from "./Dep"export default function defineRective(data, key, val) {// 这里 new Dep 实际上是个工具人,只起到一个调用 depend 函数的作用,他不保存在任何数据身上。// 调用 depend 函数时,是将代码正在运行的那个时刻的 Watcher 实例添加进去,因此不会影响。const dep = new Dep()// 如果传入两个参数,则直接取出值给 valif (arguments.length == 2) {val = data[key]}// 对传过去的每一项还要 observe 一下,如果不是对象(数组)了就会直接 return,代码往下走。// 如果仍是对象(数组),那么就形成了递归,直到不是某一层不是对象(数组)为止。这个递归比较特殊不是函数自己调用自己,而是多个函数循环调用let childOb = observe(val) // 这里接收的是子代属性创建的 Observer 的实例对象,用于后续做依赖收集// val 构成了闭包:后续代码有调用到 val 的地方,因此 val 不会消失。Object.defineProperty(data, key, {// 可枚举enumerable: true,// 可以被配置,比如可以被deleteconfigurable: true,// getter 触发这个方法,就会将数据添加到依赖中get() {console.log(`访问了 obj 的 ${key} 属性,值为${val}`)// 如果现在处于依赖收集阶段,即在模板解析的时候,就会调用 setter 方法,就会往 subs 里添加东西 if(Dep.target){console.log('访问了watcher');// console.log(data.__ob__.dep.depend);// debugger;// 将此时的 Watcher 实例对象添加 dep 中的 subs 数组里// 在这里为什么要重新 new Depdep.depend()// 这里为什么执行不了 depend 函数???// 既然17行 new的实例只是工具人,起到调用 depend 函数的作用,那我这里随便访问一个 dep 实例并调用他身上的 depend 不可以吗?// data.__ob__.dep.depend()// 给子元素也添加依赖if(childOb){childOb.dep.depend();}}return val},// setter set(newValue) {console.log(`改变了 obj 的 ${key} 属性,新的值为${newValue}`)if (val === newValue) {return}val = newValue// 当设置了新值,这个新值可能也包含对象或者数组,因此也要被 observe childOb = observe(newValue) // 这里我不理解为什么还要用 childOb 接收一下// 发布订阅模式,通知 dep 对依赖进行修改dep.notify()}})
}
6.array.js
该文件将 JS 中能改变数组的 7 个方法重写,并在进行数据劫持的时候将,数组的原型指向该文件加工后的新原型。
import { def } from './def.js'
// 得到 Array.prototype
const arrayPrototype = Array.prototype// 以 Array.prototype 为原型对象创建 arrayMethods 对象,并暴露出去
export const arrayMethods = Object.create(arrayPrototype)console.log(arrayMethods)// 需要改写的7个方法
const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]// 通过遍历给新原型的7个方法内部都添加一些新的内容
methodsNeedChange.forEach((methodName) => {// 备份原来的方法,因为劫持后仍然需要原生的 API 去改变数据const original = arrayPrototype[methodName]// 定义新的方法def(arrayMethods, methodName, function () {// 这个重写函数分为两步走:// 1.使用原来的功能去操作数组:const result = original.apply(this, arguments)// 把类数组 arguments 变为数组,类数组没有 slice 方法,要不然下边无法正常切割了const args = [...arguments]// 2. 把新添加的值(或修改后的值)都加工成可监控的:// 把这个数组身上的 __ob__ 取出来,此时 __ob__ 已经被添加了/** 为什么这里会有 __ob__ 属性呢? * 因为当 walk 函数遍历到数组 g 时,会继续按照流程走: defineReactive 文件把 g 交给 observe 文件,observe 判断 g 没有 __ob__,就会再交给 Observer 文件* 紧接着 Observer 在 16 行用 def 方法给 g 添加 __ob__ 属性。一直到这里才做是否为数组的判断,到这里才用到了 array 文件* 因此在这里完全可以取到 __ob__ 属性,它的值就是 g 的 Observer 实例本体*/const ob = this.__ob__// 有三种方法 push/unshift/splice 能够插入新项,现在ob__要把插入的新项也要变为 observe 的let inserted = [];switch (methodName) {case 'push':case 'unshift':inserted = args;break;case 'splice':// 因为 splice 方法的三个参数代表:(下标, 数量, 插入的新项) 因此用 slice 取到插入进去的那个数据inserted = args.slice(2);break;}// 判断有没有要插入的新项,如果有,就调用 observeArray 方法(来自数组的 Observer 实例身上),因为新的数据可能也包含对象类型的if (inserted) {ob.observeArray(inserted);}// 能输出这句话代表重写方法成功console.log('能输出这句话代表重写你所使用的那个数组 API 重写成功');// 通知依赖进行数据的更新ob.dep.notify()// 必须要有返回值,因为一些 pop、splice 的方法会返回被操作的值return result}, false);
})// export default arrayMethods
7.Dep.js
在依赖收集阶段,Dep 对象是 Watcher 对象和 Observer 对象之间纽带,每一个 Observer 都有一个 Dep 实例,用来存储订阅者 Watcher
var uid = 0
export default class Dep {constructor() {console.log('我是 Dep 构造器');this.id = uid++;// 用数组存储自己的订阅者 实际存放的是许多的 Watcher 实例this.subs = []}// 添加订阅addSub(sub) {this.subs.push(sub)}// 删除订阅removeSub(sub) {remove(this.subs, sub);}// 添加依赖depend() {// 在调用这个函数时,一定是出于依赖收集阶段,因此 Dep.target 是存在的if (Dep.target) {this.addSub(Dep.target)}}// 通知更新notify() {console.log('我是 notify');// 浅克隆一份const subs = this.subs.slice()// 遍历for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}
}/*** 从arr数组中删除元素item* @param {*} arr * @param {*} item* @returns */
function remove(arr, item) {if (arr.length) {const index = arr.indexOf(item);if (index > -1) {return arr.splice(index, 1);}}
}
8.Watcher.js
当解析到模板字符串 {{ }} 时,会默认去 new Watcher 实例。
/*** 每一次的 new Watcher 都是独立的,因此构造器接收的三个参数,虽然名字一样但确实不同的数据,就像是 vm.$watch() 接收的参数一样,* @param {*} target 需要监视的对象,当做修改时,他就是* @param {*} expression 这个对象中的某个属性,它是一个表达式 比如 obj.a.b.c* @param {*} callback 回调函数,需要执行的操作 */import Dep from "./Dep";// 这个 uid 用于对每一个的 Watcher 实例添加唯一的 id
var uid = 0// 在这里哪一步算是调用了 get 方法???????,解析到模板的时候export default class Watcher {constructor(target, expression, callback) {console.log('我是 Watcher 构造器');this.id = uid++;// 模板字符串中的整个表达式this.target = target;// 通过拆分表达式(对象中的对象...),获得需要 Watch 的那个数据。比如传入的是 a.b.c.d 我们需要监视属性 d,就需要拆分this.getter = parsePath(expression) // 有两种方法供使用 parsePath 会返回一个函数;如果用 reduce 方法,那么 getter 就会是一个具体的值,此时一定要修改下边的 get 方法!!!this.callback = callback// 调用该方法,进入依赖收集阶段this.value = this.get()}// 当更新 dep 中的依赖项时,会调用每一个 Watcher 实例身上的 update 方法update() {console.log('我是Watcher实例身上的update方法');this.run()}// 进入依赖收集阶段,让全局的 Dep.target 设置为 Watcher 本身get(){// Webpack 在打包的时候 Dep 是全局唯一的,不管多少个JS 文件在用 dep 的时候,都是这一个文件// 因此执行到这里console.log(this); // Watcher 实例Dep.target = this; // debugger;const obj = this.target;var value;// 防止找不到,用try catch一下,只要能找,就一直找try {value = this.getter(obj) // 获取需要监视的那个值。这里因为constructor 的时候 this.get() 返回的是一个函数} finally {Dep.target = null // 清空全局 target 的指向,同时也表示退出依赖收集阶段}return value }// 其实可以直接 getAndInvoke,但是 Vue 源码时这样写的run(){this.getAndInvoke(this.callback)}//getAndInvoke(callback){// 获取到修改后的新值 旧值是 this.valueconst value = this.get()if(value !== this.value || typeof value == 'object'){const oldValue = this.value;this.value = value;callback.call(this.target, value, oldValue)}}
}// 拆分表达式:
// 方法一:将 str 用 . 分割成数组 segments,然后循环数组,一层一层去读取数据,最后拿到的 obj 就是 str 中想要读的数据
// 假设 let o = {a:{b:{c:{d:55}}}},我想要取得 d 的值,经过拆分后的 segments 数组的值为 ['a', 'b', 'c', 'd']
// 第一次循环后 obj = {b:{c:{d:55}}}, 第二次 obj = {c:{d:55}}, 第三次 obj = {d:55}, 第四次 obj = 55
function parsePath(str) {let segments = str.split(".");return function (obj) {for (let key of segments) {if (!obj) return; // 当没有传入 obj 时,直接 returnobj = obj[key];}return obj;};
}
// 方法二 用 reduce 方法实现
// function parsePathReduce(str) {// let segments = str.split(".");
// let result = segments.reduce((total, item) => {// total = total[item]
// return total
// }, str)
// return result
// }
Vue2的响应式原理相关推荐
- 【Vuejs】952- 一文带你了解vue2之响应式原理
在面试的过程中也会问到:请阐述vue2的响应式原理?,凡是出现阐述或者理解,一般都是知无不言言无不尽,知道多少说多少.接下来,我来谈谈自己的理解,切记不要去背,一定要理解之后,用自己的语言来描述出来. ...
- 实现vue2.0响应式原理
很久之前为了面试,看了一些分析源码的博客,没有最近重新看一下vue的源码,匆匆浏览记住一些概念,因为懒惰,后面也没有再去深入探索:前段时间痛定思痛,觉得不能这样下去,过一下vue源码,此文章也是为了记 ...
- Day 05- Vue3 Vue2响应式原理
Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持 --> 给对象扩展属性 --> 属性设置 实现原理: ...
- vue2响应式原理解析并实现一个简单响应系统
vue2响应式原理 Object.defineProperty() 要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法.下面这些概念引自MDN. Obj ...
- 初始Vue响应式原理~~
自从 Vue 发布以来,就受到了广大开发人员的青睐,提到 Vue,我们首先想到的就是 Vue 的响应式系统,那响应式系统到底是怎么回事呢?接下来我就给大家简单介绍一下 Vue 中的响应式原理. vue ...
- 彻底理解Vue数据响应式原理
彻底理解Vue数据响应式原理 当创建了一个实例后,通过将数据传入实例的data属性中,会将数据进行劫持,实现响应式,也就是说当这些数据发生变化时,对应的视图也会发生改变. const data = { ...
- 面试官:说说Vue响应式原理
前言: 经常有面试官会问"你能说说vue的响应式原理吗?很多不明就里的人会说是v-model,其实面试官想问的是vue能实现响应式使用的是JS中的什么API,而且v-model这个属于双向数 ...
- Vue响应式原理探究之“发布-订阅”模式
前言 在面试题中经常会出现与"发布订阅"模式相关的题目,比如考察我们对Vue响应式的理解,也会有题目直接考验我们对"发布订阅"模式或者观察者模式的理解,甚至还会 ...
- Vue2.0 —— 由设计模式切入,实现响应式原理
Vue2.0 -- 由设计模式切入,实现响应式原理 <工欲善其事,必先利其器> 既然点进来了,麻烦你看下去,希望你有不一样的收获. 大家好,我是vk,好久不见,今天我们一起来盘一盘关于 V ...
最新文章
- python hexdump_hexdump用法
- 发现问题,是解决问题的第一步
- 让我感动的经典台词(zz)
- python oracle 运维,mysql oracle python连接
- 杨薇天津大学计算机专业,高考志愿报得好,一生幸福少不了,遇到问题怎么办?静海一中校友来支招儿~...
- 一文了解 Kubernetes
- java生成pdf表格_java在pdf中生成表格的方法
- 分享几道经典的javascript面试题
- elasticsearch threadpool
- 六、区块链主流共识算法浅析
- ios 10.3 汉字的中划线
- Android thumbnail 图片的获得及与原始图片的映射
- 《Java安全编码标准》一第 1 章 概 述
- WAP开发环境的设置[IIS, APACHE, HTTPD]
- 浙江高级职称英语 计算机考试时间2016,浙江省2016年度全国专业技术人员职称外语等级统一考试时间...
- 2017年十大移动应用开发的测试工具
- fanuc系统服务器连接,FANUC IO LINK i地址分配操作方法
- python毕业设计项目源码选题(19)篮球、足球、羽毛球等运动场地预约系统毕业设计毕设作品开题报告开题答辩PPT
- 机器人动力学与控制学习笔记(七)————基于计算力矩法的滑模控制
- inet_ntop函数的简单实现及调用
热门文章
- 什么是MySQL的预编译?
- 开发openfire的内部组件
- freeswitch
- Linux中samba配置和windows映射Linux驱动盘
- 【ARM-8】MPIDR_EL1, Multiprocessor Affinity Register 多处理器关联寄存器
- FCoin“出事”后,团队关键人物回复了!
- 网站流量统计与网站访问分析
- matlab 理想低通滤波器函数,基于MATLAB的理想低通滤波器的设计
- Unity+Vuforia+Hololens2 AR开发
- JS-计算日期差,计算日期之间的月数