diff --git a/src/watcher.js b/src/watcher.js index 576bf472b2e..c72919cde61 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -331,14 +331,25 @@ Watcher.prototype.teardown = function () { * @param {*} val */ -function traverse (val) { +function traverse (val, walkedObjs) { var i, keys + + walkedObjs = walkedObjs || {} if (isArray(val)) { i = val.length - while (i--) traverse(val[i]) + while (i--) traverse(val[i], walkedObjs) } else if (isObject(val)) { + if (val.__ob__) { + var depId = val.__ob__.dep.id + if (walkedObjs[depId]) { + return + } else { + walkedObjs[depId] = true + } + } + keys = Object.keys(val) i = keys.length - while (i--) traverse(val[keys[i]]) + while (i--) traverse(val[keys[i]], walkedObjs) } } diff --git a/test/unit/specs/watcher_spec.js b/test/unit/specs/watcher_spec.js index 5e9c1a601ed..2450417ce90 100644 --- a/test/unit/specs/watcher_spec.js +++ b/test/unit/specs/watcher_spec.js @@ -286,6 +286,23 @@ describe('Watcher', function () { }) }) + it('deep watch with circular references', function (done) { + new Watcher(vm, 'b', spy, { + deep: true + }) + Vue.set(vm.b, '_', vm.b) + nextTick(function () { + expect(spy).toHaveBeenCalledWith(vm.b, vm.b) + expect(spy.calls.count()).toBe(1) + vm.b._.c = 1 + nextTick(function () { + expect(spy).toHaveBeenCalledWith(vm.b, vm.b) + expect(spy.calls.count()).toBe(2) + done() + }) + }) + }) + it('fire change for prop addition/deletion in non-deep mode', function (done) { new Watcher(vm, 'b', spy) Vue.set(vm.b, 'e', 123)