1. Why use vue
At present, the big front-end has reached an unprecedented stage of prosperity, and a variety of framework libraries emerge in endlessly. I think we can definitely find two or three highly homogeneous frameworks for each framework we choose. So why do we choose vue instead of react and angular in the current front-end architecture of mvvm?
First of all, they are very typical front-end mvvm frameworks, which solve the relationship between view and model in business logic very well. After using these frameworks, we will no longer use jQuery as before. Page presentation and data are highly coupled, even without the need for selectors.
Compared with react and angular, vue was first developed by a domestic developer with good API documents and samples. Foreign technical documents still have high requirements for translators in the process of translation. Otherwise, it is difficult for most developers to have a deep understanding after simple reading.
Secondly, he has a good starting point. For developers who are not so skilled in node, npm, webpack and ES6, they can also watch the document demo start quickly and enter the project development. Moreover, the template writing method of vue component is easier to understand for developers who have used traditional template.
Although react's jsx allows HTML prototypes provided by designers to be pasted directly into the past, there is still a lot of work to be done, and this part of the transformation is like a need for developers to master a new template language, such as developers to manually replace class and for attributes with className and htmlFor, and also to change inline style from css grammar to JSON grammar. .
In the aspect of two-way data binding, angular uses dirty checking. When data changes, it checks the binding relationship between all data and views once. When DOM under ng-app root node becomes more and more complex, the efficiency of dirty checking will become lower and lower. This requires us to constantly consider how to solve the problem in the process of writing business logic. Resolve performance bottlenecks brought about by frameworks. The Object.defineProperty() method in ES5 is used by vue to realize the association between model and view layer. It can accurately map the changes of data to the corresponding view, and has no positive relationship with the complexity of DOM. (Of course, the disadvantage of vue here is that it can't be used in browsers that don't support ES5)
2. The data observer model of vue, starting with the source code
var data = { a: 1 }; var vm = new Vue({ data: data }) vm.$watch('a', function() { console.log('the data a value changed'); }); vm.a = 2;
In this example, each time the value of a attribute in data is changed, the data a value change will be output. Let's see what happens in the process.
In Vue 2.2.4, you will see the following code:
/** * 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; def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } };
The Observer class adds getter and setter methods for each attribute of each object, and implements a notification when the value of the object attribute changes.
def( value, '__ob__', this );
The def method registers objects through the Object.defineProperty() of ES5.
/** * Define a property. */ function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }
this.observeArray and this. wall methods recursively implement deep traversal of object s and bind setter and getter methods to each attribute.
/** * 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]]); } }; /** * 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]); } }; /** * 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 ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob } /** * 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; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); dep.notify(); } }); }
Notice the dep.notify method in the set. This is to give a notification every time the value of an object's attribute changes to see what's done in the notify:
/** * A dep is an observable that can have multiple * directives subscribing to it. */ var Dep = function Dep () { this.id = uid$1++; this.subs = []; }; Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
Notfy is one of Dep's methods. Dep mainly implements the management of a message list. Each message will be push ed in through the addSub method. When notify is triggered, all messages will be updated once (diff judgment will be made in update, unchanged state will not be processed logically).
Next, let's see how this notification is received. There's a Watcher class in the class library that handles the received messages. Watcher's constructor is as follows:
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ var Watcher = function Watcher ( vm, expOrFn, cb, options ) { this.vm = vm; vm._watchers.push(this); // options if (options) { this.deep = !!options.deep; this.user = !!options.user; this.lazy = !!options.lazy; this.sync = !!options.sync; } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; this.id = ++uid$2; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = function () {}; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? undefined : this.get(); }; /** * Scheduler job interface. * Will be called by the scheduler. */ 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); } } } };
When the value of object attribute changes, watcher will trigger the callback method passed in by the constructor to monitor the change of data.
3. vue in the project
In traditional teamwork development, we usually divide the work and cooperation according to the size of the page. Each member of the team is responsible for one page or more pages, and the logic of refinement to a page is basically completed by at least one member.
Vue is developed in a componentized way, which can break down granularity more finely:
<div id="mainPage"> <map-photo-cp v-on:detailRefresh="refreshDetail"></map-photo-cp> <order-state-cp></order-state-cp> <distanse-info-cp></distanse-info-cp> <price-info-cp></price-info-cp> <driver-info-cp></driver-info-cp> <goods-package-cp></goods-package-cp> <privileges-info-cp></privileges-info-cp> <transfer-recommend-cp></transfer-recommend-cp> <order-oprate-cp></order-oprate-cp> <pay-cp></pay-cp> </div>
In the page, several modules can be detached according to their functions, and the logic of each module is relatively independent. All the data logic of the current page is managed in a model. Take map-phono-cp as an example:
var mapPhotoCp = Vue.extend({ extends: baseCp, template:template, created:function(){ }, methods:{ onOrderDetailReady:function(data){ }, initMapLink:function(){ }, statusInArray:function(status,array){ }, tap:function(){ } }, data:function(){ } }); Vue.component('map-photo-cp', mapPhotoCp);
A component is instantiated and registered as a global component of Vue through Vue.component, so that when Vue is initialized, all the corresponding components in the page DOM will be parsed.
Each component can directly manipulate the model of the page. When the model changes, the vue maps the data changes directly to the view. In this way, every component developer will have a clearer development idea, there will be no more complex DOM operations, there will be no more behavior to obtain DOM node binding events, so that development becomes smoother.
Finally, we look forward to the future of vue. vue, as a rising star of mvvm, has a very high degree of attention. This is not only some of the features mentioned above, but also inherits most of the advantages of previous frameworks. For example, virtual DOM is also supported in vue2.0, which makes the operation of DOM more efficient, and supports SSR (server side render), which is better for accelerating the rendering of the first screen. Support.