理解对象

理解对象

面向对象最常见的方式就是类,定义一个类之后,由它创建的对象都拥有从类继承而来的方法与属性。然而 JavaScript 里面,至少在 ES6 之前是没有 class的概念的。所以它的对象与传统类的对象还是有区别的。

Js 的对象可以说是一组无序值的集合,可以包括基本类型值、引用类型值、函数

通常采用字面量的方式或者 new Object() 的方式来创建;创建时除定义的值外,还具有一些特殊值,用来定义对象的各种行为。

属性类型

JS 规定了许多属性值用于给引擎使用,但是不能直接访问他们,通常用 [[Value]] 的方式放置

数据属性

四个值,用来描述行为

  • [[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
  • [[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
  • [[Writable]]:能否直接修改某个属性的值,默认为 true
  • [[Value]]:读数据时从这读取,写入时放在这里,默认为 undefined

Object.defineProperty()

该方法可以设置上述的特殊值,接受三个参数,参数1 为要修改的对象,参数2 为修改的对象,参数3 可以指定多个特殊值的值

  • 如果是对已有属性操作,则改变相应的特殊值就行
  • 如果没有该属性,则认为是通过该方法添加新属性,此时应该显式的定义各项值,否则就会默认为 false

看实例,允许直接通过该方法定义属性并直接指定对应的特殊值,若没指定的特殊值则按false ,要想让默认值为 true,需要用字面量或 new Object() 来创建

  • 修改 Writable 属性,在严格模式下, writable 值为false时,修改属性值会报错
1
2
3
4
5
6
7
8
9
10
11
12
var great = {}
var x = {
name: 'Great'
}
Object.defineProperty(great,'name',{
writable: false,//设置 name 属性值不可修改
value: "Greatiga"
});
console.log(x.name,great.name);//Great Greatiga
x.name = "yes";
great.name = "no";
console.log(x.name,great.name);//yes Greatiga
  • 修改 Configurable 属性
1
2
3
4
5
6
7
8
var great = {}
Object.defineProperty(great,'name',{
configurable: false,//设置 name 属性值不可删除
value: "Greatiga"
});
console.log(great.name);//Greatiga
delete great.name;
console.log(great.name);//Greatiga

但是,Configurable 属性一旦被定义为 false,就不能再变为 true 了,同时 Enumberable 属性也不可修改

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
var great = {}
Object.defineProperty(great,'name',{
configurable: false,//设置 name 属性值不可删除,
enumerable: true,
writable: true,
value: "Greatiga"
});

Object.defineProperty(great,'name',{
writable: false
});
//正常不会报错

Object.defineProperty(great,'name',{
configurable: true
});
//报错
//Cannot redefine property: name
// at Function.defineProperty (<anonymous>)
// at <anonymous>:6:8

Object.defineProperty(great,'name',{
enumerable: false
})
//报错
//Uncaught TypeError: Cannot redefine property: name
// at Function.defineProperty (<anonymous>)
// at <anonymous>:11:8

访问器属性

访问器属性只能通过 Object.defineProperty() 定义,通过字面量定义不是

四个值

  • [[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
  • [[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
  • [[Get]]:读取数据时调用的函数,默认为 undefined
  • [[Set]]:写入数据时调用的函数,默认为 undefined

Object.defineProperty()

  • 但一个属性添加了 get 和 set 方法后,该属性就是一个访问器属性,读取时触发 get ,设置值时触发 set

  • set 指向了 setter方法,get 指向了 getter 方法

  • 约定属性名前面加上 _ 作为私有变量,即外部不可以直接访问,需要通过 get 与 set 来访问,(事实上也是可以直接访问的,因为都是普通变量,但是既然约定,那么我们在编写我们的对象时,就应该遵守约定,哪些可以给外部看到,哪些对于外部隐藏)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Great = {
_age: 20
}
Object.defineProperty(Great,"age",{
get: function() {
console.log('get');
return this._age;
},
set: function(s) {
console.log('set');
this._age += s;
}
});
Great._age = 25;//同样可以通过直接访问属性改变值,但是不要这样,要遵守规定
console.log(Great.age)//25
Great.age = 1;
console.log(Great.age)//26

getter 与 setter 不一定都要定义,只定义了 get 表示只能读,反之表示只能写

1
2
3
4
5
6
7
8
9
10
11
12
13
var Great = {
_age: 20
}
Object.defineProperty(Great,"age",{
get: function() {
console.log('get');
return this._age;
}
});
Great._age = 25;
console.log(Great.age)//25
Great.age = 21;
console.log(Great.age)//25 显然上面这一步没有赋值成功,因为没有定义 set

IE8 对 Object.defineProperty() 的实现并不全面,建议不要使用在这个版本

defineGetterdefineSetter

另一种定义访问器属性的方式

例子

1
2
3
4
5
6
7
var Great = {
_age: 20
}
Great.__defineGetter__("age", function() {
console.log('get');
return this._age;
});

同时定义多个属性

Object.defineProperties() 方法

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
38
39
40
41
42
43
44
45
46
47
48
var Great = {}
Object.defineProperties(Great,{
name: {
writable: true,
enumerable: true,
configurable: true,//允许修改另外两个属性,因为他们默认为 false
value: 'Greatiga'
},
_time: {
configurable: true,//允许修改另外两个属性,因为他们默认为 false
value: 2020
},
time: {
configurable: true,//允许修改 set 和 get,因为他们默认为 false
get: function() {
return this._time
},
set: function(time) {
this._time = time;
}
}
});
Great.name = 'Link';
Great.time = 1999;
console.log(Great.name,Great.time);//Link 2020
//很显然_time的值并未修改,因为默认都为 false,writable 为 false;所以不许修改其值

Object.defineProperties(Great,{
name: {
writable: false, //将此属性设为不可更改
enumerable: false //禁止遍历
},
_time: {
writable: true, //允许修改
enumerable: true //允许遍历
},
time: {
enumerable: true, //允许遍历
set: function(time) { //重写 set 方法
this._time = time + 10;
}
}
});

Great.name = "GG";
Great.time = 1999;
console.log(Great.name,Great.time);//Link 2009
//此时 _time 可以修改了,但是 name 被我们禁止修改了

警惕: 上面的例子中,如果一开始没有设置 configurable 为 true,那么后面的步骤除了修改 writable 以外,修改其他特殊属性以及重写 set 方法都会报错,因为这个 configurable 就是规定每个属性在第一次设置之后是否可以再次修改

Uncaught TypeError: Cannot redefine property: 属性名 -> 这是通常的报错信息,表示不能重新定义特殊属性

获取对象属性的特殊属性值

Object.getOwnPropertyDescriptor()

接受两个参数,参数1位对象,参数2为属性值。返回一个对象,里面包括了之前介绍的各种 property 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Great = {}
Object.defineProperties(Great,{
name: {
writable: true,
configurable: true,
value: 'Greatiga'
},
_time: {
configurable: true,
value: 2020
},
time: {
configurable: true,
get: function() {
return this._time
}
}
});
var t1 = Object.getOwnPropertyDescriptor(Great,'name');
var t2 = Object.getOwnPropertyDescriptor(Great,'time');
console.log(t1.writable,t1.enumerable,t1.set);//true false undefined
console.log(t2.writable,t2.configurable,typeof t2.get);//undefined true "function"

总结

首先来看看数据属性与访问器属性是否可以同时定义

1
2
3
4
5
6
7
8
9
10
var test = {}
Object.defineProperty(test,'name',{
writable: true,
configurable: true,
get: function() {
return this.name;
}
})
//报错
//Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute,

这样一看就明白了,数据属性是定义某个属性的读取写入功能的,而访问器属性则是用来间接读取写入对象中的属性

所以这很像 公有变量与私有变量,如果要在对象中定义对外开放的变量,此时可以用数据属性来规定它,如果你想定义一个不对外公开的变量,就用访问器属性规定它

文章作者: 努力向前
文章链接: https://greatiga.cn/2020/07/15/javaScript/understandObject/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 努力向前