Skip to content

Commit 7fa6faa

Browse files
committed
Added handling and tests for nonconfigurable properties, get-only properties, and set-only properties
1 parent 9e11b62 commit 7fa6faa

File tree

2 files changed

+123
-10
lines changed

2 files changed

+123
-10
lines changed

src/observer/index.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,26 +173,36 @@ function copyAugment (target, src, keys) {
173173

174174
function defineReactive (obj, key, val) {
175175
var dep = new Dep()
176+
var hasGetter = true
177+
var hasSetter = true
176178

177179
var target = {
178180
val: val
179181
}
180182

181183
if (config.convertAllProperties) {
182184
var property = Object.getOwnPropertyDescriptor(obj, key)
183-
if (property && property.get && property.set) {
185+
if (property && property.configurable === false) {
186+
return
187+
}
188+
if (property && (property.get || property.set)) {
189+
hasGetter = property.get !== undefined
190+
hasSetter = property.set !== undefined
184191
Object.defineProperty(target, 'val', {
185-
get: _.bind(property.get, obj),
186-
set: _.bind(property.set, obj)
192+
get: property.get && _.bind(property.get, obj),
193+
set: property.set && _.bind(property.set, obj)
187194
})
188195
}
189196
}
190197

191198
var childOb = Observer.create(target.val)
192-
Object.defineProperty(obj, key, {
199+
var propertyDefinition = {
193200
enumerable: true,
194-
configurable: true,
195-
get: function metaGetter () {
201+
configurable: true
202+
}
203+
204+
if (hasGetter) {
205+
propertyDefinition.get = function metaGetter () {
196206
var val = target.val
197207
if (Dep.target) {
198208
dep.depend()
@@ -206,15 +216,20 @@ function defineReactive (obj, key, val) {
206216
}
207217
}
208218
}
209-
return target.val
210-
},
211-
set: function metaSetter (newVal) {
219+
return val
220+
}
221+
}
222+
223+
if (hasSetter) {
224+
propertyDefinition.set = function metaSetter (newVal) {
212225
if (newVal === target.val) return
213226
target.val = newVal
214227
childOb = Observer.create(newVal)
215228
dep.notify()
216229
}
217-
})
230+
}
231+
232+
Object.defineProperty(obj, key, propertyDefinition)
218233
}
219234

220235
// Attach to the util object so it can be used elsewhere.

test/unit/specs/observer/observer_spec.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ describe('Observer', function () {
4646
// on object
4747
var obj = {}
4848
var val = 0
49+
var getCount = 0
4950
Object.defineProperty(obj, 'a', {
5051
configurable: true,
5152
enumerable: true,
5253
get: function () {
54+
getCount++
5355
return val
5456
},
5557
set: function (v) {
@@ -62,6 +64,13 @@ describe('Observer', function () {
6264
expect(ob.value).toBe(obj)
6365
expect(obj.__ob__).toBe(ob)
6466

67+
getCount = 0
68+
// Each read of 'a' should result in only one get underlying get call
69+
obj.a
70+
expect(getCount).toBe(1)
71+
obj.a
72+
expect(getCount).toBe(2)
73+
6574
// should return existing ob on already observed objects
6675
var ob2 = Observer.create(obj)
6776
expect(ob2).toBe(ob)
@@ -73,6 +82,95 @@ describe('Observer', function () {
7382
config.convertAllProperties = previousConvertAllProperties
7483
})
7584

85+
it('create on property with only getter', function () {
86+
var previousConvertAllProperties = config.convertAllProperties
87+
config.convertAllProperties = true
88+
89+
// on object
90+
var obj = {}
91+
Object.defineProperty(obj, 'a', {
92+
configurable: true,
93+
enumerable: true,
94+
get: function () {
95+
return 123
96+
}
97+
})
98+
99+
var ob = Observer.create(obj)
100+
expect(ob instanceof Observer).toBe(true)
101+
expect(ob.value).toBe(obj)
102+
expect(obj.__ob__).toBe(ob)
103+
104+
// should be able to read
105+
expect(obj.a).toBe(123)
106+
107+
// should return existing ob on already observed objects
108+
var ob2 = Observer.create(obj)
109+
expect(ob2).toBe(ob)
110+
111+
// since there is no setter, you shouldn't be able to write to it
112+
expect(function () {
113+
obj.a = 101
114+
}).toThrow()
115+
expect(obj.a).toBe(123)
116+
117+
config.convertAllProperties = previousConvertAllProperties
118+
})
119+
120+
it('create on property with only setter', function () {
121+
var previousConvertAllProperties = config.convertAllProperties
122+
config.convertAllProperties = true
123+
124+
// on object
125+
var obj = {}
126+
var val = 10
127+
Object.defineProperty(obj, 'a', { // eslint-disable-line accessor-pairs
128+
configurable: true,
129+
enumerable: true,
130+
set: function (v) {
131+
val = v
132+
}
133+
})
134+
135+
var ob = Observer.create(obj)
136+
expect(ob instanceof Observer).toBe(true)
137+
expect(ob.value).toBe(obj)
138+
expect(obj.__ob__).toBe(ob)
139+
140+
// reads should return undefined
141+
expect(obj.a).toBe(undefined)
142+
143+
// should return existing ob on already observed objects
144+
var ob2 = Observer.create(obj)
145+
expect(ob2).toBe(ob)
146+
147+
// writes should call the set function
148+
obj.a = 100
149+
expect(val).toBe(100)
150+
151+
config.convertAllProperties = previousConvertAllProperties
152+
})
153+
154+
it('create on property which is marked not configurable', function () {
155+
var previousConvertAllProperties = config.convertAllProperties
156+
config.convertAllProperties = true
157+
158+
// on object
159+
var obj = {}
160+
Object.defineProperty(obj, 'a', {
161+
configurable: false,
162+
enumerable: true,
163+
val: 10
164+
})
165+
166+
var ob = Observer.create(obj)
167+
expect(ob instanceof Observer).toBe(true)
168+
expect(ob.value).toBe(obj)
169+
expect(obj.__ob__).toBe(ob)
170+
171+
config.convertAllProperties = previousConvertAllProperties
172+
})
173+
76174
it('create on array', function () {
77175
// on object
78176
var arr = [{}, {}]

0 commit comments

Comments
 (0)