1. MVVM模型
MVVM,是Model-View-ViewModel简写,Model代表数据模型,View代表视图,ViewModel是View和Model之间的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化会通知ViewModel层更新数据
2. Vue双向数据绑定原理
vue2
通过数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图
Object.defineProperty语法
Object.defineProperty(obj, prop, descriptor)
参数说明:
- obj:要定义属性的对象
- prop:要定义或修改的属性名称
- descriptor:要定义或修改属性所拥有的特性
-
descriptor-
当且仅当该属性的
configurable键值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false。 -
当且仅当该属性的
enumerable键值为true时,该属性才会出现在对象的枚举属性中,在枚举对象属性时会被枚举到(for...in或Object.keys方法), 默认为false -
value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为
undefined -
当且仅当该属性的
writable键值为true时,属性的值,也就是上面的value,才能被赋值运算符改变。 默认为false
-
数组
对于数组响应式处理,是通过覆盖数组原型的方法,扩展7个变更方法(push,pop,shift,unshift,splice,sort,reverse)可以额外的做更新通知,做出响应
由于JavaScript的限制,vue不能检测以下变动数组:
- 利用索引直接修改数组,例如:vm.items[indexOfItem] = newValue,可通过Vue.set()
- 修改数组长度时,例如:vm.items.length = newLength,可通过splice()
实际上Object.defineProperty是可以检测到数组索引的变化的,vue没有使用这个方式去监听数组索引的变化,是因为性能消耗太大,于是在性能与用户体验之间做了取舍,原因非常明显,在初始的过程中没有循环对所有数组索引监听,但是开发者需要监听哪个索引。Vue.set就帮你监听哪个,核心还是Object.defineProperty。只是尽可能的避免了无用的数组索引监听
Object.defineProperty虽然能检测索引的变化,但的确是监听不到数组的增加或删除的,
Vue的解决方案,就是重写了数组的原型,更准确的表达是拦截了数组的原型
Object.defineProperty不能检测对象的新增或删除
vue3
Vue3的响应式是基于ES6的Proxy代理来实现的
实现原理:
- 通过Proxy代理:拦截对象属性的操作,包括属性增删改查
- 通过Reflect反射:对源对象的属性进行操作
Proxy
Proxy用来创建对象代理,第一个参数target为要拦截的目标对象,第二个参数handler为配置对象,指定拦截后的行为, get和set等方法就要写在配置对象中。
Proxy 是 ES6 新增的一个构造函数,用来创建一个 目标对象的代理对象,拦截对原对象的所有操作;用户可以通过注册相应的拦截方法来实现对象操作时的自定义行为。
目前 Proxy 支持的拦截方法包含一下内容:
get(target, propKey, receiver):拦截对象属性的读取操作;set(target, propKey, value, receiver):拦截对象属性的赋值操作;apply(target, thisArg, argArray):拦截函数的调用操作;construct(target, argArray, newTarget):拦截对象的实例化操作;has(target, propKey):拦截in操作符;deleteProperty(target, propKey):拦截delete操作符;defineProperty(target, propKey, propDesc):拦截Object.defineProperty方法;getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor方法;getPrototypeOf(target):拦截Object.getPrototypeOf方法;setPrototypeOf(target, proto):拦截Object.setPrototypeOf方法;isExtensible(target):拦截Object.isExtensible方法;preventExtensions(target):拦截Object.preventExtensions方法;enumerate(target):拦截for...in循环;ownKeys(target):拦截Object.getOwnPropertyNames、Object.getOwnPropertySymbols、Object.keys、JSON.stringify方法。
与 Object,defineProperty 比起来,有非常明显的优势:
- 拦截操作更加多样
- 拦截定义更加直接
- 性能更加高效
在 Vue 中体现最为明显的一点就是:Proxy 代理对象之后不仅可以拦截对象属性的读取、更新、方法调用之外,对整个对象的新增、删除、枚举等也能直接拦截,而 Object.defineProperty 只能针对对象的已知属性进行读取和更新的操作拦截。
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。