1

1,为什么要用vue

大前端目前已经到一个空前的繁荣阶段,各种框架类库层出不穷,我想每选择一个框架,肯定都能找到高度同质化的两到三个框架。那么在目前mvvm盛行的前端架构下,我们为什么选择了vue,而不去用react,不用angular呢?

首先他们都是非常典型的前端mvvm框架,很好的解决了业务逻辑中view和model之间的关系,用到这些框架之后,我们不会再像之前使用jQuery一样,页面的展示和数据有高度的耦合性,甚至不在需要选择器了。

而vue相比于react、angular,首先他是一位我们国内的开发者开发的,有很好的API文档、样例等。国外的技术性文档在翻译的过程中对译者还是有很高要求,否则对于大部分开发者通过简单阅读之后还是很难有较深的理解;

其次他有一个很好的入门起点,对于不是那么熟练node,npm,webpack和ES6的开发者来讲,也可以看着文档demo很快的入手,进入到项目开发中,而且vue组件的模板写法对于使用过传统模板的开发者来说更易于理解上手。

虽然react的jsx能够允许把设计师提供的HTML原型直接黏贴过去,但依然有大量的改造工作,而这部分的改造对于开发者来说就像是需要掌握一门新的模板语言一样,比如开发者要手动把class和for属性替换成className和htmlFor,还要把内联的style样式从css语法改成JSON语法等。

在数据的双向绑定方面,angular采用了脏检查的方式,当有数据发生变化时,对所有的数据和视图的绑定关系做一次检查,当随着ng-app根节点下的DOM变得越发复杂的时候,脏检查的效率会变得越来越低,这就要求我们在写业务逻辑的过程中,需要不断的去考虑怎么解决框架所带来的的性能瓶颈。而vue使用的ES5中Object.defineProperty()方法来实现model和view层的关联,他可以精确的将数据的变化映射到对应视图上,和DOM的复杂度没有正向关系。(当然vue在这里的劣势就是不能够在不支持ES5的浏览器上使用)

2,vue的数据观察者模式,先从源码开始

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,我们看看这个过程中到底发生了什么。

在Vue 2.2.4版本中会看到下面的代码:

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


Jason
74 声望2 粉丝