1,爲何要用vuecss
大前端目前已經到一個空前的繁榮階段,各類框架類庫層出不窮,我想每選擇一個框架,確定都能找到高度同質化的兩到三個框架。那麼在目前mvvm盛行的前端架構下,咱們爲何選擇了vue,而不去用react,不用angular呢?html
首先他們都是很是典型的前端mvvm框架,很好的解決了業務邏輯中view和model之間的關係,用到這些框架以後,咱們不會再像以前使用jQuery同樣,頁面的展現和數據有高度的耦合性,甚至不在須要選擇器了。前端
而vue相比於react、angular,首先他是一位咱們國內的開發者開發的,有很好的API文檔、樣例等。國外的技術性文檔在翻譯的過程當中對譯者仍是有很高要求,不然對於大部分開發者經過簡單閱讀以後仍是很難有較深的理解;vue
其次他有一個很好的入門起點,對於不是那麼熟練node,npm,webpack和ES6的開發者來說,也能夠看着文檔demo很快的入手,進入到項目開發中,並且vue組件的模板寫法對於使用過傳統模板的開發者來講更易於理解上手。node
雖然react的jsx可以容許把設計師提供的HTML原型直接黏貼過去,但依然有大量的改造工做,而這部分的改造對於開發者來講就像是須要掌握一門新的模板語言同樣,好比開發者要手動把class和for屬性替換成className和htmlFor,還要把內聯的style樣式從css語法改爲JSON語法等。react
在數據的雙向綁定方面,angular採用了髒檢查的方式,當有數據發生變化時,對全部的數據和視圖的綁定關係作一次檢查,當隨着ng-app根節點下的DOM變得愈加複雜的時候,髒檢查的效率會變得愈來愈低,這就要求咱們在寫業務邏輯的過程當中,須要不斷的去考慮怎麼解決框架所帶來的的性能瓶頸。而vue使用的ES5中Object.defineProperty()方法來實現model和view層的關聯,他能夠精確的將數據的變化映射到對應視圖上,和DOM的複雜度沒有正向關係。(固然vue在這裏的劣勢就是不可以在不支持ES5的瀏覽器上使用)webpack
2,vue的數據觀察者模式,先從源碼開始web
var data = { a: 1 }; var vm = new Vue({ data: data }) vm.$watch('a', function() { console.log('the data a value changed'); }); vm.a = 2;
這個實例中,當每次改變了data中a屬性的值,都會輸出 the data a value changeed,咱們看看這個過程當中到底發生了什麼。express
在Vue 2.2.4版本中會看到下面的代碼:npm
/** * 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); } };
Observer類爲每一個object的每一個屬性添加getter、setter方法,而且實現當對象屬性值發生變化時,會發出一個通知。
def( value, ‘__ob__’, this );
def方法是經過ES5的Object.defineProperty()來註冊對象,
/** * Define a property. */ function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }
this.observeArray 和 this.walk 方法中經過遞歸實現了對object的深度遍歷而且對每一個屬性綁定setter、getter方法
/** * 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(); } }); }
注意看set裏面的dep.notify方法,這個就是每次對象屬性值發生改變的時候,發出一個通知,看看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(); } };
notify是Dep中的一個方法,Dep主要實現了一個消息列表的管理,每一條消息會經過addSub方法push進來,當觸發notify時,會把全部消息update一次(在update中會作diff判斷,沒有發生改變的狀態,不會被作邏輯處理)
接下來看看這個通知是怎麼被接收到的,類庫裏面有一個Watcher類專門處理被接受到的消息,Watcher的構造函數以下:
/** * 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); } } } };
當object屬性的值發生變化時,會觸發watcher經過構造函數傳入的callback方法,最終實現對數據變化的監聽。
3,項目中的vue
在傳統的團隊協做開發中,一般會按照頁面的粒度來分工合做,團隊每一個成員負責一個頁面或者多各頁面,基本上細化到一個頁面的邏輯至少由一位成員來負責完成;
Vue按照組件化的方式開發,可以把粒度化拆解的更細:
<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>
頁面中能夠按照功能拆接出若干個模塊,其中每一個模塊的邏輯都相對獨立。當前頁面的全部數據邏輯在一個model裏面管理, 以map-phono-cp爲例:
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);
實例化出來一個組件,經過Vue.component來註冊成Vue的全局組件,這樣在初始化Vue的時候,會將頁面DOM中對應的全部組件解析。
每個組件均可以直接操做頁面的model,當model發生改變,vue會將數據的變化直接映射到view上。這樣每一個組件的開發者都會有一個更清晰的開發思路,不會再有複雜的DOM操做,不會再有各類獲取DOM節點綁定事件的行爲,讓開發變得更順暢起來。
最後也來展望一下vue的將來,vue做爲mvvm的後起之秀,有着很是高的關注度,這不光是前面提到的一些特色,主要也繼承了以前框架的大部分優勢,好比在vue2.0中也支持了virtual DOM,使DOM的操做有更高的效率,而且支持SSR(server side render),對有首屏渲染加速有更好的支持。