JavaScript,defineProperty如何实现属性劫持
admin
2023-05-03 12:52:10
目录

    前言

    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

    默念三遍,背诵。

    写个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这个函数,不能劫持对象的新增属性,只能劫持对象已有的属性。

    defineProperty的缺陷

    当然数组的修改可以通过别的方式监测到的,其是通过劫持改变数组方法实现的。

    以上缺陷,也是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);
    
    

    defineProperty还可以挂载属性

    其实就是访问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的属性劫持和挂载属性,核心原理差不多就是上面这些。

    defineProperty还能写日志

    比如 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

    总结

    到此这篇关于JavaScript defineProperty如何实现属性劫持的文章就介绍到这了,更多相关defineProperty属性劫持内容请搜索趣讯吧以前的文章或继续浏览下面的相关文章希望大家以后多多支持趣讯吧!

    相关内容

    热门资讯

    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    怎么样的桃花 桃花近看远看的样... 今年的“三八”妇女节,阳光明媚,踏青赏花正当时。遂与姐妹们共赴十里蓝山,与花海来一场春天的约会。十里...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
    脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
    世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
    猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    怎么样的桃花 桃花近看远看的样... 今年的“三八”妇女节,阳光明媚,踏青赏花正当时。遂与姐妹们共赴十里蓝山,与花海来一场春天的约会。十里...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
    世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
    猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
    demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...