Object.defineProperty
Object.defineProperty
会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
先看一个demo:1
2
3
4
5
6
7
8
9
10
11
12const object = {}
Object.defineProperty(object, 'property1', {
value: 20,
writable: false // 定义该属性不可改
})
console.log(object)
// { property1: 20 }
object.property1 = 30
// { property1: 20 }
语法1
Object.defineProperty(obj, prop, descriptor)
参数
- obj
要定义的对象 - prop
要定义或修改的属性名称(或Symbol) descriptor
要定义或修改的属性描述符(数据描述符、存取描述符)数据描述符包含:
- configurable(false)
为true
时,该属性的描述符才能够被改变,也能删除 - enumerable(false)
为true
时,该属性才会出现在对象的枚举属性中(被for...in
、Object.keys
访问) - value(undefined)
该属性对应的值 - writable(false)
为true
时,才可改
存取描述符包含:
- get(undefined)
属性的getter
函数,当访问该属性时,会调用此函数。 - set(undefined)
属性的setter
函数,当属性值被修改时,会调用此函数。
- configurable(false)
注意:
- 如果一个描述符不具有
value
、writable
、get
、set
中的任意一个键,它会被认为是一个数据描述符。 - 如果一个描述符同时拥有
value
或writable
和get
或set
键,它会抛出异常。
1 | var o = {}; |
自定义 Setters
和 Getters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function Archiver() {
var temp = null
var archive = []
Object.defineProperty(this, 'temp', {
get () {
console.log('get')
return temp
},
set (value) {
temp = value
archive.push({ val: temp })
}
})
this.getArchive = function () {
return archive
}
}
var arc = new Archiver()
arc.temp // 'get', null
arc.temp = 11
arc.temp = 13
arc.getArchive() // [{val: 11}, {val: 13}]
继承属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function MyClass() {}
var value;
Object.defineProperty(MyClass.prototype, 'x', {
get () {
return value
},
set (newValue) {
value = newValue
}
})
var a = new MyClass()
var b = new MyClass()
a.x = 1 // a.__proto__ 和 MyClass.prototype 相等
console.log(a.x, b.x) // 1 1
可以通过将值存储在另一个属性中解决。在 get
和 set
方法中,this
指向某个被访问和修改属性的对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function MyClass() {}
Object.defineProperty(MyClass.prototype, 'x', {
get () {
return this.stored_x
},
set (newValue) {
this.stored_x = newValue
}
})
var a = new MyClass()
var b = new MyClass()
a.x = 1
console.log(a.x, b.x) // 1 undefined
// this指向MyClass.prototype对象
Proxy
Proxy
对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
语法1
const p = new Proxy(target, handler)
参数
- target
要使用Proxy
包装的目标对象,可以是对象、数组、函数,甚至是另外一个代理 - handler
一个通常以函数为属性的对象
基础示例1
2
3
4
5
6
7
8
9
10const p = new Proxy({}, {
get (obj, prop) {
return prop in obj ? obj[prop] : 30 // 存在即返回,不存在就返回30
}
})
p.a = 1
p.b = undefined
console.log(p.a, p.b) // 1, undefined
console.log('c' in p, p.c) // false, 30
无操作转发代理1
2
3
4
5const target = {}
const p = new Proxy(target, {})
p.a = 30 // p代理会将所有的操作都转发到这个对象上
console.log(p.a, target.a) // 30, 30
vue2.x
在 vue2.x 中,双向绑定是通过监听对象的get和set操作来实现的。即是 Object.defineProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const obj = {
a: 1
}
let val = 1
let newVal = 2
Object.defineProperty(obj, 'a', {
get () {
console.log('数据被获取')
return val
},
set (newVal) {
console.log('数据被修改/设置')
val = newVal
}
})
每当我们获取 obj 的 a 属性或设置 a 属性时,都会被劫持。在 vue2.x 中正是采用这种监听方式使用发布-订阅模式,data 上的数据是发布方,而试图的修改是订阅方,每当数据发生修改时,就会发布信息,而订阅方接受到相应的信息,就会对视图进行修改。
下面简单模拟一下发布-订阅的流程: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
47const publisher = function () {
return {
currentList: [],
add (subscriber) {
this.currentList.push(subscriber)
console.log('添加订阅', this.currentList) // [{ receiveText(){} }, ..]
},
publish (key, content) {
const currentList = this.currentList
console.log('订阅集合', currentList)
// 遍历订阅者的数组
for (let i = 0; i < currentList.length; i++) {
currentList[i].receiveText(key, content)
}
}
}
}
const subscriber = function (name) {
return {
receiveText (key, content) {
console.log(`视图${key}已更新,相应的内容为${content}`)
}
}
}
// 将data作为发布方
const data = publisher()
// 将视图修改作为订阅方
const change = subscriber()
// 让视图修改订阅data的更新
data.add(change)
data._a = 1
Object.defineProperty(data, 'a', {
get () {
return data._a
},
set (newVal) {
data.publish('a', newVal)
data._a = newVal
}
})
// test
data.a // 1 返回data._a的数据
data.a = 10 //
vue3.0
虽然 Object.defineProperty
可以实现双向绑定的效果,但是在 Proxy
出现后,它就被替代掉了。Object.defineProperty
只能一个属性一个属性的监听,也就是说,对于 data 对象,我们需要进行深度遍历,去监听每一个属性的变化,而一旦我们对data一个比较深的对象直接修改它的值,又得对其进行重新的遍历,非常损耗性能。而 Proxy
可以监听一整个对象,且基于 Proxy
的监听,只有当一个数据被用到的时候,才会去监听它,所以它在监听上大大降低了性能的损耗。
1 | const p = new Proxy({}, { |
模拟发布-订阅1
var publisher