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),对有首屏渲染加速有更好的支持。