回顾definePropety方法

我就是我 2022-08-28 07:44 138阅读 0赞

definePropety

顾名思义,这个方法就是给对象添加属性的方法,属于es6的知识点。

默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。

这个方法传递三个参数,分别是 给 哪个对象 添加 哪个属性,且 这个属性需要做什么配置

配置项存在四个属性,分别是 value,enumerable,writable,configurable

value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {})
  7. console.log(person, 'person')

f193c2e2ab314a569cee7c6a8ff59361.png

给定一个值之后

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22
  8. })

7758b05d4d9e4e5cbd93ecc31fdbcc31.png

但是这个age属性,颜色较浅,这又是为什么呢?

  1. for (const i in person) {
  2. console.log(i, 'i')
  3. }

53b5ab66a67446d087cab11a453e822f.png

通过 遍历之后,我们发现,控制台上只有 原先的两个属性,新增加的 age 属性,则是没有被遍历到,这是因为 defineProperty 的 第三个参数配置项,有一个默认配置项 enumerable 会控制属性是否能被枚举

enumerable : 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false所以在for…in 遍历 或者 Object.keys() 遍历时,才会无法打印出来

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22,
  8. enumerable: true
  9. })
  10. console.log(person, 'person')
  11. for (const i in person) {
  12. console.log(i, person[i])
  13. }

93ae15d3145d441f895b48d582d06f7c.png

可以看到,我将 默认的 enumerable : false 改为了 true ,遍历的时候,控制台上就展示了所有的属性,且 person 对象属性颜色一致,这就是配置 为 是否可枚举的属性

writable:这个就是控制新增的属性是否可以修改,默认为false不能修改属性值

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22,
  8. enumerable: true,
  9. // writable:true
  10. })
  11. console.log(person, 'person') // {name: '张三', six: '男', age: 22}
  12. person.age = 18
  13. console.log(person, 'person') // {name: '张三', six: '男', age: 22}
  14. // 改变属性值无效

99d1e84cc6244542bf35800341f982ee.png

设置为 true 之后,就变成了可修改的属性

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22,
  8. enumerable: true,
  9. writable:true // 控制是否能改变属性值
  10. })
  11. console.log(person, 'person') // {name: '张三', six: '男', age: 22}
  12. person.age = 18
  13. console.log(person, 'person') // {name: '张三', six: '男', age: 18}
  14. // 改变了属性值

38648a01fcb145879fbeedbfe40b1110.png

configurable :控制属性是否能被删除,默认是 false 不能被删除的

通过正常方式定义的对象属性,是可以删除的,例如下面的 通过字面量创建的 person 对象属性

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. console.log(person, 'person')
  7. delete person.name
  8. console.log(person, 'person')

a4ff38eb513e4fa097ab77a8b64cbe9a.png

但是,通过 defineProperty 添加的属性,且 configurable: false 之后,该属性则不能被删除

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22,
  8. enumerable: true,
  9. writable: true,
  10. configurable: false //默认为false,不能删除该属性
  11. })
  12. console.log(person, 'person')
  13. delete person.age
  14. console.log(person, 'person')

8c3f7fdc02504fa2a0abb8df8b8f18cf.png

但是,将 configurable: true 之后,该属性则是可以删除的了

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 22,
  8. enumerable: true,
  9. writable: true,
  10. configurable: true
  11. })
  12. console.log(person, 'person')
  13. delete person.age
  14. console.log(person, 'person')

e321c0d3a3994928b5368300dd2249b3.png

c9d62ffeb52e4e48a5154150b9e24d7f.png

除了这四个用来配置属性的方法,还存在两个读取的方法,分别是 get() 和 set() 两个函数

get() : 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为 undefined

下面这段代码,我向 person 对象内部添加了 age 属性,且值为 18,该属性,可枚举、可修改、可删除,然后我接着写了 get() 方法,用来做读取这个操作,也就是访问这个 age 属性时,返回一个值,这个时候就出问题了。

  1. // 定义对象
  2. let person = {
  3. name: '张三',
  4. six: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. value: 18, //默认 undefined 属性值
  8. enumerable: true, // 默认 false ,禁止枚举
  9. writable: true, // 默认 false ,禁止修改
  10. configurable: true, // 默认 false , 禁止删除
  11. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  12. // 默认传入this对象,函数的返回值会被作为 该属性的值
  13. get() { return 'bValue'; },
  14. })
  15. console.log(person, 'person')

e5f8b2ecbd374dea8af8fab57b9db67d.png 这个错误解释一下就是: 使用Object.defineProperty() 定义对象属性时,如已设置 set 或 get, 就不能设置 writable 和 value 中的任何一个了,所有关于value的操作都由get和set代理了。

所以我们需要把 value 和 writable 属性删除,如下

  1. Object.defineProperty(person, 'age', {
  2. // value: 18, //默认 undefined 属性值
  3. enumerable: true, // 默认 false ,禁止枚举
  4. // writable: true, // 默认 false ,禁止修改
  5. configurable: true, // 默认 false , 禁止删除
  6. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  7. // 默认传入this对象,函数的返回值会被作为 该属性的值
  8. get() { return 'bValue'; },
  9. })

e9ad8fda5ce5455d9ab569eb82263d32.png

结果如下,三个属性都是存在的,此时 age 属性 的值没有展示出来,且多了一个 get age() 这个方法,我们点击 age 的属性值,发现 和我们在 get 函数内部返回的值 是一样的

a85fc428872442128aafb80a97bbb7a6.png

这就印证了,为什么不让写 value 这个属性,因为 get 函数 返回的值,默认成为了 value 的值

那么 writable 对应的 set 又是什么样的呢?

set():属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined

  1. Object.defineProperty(person, 'age', {
  2. // value: 18, //默认 undefined 属性值
  3. enumerable: true, // 默认 false ,禁止枚举
  4. // writable: true, // 默认 false ,禁止修改
  5. configurable: true, // 默认 false , 禁止删除
  6. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  7. // 默认传入this对象,函数的返回值会被作为 该属性的值
  8. get() { return 'bValue'; },
  9. // 当修改 age 的值时,set函数( settter )就会被调用,且会受到修改的具体值
  10. set(x) {
  11. console.log('age被修改了,且值是,', x)
  12. }
  13. })

set函数被调用时,会触发这个 console,我们来看看控制台上输出了什么

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA55m95qGD5ZGz56ig6bG854On_size_12_color_FFFFFF_t_70_g_se_x_16

在改变了 age 属性的值之后,我们看到控制台上打印出了set()函数内部的console,但是我们再次输出 person 对象的时候,发现 person 对象中 age 属性值并没有发生改变,这是因为,我们只是接收到了需要改变的值,但是并没有将新的值 赋值 给 age 属性,但是如果我们直接 赋值,则会出现下面的情况

1、直接赋值

  1. Object.defineProperty(person, 'age', {
  2. // value: 18, //默认 undefined 属性值
  3. enumerable: true, // 默认 false ,禁止枚举
  4. // writable: true, // 默认 false ,禁止修改
  5. configurable: true, // 默认 false , 禁止删除
  6. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  7. // 默认传入this对象,函数的返回值会被作为 该属性的值
  8. get() { return 'bValue'; },
  9. // 当修改 age 的值时,set函数( settter )就会被调用,且会收到修改的具体值
  10. set(x) {
  11. console.log('age被修改了,且值是,', x)
  12. age = x
  13. }
  14. })

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA55m95qGD5ZGz56ig6bG854On_size_15_color_FFFFFF_t_70_g_se_x_16

此时我们会发现,这改了和没改一样,根本没生效啊,这是因为 set() 函数内部的 age ,根本就不是 我们通过 definepropetry() 新增的age 属性

2、通过this.age 赋值

因为之前说过,set 和 get 会默认将 this 传递进去,所以,我们使用 this.age 来赋值。

  1. Object.defineProperty(person, 'age', {
  2. // value: 18, //默认 undefined 属性值
  3. enumerable: true, // 默认 false ,禁止枚举
  4. // writable: true, // 默认 false ,禁止修改
  5. configurable: true, // 默认 false , 禁止删除
  6. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  7. // 默认传入this对象,函数的返回值会被作为 该属性的值
  8. get() { return 'bValue'; },
  9. // 当修改 age 的值时,set函数( settter )就会被调用,且会收到修改的具体值
  10. set(x) {
  11. console.log(this, 'this')
  12. console.log('age被修改了,且值是,', x)
  13. }
  14. })

在调用 set() 方法之后,我们能看到,打印出的 this 内部,是存在 age 属性的,所以按道理来说,我们使用 this.age = x 来说是能成功修改 age 的属性值的

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA55m95qGD5ZGz56ig6bG854On_size_17_color_FFFFFF_t_70_g_se_x_16

但是实际上,在我进行赋值操作之后,出现了一个我没想到的问题

  1. Object.defineProperty(person, 'age', {
  2. // value: 18, //默认 undefined 属性值
  3. enumerable: true, // 默认 false ,禁止枚举
  4. // writable: true, // 默认 false ,禁止修改
  5. configurable: true, // 默认 false , 禁止删除
  6. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  7. // 默认传入this对象,函数的返回值会被作为 该属性的值
  8. get() { return 'bValue'; },
  9. // 当修改 age 的值时,set函数( settter )就会被调用,且会收到修改的具体值
  10. set(x) {
  11. console.log('age被修改了,且值是,', x)
  12. this.age = x
  13. }
  14. })

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA55m95qGD5ZGz56ig6bG854On_size_17_color_FFFFFF_t_70_g_se_x_16 1

set() 方法被调用了很多很多次,直到最后堆栈溢出才停止调用,这说明这样也是不可取的,那么,我想修改属性的值,应该怎么做呢?

其实很简单,我们在外面定义一个变量,用来存储 age 的属性值,然后通过 get() 函数将 number的值返回出去,这样的话,在外部访问 age 属性时,就会走 get() 函数,然后 将当前的 number 的值返回出去,那么在修改的时候,就会调用 set() 方法,然后动态的改变 number 的值

  1. // 定义 number
  2. let number = 18
  3. // 定义对象
  4. let person = {
  5. name: '张三',
  6. six: '男'
  7. }
  8. Object.defineProperty(person, 'age', {
  9. // value: 18, //默认 undefined 属性值
  10. enumerable: true, // 默认 false ,禁止枚举
  11. // writable: true, // 默认 false ,禁止修改
  12. configurable: true, // 默认 false , 禁止删除
  13. // get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,
  14. // 默认传入this对象,函数的返回值会被作为 该属性的值
  15. get() { return number },
  16. // 当修改 age 的值时,set函数( settter )就会被调用,且会收到修改的具体值
  17. set(x) {
  18. console.log('age被修改了,且值是,', x)
  19. number = x
  20. }
  21. })

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA55m95qGD5ZGz56ig6bG854On_size_17_color_FFFFFF_t_70_g_se_x_16 2

本章总结

1、defineProperty 方法接收三个参数,第一个参数是需要改变的对象,第二个参数是,需要添加的属性,第三个参数则是一个可配置的 json对象,用来配置 添加的属性

2、配置对象内部存在四个属性和两个方法

四个属性分别是

  • value: xxx, // 设置新增属性的值,默认 undefined 属性值,可以是任何类型的值
  • enumerable: true, // 设置新增属性是否能被枚举,默认 false ,禁止枚举
  • writable: true, // 设置新增属性是否能被修改,默认 false ,禁止修改
  • configurable: true, // 设置新增属性是否能被删除,默认 false , 禁止删除

两个方法分别是

  • get(){} : get函数( 通常叫做 getter ),当访问该属性时,会自动调用此函数,默认传入this对象,函数的返回值会被作为 该属性的值
  • set(){} : 当修改 age 的值时,set函数( settter )就会被调用,且会收到修改的具体值

ps : 一般会将新增的属性的属性值,通过一个变量定义在外部,而不是直接写死,这是因为,在set() 的时候,无法同步修改 该属性值。定义在外的属性值,其实和 需要改变的对象 是两个不想管的东西,但是通过 defineProperty 连接了起来,你要访问属性值,我就去给你现取,你想修改属性值,我也给你同步修改 外部变量的值,想访问新的值了,再去调用 新的 get() 取到新的值返回

发表评论

表情:
评论列表 (有 0 条评论,138人围观)

还没有评论,来说两句吧...

相关阅读