彻底掌握 Object.difineProperty

3/11/2021 JavaScript

# 文章目录

# 一、Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

更加具体的去描述或操作一个对象内部属性的操作性。

# 1.1 什么意思?

  • defineProperty 给对象定义属性
  • define 规定、定义
  • Property 属性 对象的 key // Attribute

# 1.2 尝试一下

let obj = {};

let newObj = Object.defineProperty(obj, "a", {
  value: 1,
});

console.log(newObj === obj); // true
console.log("obj: ", obj);
console.log("newObj: ", newObj);
1
2
3
4
5
6
7
8
9

测试结果:
在这里插入图片描述

由此可知,确实给对象定义了一个新属性 => a=1,且返回的就是该对象( newObj 恒等于 obj )

# 1.3 调用

defineProperty 是 Object 的静态方法,不能在普通对象上直接调用(没在原型链上),

function Star(){...}

Star.sex = '男' // 静态成员
Star.sing = function (){ // 静态方法
  ...
}
1
2
3
4
5
6

# 1.4 语法

语法 Object.difineProperty(obj,prop,descriptor)

obj:要定义的对象
prop:要定义或修改的属性名称
descriptor:定义或修改的操作符(五个)

# 1.5 描述

  • 该方法允许精确地添加或修改对象的属性
  • 通过赋值操作添加的普通属性可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法)
  • 默认情况下,使用 Object.defineProperty() 添加的属性值不可修改(immutable)的。
  • 这个方法允许修改默认的额外选项(或配置

接下来我们进行测试:

let obj = {};
let newObj = Object.defineProperty(obj, "a", {
  value: 1,
});

obj.a = 2; // 没变化,不可修改
delete obj.a; // 没变化,不可删除

for (let k in obj) {
  console.log(k, obj[k]); // 没输出,不可枚举
}
1
2
3
4
5
6
7
8
9
10
11

测试结构:
在这里插入图片描述
控制台啥也没有,由此可知 我们通过 Object.defineProperty() 添加的属性有三个特点:

  • 不可修改
  • 不可删除
  • 不可枚举

# 二、descriptor 描述符

# 2.1 configurable 可配置的

configurable 可配置 able=>可能的,有能力的
configurable 特性表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改。

configurable :默认为 false。

let obj = {};
let newObj = Object.defineProperty(obj, "a", {
  value: 1,
  configurable: true,
});
delete obj.a;
console.log(obj); // {}
1
2
3
4
5
6
7

通过上述测试可知,当 configurable 为 true 时,我们就可以删除该属性了。

# 2.2 enumerable 可枚举的

enumerable 默认为 false

let obj = {};
let newObj = Object.defineProperty(obj, "a", {
  value: 1,
  enumerable: true, // 可枚举的 Default:false
});
for (let k in obj) {
  console.log(k);
}
1
2
3
4
5
6
7
8

在这里插入图片描述
通过上述测试可知,当 enumerable 为 true,可枚举该属性

# 2.2 补充 如何枚举 enumerable:false 的对象属性

需求:枚举对象 obj 的所有属性

let obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  enumerable: false,
});
Object.defineProperty(obj, "b", {
  value: 2,
  enumerable: false,
});
1
2
3
4
5
6
7
8
9

普通方法:

for (let k in obj) {
  console.log(k); // 空
}
1
2
3

进阶方法:

let keys = Object.getOwnPropertyNames(obj);
console.log(keys); // ["a", "b"]
1
2

Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组
注意:拿不到继承而来的属性,而 for…in 可以拿到继承的属性

更多示例:

var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort());
// // ["0", "1", "2", "length"]
1
2
3

# 2.3 writable 可写的

writable 默认为 false.

let obj = {};
let newObj = Object.defineProperty(obj, "a", {
  value: 1,
  writable: true, // 可写的 Default:false
});
obj.a = 2;
console.log(obj);
1
2
3
4
5
6
7

在这里插入图片描述
通过上述测试可知,当 writable 为 true 时,可修改该属性。

其他测试:
在这里插入图片描述

# 2.4 get 获取

  • 当访问该属性时,会调用此函数。
  • 该函数的返回值会被用作属性的值。
let obj = {};
Object.defineProperty(obj, "a", {
  // value: 1
  get() {
    console.log("get a:", 1);
    return 1;
  },
});

console.log(obj.a);
1
2
3
4
5
6
7
8
9
10

在这里插入图片描述
通过上述测试可知,当我们定义了get()方法后,每次访问该属性都会先调用该方法,且该方法的返回值就是属性的值

# 2.5 set 设置

let obj = {};
Object.defineProperty(obj, "a", {
  // value: 1

  set(newValue) {
    console.log("set a:", newValue);
  },
});
obj.a = 2;
1
2
3
4
5
6
7
8
9

在这里插入图片描述
通过上述测试可知,当我们每次设置或修改该属性时,都会先调用 set 方法,且该方法有一个参数(也就是被赋予的新值)

# 2.6 异常

如果一个描述符同时拥有 valuewritablegetset 键,则会产生一个异常。

value || writable <= 不能与之共处 => get || set

以下为 错误 示例:会抛出一个异常

//以下为错误示例
let obj = {}
let newObj = Object.defineProperty(obj, 'a', {
  value: 1,
  get() {...}
})
newObj = Object.defineProperty(obj, 'a', {
  value: 1,
  set() {...}
})
newObj = Object.defineProperty(obj, 'a', {
  writable: true, //这里true 和 false 都会抛出异常
  get() {...}
})
newObj = Object.defineProperty(obj, 'a', {
  writable: true,//这里true 和 false 都会抛出异常
  set() {...}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

以上情况都会出现下面的异常。
在这里插入图片描述

# 2.7 描述符默认值汇总

拥有布尔值的键 configurableenumerablewritable默认值都是 false
属性值和函数的键 value、get 和 set 字段的默认值为 undefined

# 三、案例

# 3.1 自定义 set 和 get

# 3.1.1 实现一个自存档类

function Archiver() {
  let archive = [];
  let item = null;

  Object.defineProperty(this, "item", {
    set(newValue) {
      item = newValue;
      archive.push({ item: newValue });
    },
    get() {
      console.log("get!");
      return item;
    },
  });
  this.getArchiver = () => archive;
}

let person = new Archiver();

person.item = "pengsir";
person.item = "彭先生";
console.log(person.item);
console.log(person.getArchiver());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

测试结果:
在这里插入图片描述

# 3.1.2 get 总是返回相同的属性值

let pattern = {
  get() {
    return "我默认是18岁!";
  },
  set() {
    this.age = 18;
  },
};
function TestDefineSetAndGet() {
  Object.defineProperty(this, "uname", pattern);
}
let obj = new TestDefineSetAndGet();
obj.uname = "彭先生";
console.log(obj);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

测试结果:
在这里插入图片描述

# 3.2 继承属性时的一些注意事项

如果访问者的属性是被继承的,它的 get 和 set 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。

function Star() {}
let attribute = null;
Object.defineProperty(Star.prototype, "x", {
  set(newValue) {
    attribute = newValue;
    console.log("set!:", newValue);
  },
  get() {
    console.log("get!");
    return attribute;
  },
});

let ldh = new Star();
let zxy = new Star();

ldh.x = 1;
console.log(ldh.x);
console.log(zxy.x);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

测试结果:
在这里插入图片描述
如何让继承属性的同时,值不是共享的呢?

这可以通过将值存储在另一个属性中解决。在 get 和 set 方法中,this 指向某个被访问和修改属性的对象。

function Star() {}
let attribute = null;

// 属性值共享:
// Object.defineProperty(Star.prototype, 'x', {
//   set(newValue) {
//     attribute = newValue
//     console.log('set!:', newValue);
//   },
//   get() {
//     console.log('get!');
//     return attribute
//   }
// })

// 属性值不共享:
Object.defineProperty(Star.prototype, "x", {
  set(newValue) {
    this.attribute = newValue;
    console.log("set!:", newValue);
  },
  get() {
    console.log("get!");
    return this.attribute;
  },
});

let ldh = new Star();
let zxy = new Star();

ldh.x = 1;
console.log(ldh.x);
console.log(zxy.x);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

测试结果:
在这里插入图片描述
继承的值是不可写的(writable) ,那么子对象的值是否可写呢?

//'use strict';

function Star() {}
Star.prototype.x = 1;
Object.defineProperty(Star.prototype, "y", {
  writable: false,
  value: 2,
});

let instance = new Star();

instance.x = 111;
console.log(instance.x); // 111
console.log(Star.prototype.x); // 1

instance.y = 222;

console.log(instance.y); // 2  => 在严格模式下会报错
// Uncaught TypeError: Cannot assign to read only property 'y' of object '#<Star>

console.log(Star.prototype.y); // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
最后更新于: 2021年9月15日星期三晚上10点10分
Faster Than Light
Andreas Waldetoft / Mia Stegmar