“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
举个简单的例子,对于模板:
<div id="root">{{ name }}</div>
创建一个 Vue 组件:
var vm = new Vue({
el: '#root',
data: {
name: 'luobo'
}
})
代码执行后,页面上对应位置会显示:luobo。
如果想改变显示的名字,只需要执行:
vm.name = 'tang'
这样页面上就会显示修改后的名字了,并不需要去手动修改 DOM 更新数据。
Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
vue不能监听数组的变化
Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。
Vue不能检测到对象属性的添加或删除
受现代JS的限制(以及废弃 Object.observe),Vue不能检测到对象属性的添加或删除,由于Vue会在初始化实例时对属性执行 getter/setter转化过程,所以属性必须在data对象上存在才能让Vue转换它,这样才能让它是响应的。
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应的
vm.b = 2
// `vm.b` 是非响应的
Vue不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property),然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上。
Vue.set(vm.someObject, 'b', 2)
也可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名。
this.$set(this.someObject,'b',2)
有时想向已有对象上添加一些属性,例如使用Object.assign()或 _.extend()方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Vue.js 3.0的一些新特性,其中一个很重要的改变就是Vue3 将使用 ES6的Proxy 作为其观察者机制,取代之前使用的Object.defineProperty。
Proxy 是ES6中新增的一个特性,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
基本用法:
//ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
参数解释
1、target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
2、handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法
Proxy 一共 支持13 种的拦截,相对Object.defineProperty更加丰富。
1.get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
2.set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
3.has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
4.deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
5.ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
6.getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
7.defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、
Object.defineProperties(proxy, propDescs),返回一个布尔值。
8.preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
9.getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
10.isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
11.setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
12.apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、
proxy.call(object, …args)、proxy.apply(…)。
13.construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
observe(data) {
const that = this;
let handler = {
get(target, property) {
return target[property];
},
set(target, key, value) {
let res = Reflect.set(target, key, value);
that.subscribe[key].map(item => {
item.update();
});
return res;
}
}
this.$data = new Proxy(data, handler);
}