在vue2.x版本官方文档中
深入响应式原理 https://cn.vuejs.org/v2/guide/reactivity.html一文的解释当中,Object.defineProperty将声明响应式 property数据的状态转换为getter和setter。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor) obj: 要在其上定义属性的对象 prop: 要定义或修改的属性的名称或Symbol descriptor: 定义或修改的属性的描述符 * @descriptor * * configurable 为 true 时,属性才能重新被定义,默认为 false。 * enumerable 为 true 时,该属性才能够出现在对象的枚举属性中,此时才能通过for in遍历属性。默认为 false。 * writable 为 true 时,value属性值才能被修改。默认为 false,此时value属性值为只读状态。 * value 该属性对应的初始值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 * get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行。默认为 undefined。 * set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。 * 注意事项:get 和 set 它们与value和writable是互斥的。一旦使用它们,则这个属性就没有保存属性值的能力
let userInfo = { age: "11", }; console.log("初始userInfo:", userInfo); // Object.defineProperty新增对象属性 Object.defineProperty(userInfo, "name", { value: "zhangsan", enumerable: true, //将enumerable设为true 才能够出现在对象的枚举属性 }); console.log("设置name后的userInfo:", userInfo); console.log("设置name后的userInfo属性:", Object.keys(userInfo));
当enumerable设置为false的时候,我们通过Object.keys枚举属性,并不能获取到name属性
let userInfo = { age: "11", }; let initAge = null; Object.defineProperty(userInfo, "age", { enumerable: true, configurable: true, get: function () { console.log("get属性方法,当前对象:", this); return initAge; }, set: function (newValue) { console.log("set属性方法", newValue); initAge = newValue; }, }); userInfo.age = "30"; console.log("最后userInfo", userInfo);
通过get和set操作对象属性类似于我们在vue开发过程中的计算属性computed详解,通过get和set对当前对象进行设置属性值
回到开头提到的官方文档中描述的,响应式原理中,如下图
图片源自vue官方文档深入响应式原理: https://cn.vuejs.org/v2/guide/reactivity.html
当组件渲染时,通过对象劫持,遍历data状态,那么需要考虑的点是:
假如在组件运行时,我们需要额外添加状态的时候,添加一个新的状态,或者在原有的状态下,添加一个新的属性会发生什么,这个新的状态并不在组件劫持的状态之内:
<p> <span style="cursor: pointer; color: red" @click="handleUser" >点击修改不在data状态下的值</span > </p> <p>用户信息: name {{ defaultUser.name }}, age: {{ defaultUser.age }}</p> data() { return { defaultUser: { name: "张三", }, }; }, methods: { handleUser() { this.defaultUser.age = "23"; console.log("数据已经发生更改,但是视图没有发生更新", this.defaultUser); }, },
当我们点击按钮的时候,视图中的ui并没有发生更改,根据官方的文档,我们可以使用 $set 针对新增的状态进行修改
<p> <span style="cursor: pointer; color: red" @click="handleSet" >点击$set修改属性值</span > </p> <p>通过$set修改的值:{{ setData.name }}</p> data() { return { setData: {}, }; }, methods: { handleSet() { this.$set(this.setData, "name", "张三"); }, },
除了后续新增的状态无法进行修改之外,使用Object.defineProperty劫持的数据,对数组本身可以操作的到,但是会存在一定的性能问题,具体不做详细解释,可以参考以下博文,感谢该博主的博文,vue的框架的作者尤大也做了解释:
记一次思否问答的问题思考:Vue为什么不能检测数组变动 https://segmentfault.com/a/1190000015783546
根据以上例子结合vue响应式原理,我们可以知道在vue2.x版本中Object.defineProperty存在以下问题:
1.监听数组变化下存在性能问题
2.Object.defineProperty只能劫持对象的属性,并且针对新增的data状态,无法劫持到,只能通过vue的扩展方法$set进行处理
阮一峰-ECMAScript 6 入门
查看最简单的例子和使用方法
下文截取自:阮一峰-ECMAScript 6 入门----> Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”
var obj = new Proxy({}, { get: function (target, propKey, receiver) { console.log(`getting ${propKey}!`); return Reflect.get(target, propKey, receiver); }, set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}!`); return Reflect.set(target, propKey, value, receiver); } });
更多Object.defineProperty和Proxy的对比,以及vue3如果使用proxy实现对象的监听,感兴趣的同学可以去搜索相关博文,后续有时间再整理。
文章个人博客地址:vue2.x版本中Object.defineProperty对象属性监听和关联
创作不易,转载请注明出处和作者。