Kothing
Author of Kothing, a Bootstrap Medium styled template available for WordPress, HTML, Ghost and Jekyll. You are currently previewing Jekyll template demo.

Object.defineProperty()用法详解

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

通常给对象添加属性的做法

1
2
3
4
5
6
7
8
9
10
11
12
13
let user = {};

// 现在给 `user` 对象增加一个属性 `name`,通常的做法:
user.name="妙公子";
console.log(user);
// {name: "妙公子"}

// 如果想要增加一个sayHi方法:
user.sayHi = () => {
  console.log("Hi !");
};
console.log(user);
// {name: "妙公子", sayHi: ƒn}

使用 Object.defineProperty() 的做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let user = {};

// 使用`Object.defineProperty()`给user添加name属性:
Object.defineProperty(user, 'name', {
  value: '妙公子'
});
console.log(user);
// {name: "妙公子"}

// 使用`Object.defineProperty()`给user添加sayHi方法:
Object.defineProperty(user, 'sayHi', {
  value: function() {
    console.log("Hi !");
  }
});
console.log(user);
// {name: "妙公子"}




语法

Object.defineProperty是ES5的属性

Object.defineProperty(object, property, descriptor)

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

同时定义多个属性时可使用Object.defineProperties()用法详解

参数

object: 要定义属性的对象
property: 要定义或修改的属性的名称或 Symbol
descriptor: 要定义或修改的属性描述符

descriptor是以对象的类型传入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
descriptor: {  
  value: 默认为 undefined,  
  // 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)
  
  writable: 默认为 false,  
  // 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变
  
  enumerable: 默认为 false,  
  // 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中
  
  configurable: 默认为 false,  
  // 当且仅当该属性的 configurable 键值为 true 时,该属性才可以被删除,以及除 value 和 writable 特性外的其他特性才能可以被修改
  
  get: 默认为undefined,  
  // 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值就是属性的值。
  
  set: 默认为undefined,  
  // 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值)。
}

返回值

返回值为当前对象

示例

参数 descriptor 各个属性的具体用法。

value属性

使用Object.defineProperty()给user添加name属性:

1
2
3
4
5
6
7
let user = {};
Object.defineProperty(user, 'name', {
  value: '妙公子'
});

console.log(user);
// {name: "妙公子"}

value可以是任何有效的 JavaScript 值,所以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let user = {};

Object.defineProperty(user, "name", {
 value: "妙公子" // 字符串
});

Object.defineProperty(user,"isSlow",{
 value: true // 布尔型
});

Object.defineProperty(user,"sayHi",{
 value: function () { console.log("Hi !") } // 函数
});

Object.defineProperty(user, "age", {
 value: 12 // 数字
});

Object.defineProperty(user, "birth", {
 value:{
  date:"2018-06-29",
  hour:"15:30"
 } // 日期
});

writable 属性

当 writable 属性设置为 false 时,该属性被称为“不可写的”。它不能被重新赋值。

1
2
3
4
5
6
let user = {};

Object.defineProperty(user, "name", {
 value: "妙公子"
});
console.log(user);

当前user对象已经有了name属性,值为”妙公子”,我们试着改变name的值:

1
2
3
user.name="NiceBoy"
console.log(user);
// {name: "妙公子"}

上面代码并没有设置 writable 这个值,默认是false,所以属性值不会改变,也不会引发错误。 我们再验证把writable设置为true时候能否修改成功

1
2
3
4
5
6
7
8
9
10
let user = {};
Object.defineProperty(user, "name", {
 value: "妙公子",
 writable: true
});
console.log(user);

user.name="NiceBoy"
console.log(user);
// {name: "NiceBoy"}

可以看出修改成功

enumerable 属性

enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

1
2
3
4
let user ={
 name: "妙公子",
 age: 25
};

如果获取user对象的全部属性,通常做法:

1
2
3
4
5
6
7
8
9
10
11
let keys=Object.keys(user)
console.log(keys);
// ['name', 'age']

// 或
let keys = [];
for(key in user){
 keys.push(key);
} 
console.log(keys);
// ['name', 'age'] 

当使用Object.defineProperty定义user属性时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let user ={
 name: "妙公子",
 age: 25
} ;

//定义一个性别 可以被枚举
Object.defineProperty(user, "gender", {
 value: "",
 enumerable: true
});
 
//定义一个出生日期 不可以被枚举
Object.defineProperty(user, "birth", {
 value: "2020-05-03",
 enumerable: false
});

获取(枚举)user属性

1
2
3
4
5
6
console.log(user);
// {name: "妙公子", age: 25, gender: "男", birth: "2020-05-03"}

let keys = Object.keys(user);
console.log(keys);
// ["name", "age", "gender"]

可以看出 enumerable=falsebirth 属性并没有被枚举出来。

configurable 属性

configurable 特性表示对象的属性是否可以被删除,以及除 valuewritable 特性外的其他特性(在第一次设置之后)是否可以被修改。

第一次定义属性时把 configurable 设置为 false

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
let user = {
 name: "妙公子",
 age: 25
};

// 定义一个性别属性 不可以被删除和重新定义特性
Object.defineProperty(user, "gender", {
 value: "",
 enumerable: true,
 configurable: false // 第一次定义为false
});
 
//试着删除 gender 属性
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 结果显示 gender 没有被删除
 
// 重新定义特(试着修改enumerable设为false)
Object.defineProperty(user, "gender", {
 value: "",
 enumerable: false,  // 修改为 false
 configurable: false
});
// 会报错,如下:
// Uncaught TypeError: Cannot redefine property: gender
//   at Function.defineProperty (<anonymous>)
//   at <anonymous>

// 说明 configurable 为 false 时, enumerable 属性不能被重新定义

当configurable为false时,除value和writable以外的其他参数属性(enumerable/configurable/get/set)都不能被修改和删除。

第一次定义属性时把 configurable 设置为 true

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
34
35
36
37
let user ={
 name: "妙公子",
 age: 25
};

//定义一个性别 可以被删除和重新定义特性
Object.defineProperty(user, "gender", {
 value: "",
 enumerable: true,
 configurable: true // 第一次定义时候就设置为true
});
 
// 删除前
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
 
// 删除
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25}
 
// 重新定义特性
Object.defineProperty(user, "gender", {
 value: "",
 enumerable: true,
 configurable: false // 修改为 false
})
 
//删除前
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}

// 删除
delete user.gender;
console.log(user);
// {name: "妙公子", age: 25, gender: "男"}
// 结果显示 删除gender失败

Set 和 Get

get

当访问对象的该属性时(例如:console.log(user.name)),就会调用这个方法,并返回结果。默认为 undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
let user ={
	name: "妙公子"
};
let val = 18;
Object.defineProperty(user, "age", {
  // 当获age属性值的时候触发的函数
  get: () => {
    console.log('数据被获取');
    return val;
  },
});
console.log(user.age);
// 数据被获取 18

set

当为对象设置该属性值时,就会调用这个方法。默认为 undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let user ={
	 name: "妙公子"
};
let val = 18;
Object.defineProperty(user, "age", {
  // 当获age属性值的时候触发的函数
  get: function(){
    console.log('数据被获取');
    return val;
  },
  // 当设置值的时候触发的函数
  set: function(newVal){
    console.log('数据被修改/设置')
    count = newVal;
  }
});

//获取值
console.log(user.age); // 数据被获取 18

//设置值
user.age = 22; // 数据被修改

console.log(user.age); // 数据被获取 22

下面这个例子中,getter 总是会返回一个相同的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function TestDefineSetAndGet() {
  Object.defineProperty(this, 'myproperty', {
    get: function () {
        return 'I alway return this string,whatever you have assigned';
    },
    set: function () {
        this.myname = 'this is my name string';
    }
  });
}

var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';

// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);

实现一个自存档对象,当设置temperature 属性时,archive 数组会收到日志条目。

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
function Archiver() {
  let temperature = null;
  let archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { 
    return archive;
  };
}

let arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

注意

  1. 当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错滴)
  2. get或set不是必须成对出现,任写其一就可以

实际运用

1、对象常量 结合writable: false 和 configurable: false 就可以创建一个常量属性(不可修改,不可重新定义或者删除)

2、优化对象获取和修改属性方式
如果你想禁止一个对象添加新属性并且保留已有属性,就可以使用Object.preventExtensions(…)

3、增加属性获取和修改时的信息

扩展知识点

在一些框架,如vue、express、qjs等,经常会看到对Object.defineProperty的使用。那这些框架是如何使用呢?

  • MVVM中数据‘双向绑定’实现