What is two-way data binding?
Data changes update views, and views change update data
When the content of the input box changes, the data in data changes synchronously view => model
When the data in data changes, the content of the text node changes synchronously model => view
Design idea: Observer mode
Vue’s bidirectional data binding is designed with the observer model in mind. Dep objects: Dependency Dependency – short for Dependency Dependency, including three main attributes ID, subs, target and four main functions addSub, removeSub, Depend, notify. Use notify() to trigger the subscription list saved under subs, updating the data and DOM in turn.
An Observer object contains two main attributes: value, dep. The idea is to override the default values and assignments with getter/setter methods, encapsulate the object as a reactive object, update the dependency list with each invocation, and trigger the subscriber when the value is updated. Binds to the object’s __ob__ stereotype chain property.
new Vue({
el: '#app'.data: { count: 100},... });Copy the code
How to initialize the above code in vue source code
Init function: initMixin:
Vue.prototype._init = function (options) {... var vm =this; . initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm,'beforeCreate');
// initState is the initialization Vue parameter we will follow up with
initState(vm);
initInjections(vm);
callHook(vm, 'created'); . };Copy the code
Initialization parameter initState:
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
// Our count is initialized here
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) {
initComputed(vm, opts.computed);
}
if(opts.watch) { initWatch(vm, opts.watch); }}Copy the code
InitData:
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
if(! isPlainObject(data)) { data = {}; }...// observe data
observe(data, true /* asRootData */);
Copy the code
Set the data parameter to reactive:
/*** Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
function observe(value, asRootData) {
if(! isObject(value)) {return }
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer)
{ ob = value.__ob__; }
else if
/* In case value is not a pure object but a Regexp or function or vm instance or is not extensible */( observerState.shouldConvert && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
Copy the code
The Observer:
/*** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. */
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
The def function is a simple wrapper around defineProperty
def(value, '__ob__'.this);
if (Array.isArray(value)) {
// In ES5 and lower versions of JS, arrays cannot be perfectly inherited, so check and select the appropriate function // protoAugment function uses the prototype augment chain inheritance, while copyAugment function uses the prototype augment chain definition (i.e. each array defineProperty).
var augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value); }};Copy the code
ObserverArray:
/*** 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]); }};Copy the code
Dep class:
/** * A dep is an observable that can have multiple * directives subscribing to it. */
var Dep = function Dep () {
this.id = uid$1+ +;this.subs = [];
};
Copy the code
Walk function:
/** * 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?1(obj, keys[i], obj[keys[i]]); }};Copy the code
DefineReactive:
/** * Define a reactive property on an Object. */
function defineReactive? 1(obj, key, val, customSetter) {
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;
var setter = property && property.set;
var childOb = 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;
// Dirty check, excluding NaN! == NaN effect
if(newVal === value || (newVal ! == newVal && value ! == value)) {return }
if (setter) {
setter.call(obj, newVal);
} else{ val = newVal; } childOb = observe(newVal); dep.notify(); }}); }Copy the code
Dep. Target&depend () :
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
Dep.prototype.depend = function depend() {
if (Dep.target) {
Dep.target.addDep(this); }}; Dep.prototype.notify =function notify() {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};Copy the code
AddDep () :
/** * Add a dependency to this directive. */
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)) {
// Add a subscriber using the push() method
dep.addSub(this); }}};Copy the code
DependArray () :
/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */
function dependArray(value) {
for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
e = value[i];
e && e.__ob__ && e.__ob__.dep.depend();
if (Array.isArray(e)) { dependArray(e); }}}Copy the code
Array update detection:
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype * /
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'].forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator() {
var arguments$1 = arguments;
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
var i = arguments.length;
var args = new Array(i);
while (i--) {
args[i] = arguments$1[i];
}
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
inserted = args;
break
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
Copy the code
Conclusion:
From the above code, we can see how Vue designs two-way data binding step by step. The two main points are:
-
Reading and assigning values using getter/setter proxies allows us to control the flow of data.
-
The observer pattern is used to realize the dependency between instruction and data and trigger update.