defineProperty属性劫持如何实现,缺陷是什么
Admin 2022-07-18 群英技术资讯 304 次浏览
defineProperty是vue实现数据劫持的核心,本文一点点的说明defineProperty怎么实现属性劫持的。
其实我们一般的操作对象属性的方式,增加或者修改属性,均可以使用Object.defineProperty。
let obj = {}; // 寻常操作:增加/修改 新属性 obj.a = 1; // 等同于: Object.defineProperty(o, "a", { value: 1, writable: true, configurable: true, enumerable: true });
当然寻常的例子,我们是不会这么玩的,太��嗦了。
但defineProperty可以更精确地添加或修改对象的属性。
先说个专有名词:描述符。
其实就是defineProperty的第三个参数,是个对象。这个对象的有以下属性:
注意!!!
默念三遍,背诵。
写个get 和 set 的例子辅助理解。
这个例子必须掌握,弄懂之后基本就掌握了数据劫持的精髓了
let obj = {}; let value = 1; Object.defineProperty(obj, "b", { get() { console.log("读取b属性", value); return value; }, set(newValue) { console.log("设置b属性", newValue); value = newValue; } }); // 触发get函数,get的返回值就是属性值 // 1 console.log(obj.b); // 触发set函数,value的值变成了2,注意!!!,此时内存里,属性值并没有改变 obj.b = 2; // 但是,想要读取属性值的时候,就必然会触发get函数,属性值也自然就改变了,这个思想真的很赞 console.log(obj.b);
这里有个坑:get里是不能有读取的操作,不然一直死循环,所以使用到get set的地方,总需要借助一个变量
所以,这里,变量value的值就是属性的值,如果想要修改属性,修改 value 的值即可。
这个例子弄懂了,get,set 的精髓,我觉得也就差不多了。
有了刚刚例子的基础,试着写写劫持对象的任意一个属性。
function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("读取属性", value); return value; }, set(newValue) { console.log("设置属性", newValue); value = newValue; } }); } let obj = { a: 1 }; observeKey(obj, "a"); // 读取a,触发get函数 console.log(obj.a); // 设置a,触发set函数 obj.a = 1;
再试试劫持对象的所有属性
其实就是遍历:
function observeObj(obj) { for (let key in obj) { // 直接使用 obj.hasOwnProperty会提示不规范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); } } return obj; } function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("读取属性", value); return value; }, set(newValue) { console.log("设置属性", newValue); value = newValue; } }); } let obj = { a: 1, b: 2 }; observeObj(obj); console.log(obj); // 读取a,触发get函数 console.log(obj.a); // 设置a,触发set函数 obj.a = 1;
上面的有个缺陷,就是当属性值也是对象的时候,不能劫持属性值,如{a:1,c:{b:1}}
简单,递归,补上就行。
function observeObj(obj) { // 加上参数限制,必须是对象才有劫持,也是递归的终止条件 if (typeof obj !== "object" || obj == null) { return; } for (let key in obj) { // 直接使用 obj.hasOwnProperty会提示不规范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); // 这里劫持该属性的属性值,如果不是对象直接返回,不影响 observeObj(obj[key]); } } return obj; } function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("读取属性", value); return value; }, set(newValue) { console.log("设置属性", newValue); value = newValue; } }); } let obj = { a: 1, b: 2, c: { name: "c" } }; observeObj(obj); console.log(obj); // 读取a,触发get函数 console.log(obj.a); // 设置a,触发set函数 obj.a = 1; // 触发set函数 obj.c.name = "d";
注意,observeObj这个函数,不能劫持对象的新增属性,只能劫持对象已有的属性。
当然数组的修改可以通过别的方式监测到的,其是通过劫持改变数组方法实现的。
以上缺陷,也是vue里面为啥有$set/$delete以及对数组只能使用特定方法才能检测到。
let obj = { a: 1, b: [1, 2] }; observeObj(obj); // 新增属性 obj.c = 3; // 不会触发get函数 console.log(obj.c); // 不会触发set函数 obj.b.push(3);
其实就是访问options.data.name 可以简写成 options.name,专业话术,将data上的属性挂载到options上
相当于,用defineProperty,在options上增加新属性:
// 先挂载单个属性 // options.data相当于source options相当于target function proxyKey(target, source, key) { Object.defineProperty(target, key, { // 这里的source[key]相当于变量value,所以说最简单的那个例子是核心 get() { return source[key]; }, set(newValue) { if (newValue === source[key]) { return; } source[key] = newValue; } }); } // 遍历属性,挂载下 function proxyObj(target, source) { for (let key in source) { // 直接使用 obj.hasOwnProperty会提示不规范 if (Object.prototype.hasOwnProperty.call(source, key)) { proxyKey(target, source, key); } } } let options = { data: { name: 1 } }; proxyObj(options, options.data); // 1 console.log(options.name);
话说,vue的属性劫持和挂载属性,核心原理差不多就是上面这些。
比如 obj 有个属性,此属性值经常变化,想要记录其所有变化的值,以此可以形成日志。
let obj = { a: 1 }; let log = [obj.a]; let value = obj.a; Object.defineProperty(obj, "a", { get() { return value; }, set(newValue) { if (newValue === value) { return; } value = newValue; log.push(newValue); } }); obj.a = 2; obj.a = 3; obj.a = 4; // [1,2,3,4] console.log(log);
通用的可以抽离出一个类,专门记录某个值的变化
class Archiver { constructor() { let value = null; this.archive = []; Object.defineProperty(this, "a", { get() { return value; }, set(newValue) { if (newValue === value) { return; } value = newValue; this.archive.push(newValue); } }); } } let archiver = new Archiver(); archiver.a = 1; archiver.a = 2; // [1,2] console.log(archiver.archive);
引用
MDN的defineProperty
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了react-native 实现购物车滑动删除效果的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
文本主要给大家介绍js中闭包的使用场景。首先闭包就是能够读取其他函数内部变量的函数。因此在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成 “定义在一个函数内部的函”。js中闭包的优点有保护函数内的变量安全、方便调用上下文的局部变量、加强封装性等等。下面我们就来具体看看js闭包的使用场景有哪些。
HTML5中的大部分动画都是通过Canvas实现,因为Canvas就像一块画布,我们可以通过调用脚本在Canvas上绘制任意形状,甚至是制作动画,这篇文章主要给大家介绍了关于用几十行js实现很炫的canvas交互特效的相关资料,需要的朋友可以参考下
在js中,存在着N多的关于高度和宽度的属性,比如:clientHeight、offsetHeight、scrollHeight、availHeight、scrollLeft、scrollTop、style.height、innerHeight、o
这篇文章主要给大家介绍了关于Angular管道PIPE的相关资料,管道的作用就是传输,并且不同的管道具有不同的作用,需要的朋友可以参考下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008