最近两天过了一遍JS高程的前7章,第一次了解ECMAScript中基于原型(prototype-based)的面向对象的实现。思想看起来是那么的顺理成章并不难理解,但是由于属于新事物,所以打算对ECMAScript的OOP进行一下简单的小结,方便以后回来温习。

属性类型

ECMAScript对象中有两种属性:

  • 数据属性
  • 访问器属性

属性特征值(characteristic)

ECMAScript对象创建时都带有一些特征值,JS通过这些特征值来定义对象属性的行为。

先创建一个对象为后面提供例子:

1
2
3
4
5
6
7
8
var person = {
name: 'shao',
age: 24,
job: 'student',
sayName: function(){
console.log(this.name);
}
}

数据属性和访问器属性共有的属性特性

[[Configurable]]

表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为访问器属性。
直接在对象上定义的属性,默认为true

也就是说如果对象的某个属性的[[configurable]]特性值改为false,则就不能再修改这个属性的特性了。

1
2
3
4
5
6
> Object.getOwnPropertyDescriptor(person, 'name')
{ value: 'shao',
writable: true,
enumerable: true,
configurable: true }
>

可见,默认情况下,configurable的值为true。这时我们修改name的值或者删除name属性都是可以的。

1
2
3
4
5
6
7
> person.name = 'zhao'
'zhao'
> delete person.name
true
> person
{ age: 24, job: 'student', sayName: [Function] }
>

但是,如果我将configuratble修改为false,这时再重复上述操作就被禁止了。

修改了name属性的configurable属性为false.

1
2
> Object.defineProperty(person, 'name', {configurable: false})
{ age: 24, job: 'student', sayName: [Function], name: 'shao' }
1
2
3
4
5
6
7
8
> person.name = 'zhao'
'zhao'
> person.name
'zhao'
> delete person.name
false
> person.name
'zhao'

可见,我们还是可以对该属性的值进行修改的,但是如果我们想删除这个属性的时候便会失败。

看看如果我们再想将[[configurable]]属性特性再修改回true会怎样:

1
2
3
4
5
6
7
8
9
10
11
12
13
> Object.defineProperty(person, 'name', {configurable: true})
TypeError: Cannot redefine property: name
at Function.defineProperty (native)
at repl:1:8
at REPLServer.defaultEval (repl.js:252:27)
at bound (domain.js:287:14)
at REPLServer.runBound [as eval] (domain.js:300:12)
at REPLServer.<anonymous> (repl.js:417:12)
at emitOne (events.js:82:20)
at REPLServer.emit (events.js:169:7)
at REPLServer.Interface._onLine (readline.js:210:10)
at REPLServer.Interface._line (readline.js:549:8)
> person

[[Enumerable]]

表示能否通过for-in循环返回该属性,例如:

1
2
3
4
5
6
7
8
> for (i in person) {
... console.log(i);
... }
name
age
job
sayName
>

可见这个对象的所有属性默认的[[Enumerable]]属性都为true

但如果我将name[[Enumerable]]改为false,输出中便不会出现name属性了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> Object.defineProperty(person, 'name', {enumerable: false})
{ age: 24, job: 'student', sayName: [Function] }
> Object.getOwnPropertyDescriptor(person, 'name')
{ value: 'shao',
writable: true,
enumerable: false,
configurable: true }
> for (i in person) {
... console.log(i);
... }
age
job
sayName
>

数据属性特性

[[Writable]]

该属性表示能否修改属性的值,默认值为true

1
2
3
4
5
6
> Object.defineProperty(person, 'name', {writable: false})
{ name: 'shao', age: 24, job: 'student' }
> person.name = 'zhao'
'zhao'
> person.name
'shao'

可见我们无法修改name属性的值了。

[[Value]]

这个包含这个属性的数据值。读取属性的时候再这个位置读取;写入数据的时候把新值保存再这个位置。这个位置的值默认值为undefined

访问器属性

[[Get]]

这个属性特性就是在读取属性的时候调用的函数,相当与Python描述符中的__get__()方法,也就是属性的getter方法。

[[Set]]

这个属性特性就是在写入属性的时候调用的函数,相当于Python描述符中的__set__()方法。

具体的来个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> var person = {
... _name: 'shao',
... age: 24
... }
> Object.defineProperty(person, 'name', {
... get: function() {
..... return this._name;
..... },
... set: function(name) {
..... this._name = name;
..... }
... })
{ _name: 'shao', age: 24 }
> person.name
'shao'
> person.name = 'zhao'
'zhao'
> person.name
'zhao'

关于描述符

虽然都叫描述符(descriptor),但是ECMAScript中和Python的完全是两个不同的概念。

  • 在JS中,描述符就是个对象(object),这个对象描述了对象的属性,也就是一个包含configurable, writable, enumerable, value, get, set的对象而已。
  • 而在Python中描述符是一个实现描述符协议的对象,类似js中的访问器属性。

Comments