vue reactivity

anchors

题外

  • watcher定义后会立即执行一次getter(不是cb) this.value = this.lazy ? undefined : this.get();
  • Observe里面有dep, defineReactivity里也有dep.
  • defineReactive为在模型上的每个属性, 创建dep. 谁get那个属性就把target watcher添加到dep.subs里去.
  • render watcher执行一次get后, 让不再引用的的属性的dep取消订阅自己.

定义响应式

定义响应式流程

  • 遍历属性对象, 通过getter定义代理到vm上
  • 为属性对象添加observer, data.__ob__ = new Observer(data)
  • 关系为data有dep, dep里有watcher, watcher里有dep.循环依赖?
  • defineReactive内部又new dep去notify和depend, 那observer里的dep有什么用?
    • childOb.dep.depend();
      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
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      initState(vm);
      function initState (vm) {
      vm._watchers = [];
      var opts = vm.$options;
      if (opts.props) { initProps(vm, opts.props); }
      if (opts.data) {
      initData(vm);
      } else {
      observe(vm._data = {}, true /* asRootData */);
      }
      if (opts.computed) { initComputed(vm, opts.computed); }
      if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
      }
      }
      function initData (vm) {
      var data = vm._data = vm.$options.data
      var keys = Object.keys(data);
      var i = keys.length;
      while (i--) {
      var key = keys[i];
      proxy(vm, "_data", key);
      }
      // observe data 定义响应式
      observe(data, true /* asRootData */);
      }
      function observe (value, asRootData) {
      var ob;
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__;
      } else if (
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
      ) {
      ob = new Observer(value);
      }
      if (asRootData && ob) {
      ob.vmCount++;
      }
      return ob
      }
      var Observer = function Observer (value) {
      this.value = value;
      this.dep = new Dep(); // dep.subs收集watcher
      this.vmCount = 0;
      def(value, '__ob__', this); // 不可枚举属性
      if (Array.isArray(value)) {
      var augment = hasProto
      ? protoAugment
      : copyAugment;
      augment(value, arrayMethods, arrayKeys);
      this.observeArray(value);
      } else {
      this.walk(value);
      }
      };
      /**
      * Walk through each property and convert them into
      * getter/setters. This method should only be called when
      * value type is Object.
      */
      Observer.prototype.walk = function walk (obj) {
      var keys = Object.keys(obj);
      for (var i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]);
      }
      };
      /**
      * Observe a list of Array items.
      */
      Observer.prototype.observeArray = function observeArray (items) {
      for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
      }
      };
      function defineReactive (
      obj,
      key,
      val,
      customSetter,
      shallow
      ) {
      var dep = new Dep();
      var property = Object.getOwnPropertyDescriptor(obj, key);
      if (property && property.configurable === false) {
      return
      }
      // cater for pre-defined getter/setters
      var getter = property && property.get;
      if (!getter && arguments.length === 2) {
      val = obj[key];
      }
      var setter = property && property.set;
      var childOb = !shallow && observe(val);
      Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
      dep.depend();
      if (childOb) {
      childOb.dep.depend();
      if (Array.isArray(value)) {
      dependArray(value);
      }
      }
      }
      return value
      },
      set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
      return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
      customSetter();
      }
      if (setter) {
      setter.call(obj, newVal);
      } else {
      val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
      }
      });
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 属性proxy
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}

user-watcher响应式流程

  • user定义的watch在initState时通过$watch创建并放入vm._watchers, renderWatcher在vm.$mount创建, watcher创建晚于defineReactivity这样, 在get属性的时候就能访问属性的getters收集依赖

    1
    2
    3
    4
    5
    6
    function initState (vm) {
    // props data computed reactivity
    if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch); // => createWatcher(vm, key, handler); => vm.$watch(expOrFn, handler, options) => var watcher = new Watcher(vm, expOrFn, cb, options);
    }
    }
  • user watcher在创建后访问getter, 此时Dep.target是user watcher, expOrFun

    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
    get: function reactiveGetter () {
    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
    dep.depend();
    if (childOb) {
    childOb.dep.depend();
    if (Array.isArray(value)) {
    dependArray(value);
    }
    }
    }
    return value
    }
    Dep.prototype.depend = function depend () {
    if (Dep.target) {
    Dep.target.addDep(this);
    }
    };
    Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
    dep.addSub(this);
    }
    }
    };
    // depend过后, user watcher会有如下数据, 的确是循环引用
    Dep.target.deps = [
    dep : {
    sub : `user watcher`
    },
    dep : { // "defineReactive person.name dep"
    sub : `user watcher`
    },
    dep : { // "defineReactive person dep"
    sub : `user watcher`
    },
    dep : { // "person.data observer dep"
    sub : `user watcher`
    }
    ]

依赖收集

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
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null;
var targetStack = [];
function pushTarget (_target) {
if (Dep.target) { targetStack.push(Dep.target); }
Dep.target = _target;
}
function popTarget () {
Dep.target = targetStack.pop();
}
  • Watcher的get 收集依赖后, 调用取消视图上不再依赖的dep.subs注册.
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
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
Watcher.prototype.cleanupDeps = function cleanupDeps () {
var this$1 = this;
var i = this.deps.length;
while (i--) {
var dep = this$1.deps[i];
if (!this$1.newDepIds.has(dep.id)) {
dep.removeSub(this$1);
}
}
var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
};

派发更新

  • 让defineReactive里的dep执行notify, notify遍历subs watcher, 并调用update
  • render watch的before钩子执行before update
  • callUpdatedHooks(updatedQueue);执行updatedhook
  • user cb调用条件(value !== this.value || isObject(value) || this.deep)
    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
    47
    48
    Dep.prototype.notify = function notify () {
    var subs = this.subs.slice();
    if (process.env.NODE_ENV !== 'production' && !config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
    }
    };
    // 加入队列
    Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
    this.dirty = true;
    } else if (this.sync) {
    this.run();
    } else {
    queueWatcher(this);
    }
    };
    function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
    queue.push(watcher);
    } else {
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    var i = queue.length - 1;
    while (i > index && queue[i].id > watcher.id) {
    i--;
    }
    queue.splice(i + 1, 0, watcher);
    }
    // queue the flush
    if (!waiting) {
    waiting = true;
    // 异步刷队列前可以不停加任务
    nextTick(flushSchedulerQueue);
    }
    }
    }
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
function flushSchedulerQueue () {
currentFlushTimestamp = getNow();
flushing = true;
var watcher, id;
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort(function (a, b) { return a.id - b.id; });
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before();
}
id = watcher.id;
has[id] = null;
watcher.run();
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1;
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? ("in watcher with expression \"" + (watcher.expression) + "\"")
: "in a component render function."
),
watcher.vm
);
break
}
}
}
// keep copies of post queues before resetting state
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
resetSchedulerState();
// call component updated and activated hooks
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush');
}
}
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};

vue-nextTick

  • 默认micro task用messageChannel?
  • $nextTick调用的先后,也就是cb执行顺序. 设置值会立即添加到队列.
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
var isUsingMicroTask = false;
var callbacks = [];
var pending = false;
function flushCallbacks () {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}
// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
var timerFunc;
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks);
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) { setTimeout(noop); }
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = function () {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
}
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}

手动设置响应式和数组响应式

  • {} []上都会创建observer, object会对每个key
  • setter里让watcher 添加到dep的subs里.
  • observer里dep的作用, 为了set触发渲染watcher的重新渲染。

    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
    function set (target, key, val) {
    var ob = (target).__ob__;
    defineReactive(ob.value, key, val);
    ob.dep.notify();
    return val
    }
    var childOb = !shallow && observe(val); // 子属性依赖创建observer
    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
    dep.depend();
    if (childOb) {
    childOb.dep.depend(); // 收集子属性依赖
    if (Array.isArray(value)) {
    dependArray(value);
    }
    }
    }
    return value
    },
  • 数组响应式方法

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
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
})