JavaScript defineProperty如何实现属性劫持

 更新时间:2021年7月18日 15:00  点击:2061

前言

defineProperty是vue实现数据劫持的核心,本文一点点的说明defineProperty怎么实现属性劫持的。

其实我们一般的操作对象属性的方式,增加或者修改属性,均可以使用Object.defineProperty。

let obj = {};
// 寻常操作:增加/修改 新属性
obj.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});

当然寻常的例子,我们是不会这么玩的,太啰嗦了。

但defineProperty可以更精确地添加或修改对象的属性。

描述符

先说个专有名词:描述符。

其实就是defineProperty的第三个参数,是个对象。这个对象的有以下属性:

  • configurable 属性:能不能修改描述符,就是能不能再次修改描述符的其他属性
  • enumerable 属性:能不能枚举该属性,就是 a 属性能不能被 for 到
  • writable 属性:能不能修改属性值,就是能不能这样修改obj.a = 1
  • value 属性:该属性的值
  • get 属性:是个函数,当访问该属性的时候,函数自动调用,函数返回值就是该属性的值
  • set 属性:是个函数,当修改该属性的时候,函数自动调用,函数有且只有一个参数,赋值的新值

注意!!!

  • 描述符里的value属性 writable属性 与 get属性 set属性是互斥的关系,只能存在一个
  • 另外的属性默认值都是false,不想false的话,记得配置哈,不细说(主要我也不怎么用)。

细说get 和 set

  • 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属性劫持内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • 详解前端安全之JavaScript防http劫持与XSS

    作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
  • js修改input的type属性问题探讨

    js修改input的type属性有些限制。当input元素还未插入文档流之前,是可以修改它的值的,在ie和ff下都没问题。但如果input已经存在于页面,其type属性在ie下就成了只读属性了,不可以修改。...2013-10-19
  • Vue之计算属性详解

    这篇文章主要为大家介绍了Vue的计算属性,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助...2021-11-16
  • C# Dynamic关键字之:调用属性、方法、字段的实现方法

    本篇文章是对C#中调用属性、方法、字段的实现方法进行了详细的分析介绍,需要的朋友参考下...2020-06-25
  • 详解jquery方法属性

    这篇文章主要介绍了jquery的方法属性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-11-10
  • 关于vue属性使用和不使用冒号的区别说明

    这篇文章主要介绍了关于vue属性使用和不使用冒号的区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-22
  • C# 列表List的常用属性和方法介绍

    这篇文章主要介绍了C# 列表List的常用属性和方法介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • 在Vue中获取自定义属性方法:data-id的实例

    这篇文章主要介绍了在Vue中获取自定义属性方法:data-id的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-09
  • 轻松学习C#的属性

    轻松学习C#的属性,对C#的属性感兴趣的朋友可以参考本篇文章,帮助大家更灵活的运用C#的属性。...2020-06-25
  • C#类中属性与成员变量的使用小结

    本篇文章主要是对C#类中属性与成员变量的使用进行了总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助...2020-06-25
  • JavaScript中的全局属性与方法深入解析

    这篇文章主要给大家介绍了关于JavaScript中全局属性与方法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用JavaScript具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-14
  • angularjs循环对象属性实现动态列的思路详解

    这篇文章主要介绍了angularjs循环对象属性实现动态列的思路详解,本文给大家分享一个demo代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-11-02
  • JS如何判断对象是否包含某个属性

    这篇文章主要介绍了JS如何判断对象是否包含某个属性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-30
  • SpringBoot 返回Json实体类属性大小写的解决

    这篇文章主要介绍了SpringBoot 返回Json实体类属性大小写的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-14
  • C#中属性和成员变量的区别说明

    本篇文章主要是对C#中属性和成员变量的区别进行了介绍说明。需要的朋友可以过来参考下,希望对大家有所帮助...2020-06-25
  • JavaScript中关联原型链属性特性

    这篇文章主要介绍了JavaScript中关联原型链属性特性的相关资料,需要的朋友可以参考下...2016-02-18
  • 详解EFCore中的导航属性

    这篇文章主要介绍了详解EFCore中的导航属性的相关资料,帮助大家更好的理解和学习使用ASP.NET CORE,感兴趣的朋友可以了解下...2021-09-22
  • Vue.js第三天学习笔记(计算属性computed)

    这篇文章主要为大家详细介绍了Vue.js第三天的学习笔记,vue.js计算属性computed,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-12-02
  • php类中protected与const属性详解

    在php中protected是私有变量,若该成员被声明称protected(保护),则代表只能在该类和该类的子类中使用该字段,而const是一个常量它的值一旦赋值不能被改变,下面看两个例子...2016-11-25