对象变化侦测原理(一)

变化侦测原理

一、对象属性

对于 js 对象的属性,可以分为2种:数据属性和访问属性。

1.1 数据属性

数据属性就是普通的对象属性值,可以读入和写入值。

例如,下述的属性 data 就是数据属性:

1
2
3
4
var obj = {};
obj.data = 'data';

alert(obj.data) // "data"

对于数据属性,它有4个特性:

  • [[Configureable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问属性。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。
  • [[Writable]]:表示能否修改数据的值。
  • [[Value]]:这个属性的值。

这些属性特性一般是不能通过一般的方式获取到,需要通过使用方法 Object.getOwnPropertyDescriptor(obj, key)来获取特性值。以上面的代码为例:

1
Object.getOwnPropertyDescriptor(obj, 'data')
1
2
3
4
5
6
{
configurable: true,
enumerable: true,
writable: true,
value: "data"
}

通过一般设置的对象属性,默认都是数据属性,而除了 value 特性以外,其他特性的值都是默认为 true

如果需要修改特性的值,必须使用方法 Object.defineProperty(obj, key, option)来修改。

1
2
3
Object.defineProperty(obj, 'data', {
writable: false
})

再查看属性的特性,此时 writable 特性的值就已经发生变化了。

1
Object.getOwnPropertyDescriptor(obj, 'data')
1
2
3
4
5
6
{
configurable: true,
enumerable: true,
writable: false,
value: "data"
}

writable=false 表示属性不能修改,因此修改属性值是不生效的。

1
2
3
alert(obj.data)  // "data"
obj.data = "123" // 此时修改的话也不生效
alert(obj.data) // "data"

可以多次调用 Object.defineProperty 来修改属性特性。

接下来看一下特性 configurable 的作用:

1
2
3
Object.defineProperty(obj, 'data', {
configurable: false
})

configurable=false 表示属性无法通过 delete 删除,再重新定义属性。

1
2
3
alert(obj.data)  // "data"
delete obj.data // 无法删除
alert(obj.data) // "data"

而且,还有一个比较重要的点,那就是一旦把 configurable 设置为 false 之后,就不能再把它重新恢复为 true 了。

1
2
3
4
5
6
7
8
Object.defineProperty(obj, 'data', {
configurable: false
})

// 设置失败,抛出错误
Object.defineProperty(obj, 'data', {
configurable: true
})

1.2 访问属性

访问属性不包括数据,实际上是通过一对函数 settergetter 进行属性的读取访问和设置。

访问属性不能通过一般的对象属性进行定义,必须使用 Object.defineProperty(obj, key, option) 进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数据属性
var obj = {};
obj.data = 'data';

// 访问属性
var objProxy = {};
Object.defineProperty(objProxy, 'access', {
get: function () {
return obj.data;
},
set: function (val) {
obj.data = val;
}
})

alert(obj.data) // "data"
alert(objProxy.access) // "data"

属性 access 就是所谓的访问属性,它和一般的数据属性 data 一样,它也有 4 个特性:

  • [[Configureable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问属性。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。
  • [[Get]]:在读取属性时调用的函数。
  • [[Set]]:在设置属性时调用的函数。

其中特性 ConfigureableEnumerable 和数据属性中的是一样的。

获取属性特性的值也和数据属性的一样,通过方法 Object.getOwnPropertyDescriptor(obj, key) 来获取:

1
Object.getOwnPropertyDescriptor(objProxy, 'access')
1
2
3
4
5
6
{
configurable: true,
enumerable: true,
get: [function ()],
set: [function ()]
}

访问属性比较特别,可以通过在访问函数 settergetter
中做一些中间操作,获得其他的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var readTimes = 0
var writeTimes = 0

Object.defineProperty(objProxy, 'access', {
get: function () {
readTimes++;
return obj.data;
},
set: function (val) {
writeTimes++;
obj.data = val;
}
})

objProxy.access
objProxy.access
alert(readTimes) // 2
alert(writeTimes) // 0

objProxy.access = '111'
alert(readTimes) // 2
alert(writeTimes) // 1

这种利用访问函数的方式,其实就是 Vue 定义响应式属性所用的方法,通过利用访问属性的 settergetter 来收集和通知属性发生了变更,就可以实现响应式属性。

也许,访问属性叫访问方法更好,虽然操作上和数据属性类似,但是它本身并不算一个属性,只是通过方法 settergetter 进行了定义,然后拿的还是其他数据属性的值。

定义 settergetter 实际上就是在定义 objProxy.access=objProxy.access。调用 objProxy.access 就是在调用 getter 函数,调用 objProxy.access= 就是在调用 setter 函数。

对象变化侦测原理(一)

http://example.com/framework/vuejs/dection01/

作者

jiaduo

发布于

2021-08-28

更新于

2023-04-02

许可协议